Django Quickstart
環境配置
Django環境
pip install pipenv
pipenv shell
pipenv install django
pipenv install djangorestframework
django-admin startproject siteconfig .
python manage.py startapp student
以上完成初始化配置,建立了項目和App,此時項目的目錄結構如下。
# tree檢視目錄結構
.
├── manage.py
├── Pipfile
├── Pipfile.lock
├── README.md
├── siteconfig
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── student
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
資料庫環境
Django預設使用sqlite3資料庫,但實際工程環境一般使用mysql或者postgresql等。
(待續)
安裝完成mysql後,還需要在項目檔案做如下配置,以使用MySQL資料庫為例:
- 安裝所需的包,需要安裝
和pymysql
cryptography
- 修改
的資料庫配置settings.py
with open(os.path.join(BASE_DIR, 'siteconfig/MYSQL_PASSWORD.env')) as f:
MYSQL_PASSWORD = f.read()
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost',
'PORT': '13306',
'NAME': 'learndjango',
'USER': 'user',
'PASSWORD': MYSQL_PASSWORD,
'TEST': {
'NAME': 'testdjango',
}
}
- 修改應用的
檔案__init__.py
import pymysql
pymysql.install_as_MySQLdb()
- 執行django腳手架指令
python manage.py makemigrations
python manage.py migrate
編寫代碼
Django的官方文檔入門教程還是很全面的,講解了後端和前端的基本代碼,最基本的背景一般包括實作資料庫ORM的Model(models.py),以及管理背景admin(admin.py),最基本的前台,包括網站的兩級路由配置(urls.py),視圖層實作業務邏輯(views.py),以及模闆層提供html頁面(templates/*.html)。
後端開發
Django的後端包括Model層和admin管理背景。
ORM層(models.py)
Django的Model API包括字段選項和字段類型兩個部分,詳情見Model官方文檔。
from django.db import models
class Student(models.Model):
SEX_ITEMS = [
(1, '男'),
(2, '女'),
(0, '未知')
]
STATUS_ITEMS = [
(0, '申請'),
(1, '通過'),
(2, '拒絕')
]
name = models.CharField(max_length=128, verbose_name='姓名')
sex = models.IntegerField(choices=SEX_ITEMS, verbose_name='性别')
profession = models.CharField(max_length=128, verbose_name='職業')
email = models.EmailField(verbose_name='郵箱')
qq = models.CharField(max_length=128, verbose_name='QQ')
phone = models.CharField(max_length=128, verbose_name='手機')
status = models.IntegerField(choices=STATUS_ITEMS, default=0, verbose_name='稽核狀态')
created_time = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="建立時間")
def __str__(self) -> str:
return f'Student: {self.name}'
class Meta:
verbose_name = "學員資訊"
verbose_name_plural = verbose_name
管理背景(admin.py)
為快速開發,一般會為每個模型注冊一個管理背景,以便直接操作資料庫,具體可以參考下ModelAdmin options。
- 背景清單頁的url為/admin/model/model
- 背景編輯頁的url為/admin/model/model/add或者/admin/model/model/id/change
比較常見的管理背景選項有:
ModelAdmin options | 定義 |
---|---|
list_display | 背景清單頁上顯示的字段,預設為__str__ |
list_filter | 背景清單頁上顯示的篩選字段 |
search_fields | 背景清單頁上用于搜尋的字段 |
fieldsets | 背景編輯頁上顯示的字段 |
from django.contrib import admin
from .models import Student
class StudentAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'sex', 'profession', 'email', 'qq', 'phone', 'status', 'created_time')
list_filter = ('sex', 'status', 'created_time')
search_fields = ('name', 'profession')
fieldsets = (
(None, {
'fields': (
'name',
('sex', 'profession'),
),
}),
('進階', {
'classes': ('collapse',),
'fields': (
('email', 'qq', 'phone'),
'status',
),
}),
)
admin.site.register(Student, StudentAdmin)
前台開發
Django的前台展示資料主要是靠Template層渲染View層,使用DRF時沒有Template層,而送出資料使用Form層。
網站路由(urls.py)
Django的路由是串聯的兩級,第一級是站點檔案夾内的urls.py,第二級是各個APP檔案夾内的urls.py,兩級是url要拼接在一起,并且從上往下比對,可以最下面會放一個404的路由,可參考官方文檔的例子。
from django.contrib import admin
from django.urls import path
from django.urls.conf import include
from .views import index, Index
urlpatterns = [
path('', index, name='index'),
path('index', Index.as_view(), name='student_index'),
]
業務邏輯層(views.py)
網站路由把通路的url與處理的業務邏輯挂鈎,可以基于函數,也可以基于類。業務邏輯層的輸入是request,函數或類執行個體根據業務邏輯進行加工,并輸出response。在DRF的前後端分離模式下,response是一個json或xml,而在單體應用下,response是一個html。
from django import forms
from django.http.response import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.views import View
from .models import Student
from .forms import StudentForm
# 基于函數的模闆
def index(request):
students = Student.objects.all()
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
form.save()
# cleaned_data = form.cleaned_data
# student = Student()
# student.name = cleaned_data['name']
# student.sex = cleaned_data['sex']
# student.email = cleaned_data['email']
# student.profession = cleaned_data['profession']
# student.qq = cleaned_data['qq']
# student.phone = cleaned_data['phone']
# student.save()
return HttpResponseRedirect(reverse('student_index'))
else:
form = StudentForm()
context = {
'students': students,
'form': form
}
return render(request, 'student/index.html', context=context)
# 基于類的模闆
class Index(View):
template_name = 'student/index.html'
def get_context(self):
context = {
'students': Student.objects.all(),
}
return context
def get(self, request):
form = StudentForm()
context = self.get_context()
context.update({'form': form})
return render(request, self.template_name, context=context)
def post(self, request):
form = StudentForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('student_index'))
context = self.get_context()
context.update({'form': form})
return render(request, self.template_name, context=context)
頁面顯示層(templates/*.html)
這裡會用到django自創的模闆指令,此外,django的模闆引擎還可以換成Jinja2,模型字段的引用可以參考官方文檔。視圖通過調用
render(request, template_name, context)
函數,來渲染模闆,進而得到response。
表單輸入(forms.py)
Django可以事先定義好表單,然後在模闆中使用。表單既可以繼承自forms.Form類,字段設定參考這裡,也可以繼承自forms.ModelForm,直接複用Model的代碼,詳情可參考官方文檔。此外,django的表單還可以對輸入的資料進行校驗。
中間件
Django的中間件在前端/後端之前被調用,而且會用于所有的請求/響應,Django架構提供了
django.utils.deprecaion.MiddlewareMixin
類以便于使用者自定義站點的中間件。
MiddlewareMixin
的接口按順序包括:
-
:傳回None才會執行後續的方法和中間件,傳回HttpResonse則不會。process_request(self, request)
-
process_view(self, request, func, *args, **kwargs)
-
process_exception(self, request, exception)
-
:使用了模闆才會調用。process_template_response(self, request, response)
-
:視圖層業務處理或者模闆層渲染發生異常時才會調用。process_response(self, request, response)
process_response
似乎一定會被調用,但
process_view
等則不一定,例如不涉及業務處理,僅僅重新整理頁面的時候。
import time
from django.http import response
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
class TimeItMiddleware(MiddlewareMixin):
def process_request(self, request):
self.start_time = time.time()
return None
def process_view(self, request, func, *args, **kwargs):
if request.path != reverse('index'):
return None
start = time.time()
response = func(request)
costed = time.time() - start
print('process view: {:.2f}'.format(costed))
return response
def process_exception(self, request, exception):
pass
def process_template_response(self, request, response):
costed = time.time() - self.start_time
print('render cost: {:.2f}'.format(costed))
return response
def process_response(self, request, response):
costed = time.time() - self.start_time
print('request to response cost: {:.2f}'.format(costed))
return response
測試
Django提供了一個
django.test.TestCase
的基類,開發者可以繼承此類來實作自己的單元測試,如果不涉及資料庫(測試資料與生産資料是隔離的),則可以使用
SimpleTestCase
作為基類,測試類提供的接口包括:
-
:初始化環境。setUp(self)
-
:待測試的方法,均會被執行。test_xxxx(self)
-
:清理測試環境。tearDown(self)
from django.forms.fields import EmailField
from django.test import TestCase
from .models import Student
class StudentTestCase(TestCase):
def setUp(self):
Student.objects.create(
name='1号玩家',
sex = 1,
email = '[email protected]',
profession = '法師',
qq = '11',
phone = '111'
)
def test_create_and_sex_status(self):
student = Student.objects.create(
name='2号玩家',
sex = 2,
email = '[email protected]',
profession = '戰士',
qq = '22',
phone = '222'
)
self.assertEqual(student.get_sex_display(), '女', '碼表有誤')
def test_filter(self):
Student.objects.create(
name='3号玩家',
sex = 1,
email = '[email protected]',
profession = '律師',
qq = '33',
phone = '333'
)
name = '3号玩家'
students = Student.objects.filter(name=name)
self.assertEqual(students.count(), 1, '過濾器有誤')
def test_get_index(self):
client = self.client
response = client.get('/student/')
self.assertEqual(response.status_code, 200, 'homepage not availabel')
def test_post_index(self):
client = self.client
data = dict(
name='4号玩家',
sex = 4,
email = '[email protected]',
profession = '警察',
qq = '44',
phone = '444'
)
response = client.post('/student/', data)
self.assertTrue(b'[email protected]' in response.content, 'post失敗')