--- 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() ```