---
title: Django網站系統部署
tags: Django, python, deployed
---
# Django網站系統部署
> [TOC]
>
> Reference Website:
> 1. [How To Deploy Django on Ubuntu 18.04 using Apache and MySQL](https://www.youtube.com/watch?v=Xjdv31k-Kf4&fbclid=IwAR1t_y17vgL5ZlPHG2TLxppriZNkTOMseaB_1OaTWK7VylyJjYGOO1mReuM)
> 2. [How to use Django with Apache and mod_wsgi](https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/modwsgi/)
> 3. [[Django] Window Apache 架站](https://zwindr.blogspot.com/2017/09/django-window-apache.html)
> 4. [利用 Mod_wsgi 在 Apache 上部屬 Django](https://jerrynest.io/mod-wsgi-apache-django/)
> 5. [Django + Apache 部署](https://segmentfault.com/a/1190000017150991)
> 6. [Django 部署(Apache)](https://code.ziqiangxuetang.com/django/django-deploy.html)
> * [How to configure multiple websites with Apache web server](https://opensource.com/article/18/3/configuring-multiple-web-sites-apache)
---
## 環境要求與基礎確認 (USE terminal)
* OS:Linux - ubuntu 18.04
* Framework - Django
* Django專案:`stat01`
* Database - MySQLDB
* 伺服軟體 - Apache
* IP address - 140.136.153.182 (實體IP)
```
sudo apt install net-tools
ifconfig
```
* python 確認
```
python
```
:::info
> **108扶秀作品集**
>
* OS:Linux - Debian 10
* Framework - Django
* Django專案:`Portfolio_Voting`
* Database - MariaDB
* 伺服軟體 - Apache
* IP address - 140.136.202.234 (實體IP)
* Python, Git
* ssh
```
white504@white504-System-Product-Name:~$ ssh user@140.136.202.234
user@140.136.202.234's password:
Linux hecct 4.19.0-9-amd64 #1 SMP Debian 4.19.118-2+deb10u1 (2020-06-07) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Jul 11 19:49:50 2020 from 140.136.153.167
```
```
user@hecct:~$ su - root
Password:
root@hecct:~#
```
* Can not use `apt install ...`?
* Question
* 一開始在設定的時候只有設學校的 DNS ,所以根本沒辦法解析 debian 的 domain 啊…
* Solve
* 解決辦法就直接到 /etc/resolv.conf 加上
```
nameserver 127.0.0.1
nameserver 8.8.8.8
```
就搞定了啊= =
到底為啥會搞那麼久啊…
:::
---
> * 先安裝Django和MySQLDB,接著安裝並設定Apache,最後才能從外網連到網站。
> * MySQLDB的部分:
> * 安裝mysql-server, mysql-client...,然後再把Django與MySQLDB做連結,這樣資料才會寫進MySQLDB裡。
---
## **Step 1: Python安裝與虛擬環境建立 + Django安裝**
* 安裝 Python3 & pip3
```
sudo apt install python3-pip
```
* 安裝虛擬環境套件
```
sudo su
sudo pip3 install virtualenv
```
* 顯示目前所安裝的套件
```
pip3 freeze
pip freeze
```
* 移至根目錄建立`site_test`資料夾,並建立與啟用虛擬環境
```
cd /
mkdir site_test
cd site_test/
virtualenv venv -p python3
source venv/bin/activate
```
* 安裝虛擬環境&Django套件
```
pip install django
```
:::success
* 備註:
* 完整解除安裝 + 刪除其配置的文件
```
sudo apt autoremove --purge python3
```
* 安裝 get-pip.py
```
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py --user --force-reinstall
```
* 安裝 gnome-terminal
```
sudo apt install --reinstall gnome-terminal
```
* 安裝 文字編輯器 geany
```
sudo apt install geany
```
* set mysql password policy
```
set global validate_password_policy=LOW;
```
:::
---
## **Step 2: 建立Django專案 + 修改`settings.py`**
* 準備已做好的Django project`stat01` (github範例檔)
```
sudo apt install git
git clone https://github.com/stat-website/stat_begin.git
```
* 建立Django project`stat01`及其網頁內容
```
django-admin startproject stat01
cp -r /home/testchia/stat_begin/stat01 .
```
* 查詢IP並修改`settings.py`
* 查詢IP
```
ifconfig
```
* 修改`settings.py`
```
cd stat01/stat01
nano settings.py -> ALLOWED_HOSTS = ['140.136.153.182']
```
:::success
* 練習:
* 建立Django project
```
django-admin startproject tutorial
```
* 建立Django app
```
cd tutorial
python manage.py startapp app
```
* 下指令刪除整個Django project
```
rm -r tutorial
```
:::
---
## **Step 3: MySQLDB安裝與設定**
> [How To Install MariaDB on Debian 10](https://www.digitalocean.com/community/tutorials/how-to-install-mariadb-on-debian-10)
> :star::star::star: [MariaDB connected with Django (Bible Note)](https://drive.google.com/file/d/1-inWbB9iqCIrxRkes-JbKNR8Y4cqfZJl/view?usp=sharing) :star::star::star:
* 使用Terminal安裝MySQLDB
```
sudo apt update
sudo apt upgrade
sudo apt install mysql-server
sudo mysql_secure_installation
sudo apt install mysql-client
sudo apt install python3-dev libmysqlclient-dev
```
> Mariadb
> ```
> sudo apt update
> sudo apt upgrade
> sudo apt install mariadb-server mariadb-client
> sudo mysql_secure_installation
> ```
* 確認 mysql / mariadb 有開啟
```
systemctl status mysql.service
```
* 進入 MySQL/ mariadb 建立Database,以供Django使用
```
mysql -u root -p
CREATE USER <testchia_test>@localhost IDENTIFIED BY '<password>';
CREATE DATABASE <db_demo> CHARACTER SET UTF8;
GRANT ALL PRIVILEGES ON <db_demo>.* TO <testchia_test>@localhost;
SHOW DATABASES;
```
* 安裝mysqlclient套件
```
pip install mysqlclient
```
> Mariadb does not need!
* migrate + check Database tables
```
python manage.py migrate
```
```
mysql -u root -p
SHOW DATABASES;
USE db_demo;
SHOW TABLES;
exit
```
* runserver
```
python manage.py runserver 140.136.153.182:80
python manage.py runserver 0.0.0.0:8000
```
:::success
* Note:
* Only, Mariadb 需要安裝`pymysql`套件
* `pip3 install pymysql`
---
* Createsuperuser
```
python manage.py createsuperuser
```
* 打開瀏覽器
```
140.136.153.182/admin
```
:::
---
## **Step 4: Apache2**
* 安裝Apache2
```
sudo apt install apache2 libapache2-mod-wsgi-py3
```
* 開啟瀏覽器 `140.136.153.182`
* 修改Apache2設定 (建立`stat01.conf`)
```
cd /etc/apache2/
cd sites-available/
cp 000-default.conf stat01.conf -> copy as stat01.conf
sudo nano stat01.conf
```
* `stat01.conf`
```
<VirtualHost *:80>
ServerName 140.136.153.182
ServerAdmin webmaster@localhost
DocumentRoot /site_test
ErrorLog /site_test/error.log
CustomLog /site_test/access.log combined
<Directory /site_test/stat01/stat01>
<Files wsgi.py>
Require all granted
Require ip 127.0.0.1
</Files>
</Directory>
WSGIDaemonProcess site_test python-path=/site_test/stat01 python-home=/site_test/venv
WSGIProcessGroup site_test
WSGIScriptAlias / /site_test/stat01/stat01/wsgi.py
</VirtualHost>
```
* 檢查語法
```
sudo apachectl configtest
```
:::info
==Apache啟動報錯==
* [(98)Address already in use: AH00072: make_sock: could not bind to address [::]:80](https://blog.csdn.net/crazw/article/details/8266751)
* 解决方法:
```
$ netstat -lnp|grep 80
tcp 0 0 192.168.0.102:80 0.0.0.0:* LISTEN 7700/python
$ sudo kill -9 7700
$ systemctl restart apache2
$ systemctl status apache2.service
```
* [解決啟動 Apache 網站伺服器時找不到 ServerName 的問題](https://blog.miniasp.com/post/2012/06/23/apache2-Could-not-reliably-determine-the-server-fully-qualified-domain-name-using-for-ServerName)
* 解决方法:
```
$ echo "ServerName 192.168.0.102" >> /etc/apache2/apache2.conf
$ systemctl restart apache2
$ systemctl status apache2.service
```
:::
---
## **Step 5: 套用`stat01.conf`設定 + 重新啟動Apache2**
* 關閉預設`000-default.conf`、開啟`stat01.conf`
```
a2dissite 000-default.conf
a2ensite stat01.conf
```
* 載入設定 + 重新啟動Apache2
```
systemctl reload apache2
systemctl restart apache2
```
```
/etc/init.d/apache2 restart
```
* 查看Apache2狀態
```
systemctl status apache2.service
```
## *Step 6: Apache2的靜態檔案路徑設定*
* 建立一個新資料夾,修改`settings.py`
```
mkdir /site_test/stat01/static/all_static
nano settings.py
```
* 修改`settings.py`
```
STATIC_URL = '/static/'
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static'),
]
STATIC_ROOT = '/site_test/stat01/static/all_static'
```
* 下指令將所有靜態檔案蒐集至此資料夾之下(admin interface & static)
```
python manage.py collectstatic
```
* 修改`stat01.conf`
```
nano /etc/apache2/sites-available/stat01.conf
```
```
Alias /static /site_test/stat01/static/all_static
<Directory /site_test/stat01/static/all_static>
Require all granted
</Directory>
```
* 重新啟動Apache2
> systemctl restart apache2
:::success
* Note:
* `settings.py`
```
DEBUG = FALSE
```
:::
---
# 完整程式碼
## `settings.py`
```python=
# -- coding:UTF-8 --
"""
Django settings for about_01 project.
Generated by 'django-admin startproject' using Django 2.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'v4q50ygn$$t!oa#)+0(58326ai4t8mr=wmc(+o+!q9!+pvvt)8'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['140.136.153.182']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'_student',
'_teacher',
'_about',
'_contact',
'_research',
'_class',
'_join',
'_sitemap',
'_download',
'_bonus',
'_association',
'_news',
'_sitemember',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'stat01.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')], #新增
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'stat01.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db_demo',
'USER': 'testchia_test',
'PASSWORD': 'testchia',
'HOST': 'localhost',
'PORT': '',
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'zh-Hant' #新增
TIME_ZONE = 'Asia/Taipei' #新增
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS=[ #新增
os.path.join(BASE_DIR,'static'), #注意,BASE_DIR 不用加引號
]
STATIC_ROOT = '/site_test/stat01/static/all_static'
```
---
## `stat01.conf`
```
<VirtualHost *:80>
ServerName 140.136.153.182
ServerAdmin webmaster@localhost
DocumentRoot /site_test
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
ErrorLog /site_test/error.log
CustomLog /site_test/access.log combined
<Directory /site_test/stat01/stat01>
<Files wsgi.py>
Require all granted
Require ip 127.0.0.1
</Files>
</Directory>
Alias /static /site_test/stat01/static/all_static
<Directory /site_test/stat01/static/all_static>
Require all granted
</Directory>
WSGIDaemonProcess site_test python-path=/site_test/stat01 python-home=/site_test/venv
WSGIProcessGroup site_test
WSGIScriptAlias / /site_test/stat01/stat01/wsgi.py
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
```
## `__init__.py` (Project Level)
```python=
#import pymysql
#pymysql.install_as_MySQLdb()
```
---
# *108扶秀作品集 `hecct.fju.edu.tw`*
## `/etc/apache2/site-available/hecct.conf`
```
<VirtualHost *:80>
#ServerName www.hecct.fju.edu.tw
ServerAdmin webmaster@localhost
DocumentRoot /var/www/FJU_Portfolio_Voting/Portfolio_Voting
ErrorLog /var/www/error.log
CustomLog /var/www/access.log combined
Alias /static/ /var/www/FJU_Portfolio_Voting/Portfolio_Voting/staticfiles/
<Directory /var/www/FJU_Portfolio_Voting/Portfolio_Voting/staticfiles>
Require all granted
</Directory>
#Alias /media/ /var/www/FJU_Portfolio_Voting/Portfolio_Voting/media/
#<Directory /var/www/FJU_Portfolio_Voting/Portfolio_Voting/media>
# Require all granted
#</Directory>
<Directory /var/www/FJU_Portfolio_Voting/Portfolio_Voting/Portfolio_Voting/>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIDaemonProcess Portfolio_Voting python-path=/var/www/FJU_Portfolio_Voting/Portfolio_Votin$
WSGIProcessGroup Portfolio_Voting
WSGIScriptAlias / /var/www/FJU_Portfolio_Voting/Portfolio_Voting/Portfolio_Voting/wsgi.py
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
```
## `settings.py`
```python=
ALLOWED_HOSTS = ['140.136.202.234', 'hecct.fju.edu.tw', '*']
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'hecctdb',
'USER': 'admin',
'PASSWORD': 'zj6vu.4yji4qup3ru6',
'HOST': 'localhost',
'PORT': '',
}
}
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # will build a file 'media' in same level path as manag$
#
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
```
## `__init__.py` (Project Level)
> pip3 install pymysql
>
```python=
import pymysql
pymysql.version_info = (1, 3, 13, "final", 0)
pymysql.install_as_MySQLdb()
```