Django3.2.x 学习实践
前言
最近因为学校要用,需要写一个 web 服务器,但是我不想学 java 系列的 spring 框架,又有之前使用 python socket 框架的老底,于是打算学习一下 Python 下的重量级框架 Django。
我几乎是完全看着 Django 的官方教程学的,Django 官方教程实属良心,翻译得也非常得符合中文语言环境。
一、Django 的部署
1.1 检查 Python 版本与 Django 版本
Django 官方文档:我应该使用哪个版本的 Python 来配合 Django
我在本地 Windows 10 (Windows Subsystem Linux Ubuntu 20.04 LTS)
安装了 Python 3.8.5
。腾讯云云服务器 Ubuntu 18.04 LTS
上,默认安装了 Python 2.7.17
和 Python 3.6.9
(这两个版本我决定不了)。最终决定在本地和云端均部署 Django 3.2.x
。
查询本地已经部署的 python
版本,命令行输入:python3 --version
。Windows Subsystem Linux
的用户一定要注意,WSL
中的 Python
版本可能与 Windows
下的 Python
版本不同。
1.2 使用 python venv 建立并切换到虚拟环境
为什么需要安装虚拟环境:虚拟环境简介(英文)。虚拟环境简介一文比较完整地介绍了如何建立虚拟环境,以及如何切换到虚拟环境,下文中关于虚拟环境的内容都可以在其中找到。
第一步:切换到想要安置虚拟环境的目录
第二步:在命令行执行 python3 -m venv [环境名]
例如:执行 python3 -m venv tutorialEnv
后,python
会在在生成的 ./tutorialEnv
目录下部署一个相同版本的 python
虚拟环境。
如果当前 python
并没有安装 venv
包,执行上述指令时 python
解释器会报错,根据报错信息安装 venv
包即可。
第三步,切换到虚拟环境:
Windows 下:tutorial-env\Scripts\activate.bat
Linux 下:source tutorial-env/bin/activate
(bash / zsh)
在 Linux 下,如果你使用
csh
或者fish
作为自己的shell
,切换到虚拟环境需要使用命令如下:
csh
:source tutorial-env/bin/activate.csh
fish
:source tutorial-env/bin/activate.fish
1.3 在虚拟环境中部署 Django
为什么一定要在虚拟环境中部署 Django
- 避免
Django
使用的包与原有环境内的包发生版本冲突; - 如果在虚拟环境与真实环境中同时安装了
Django
,会发生那一预测的麻烦。
如何判断自己在虚拟环境中:
观察命令行中命令提示符的变化:
在虚拟环境外:目录$
在虚拟环境中:(虚拟环境名) 目录$
在
python
命令行中输入以下语句:
>>> import sys; print(sys.path)
观察到site-package
目录在虚拟环境目录内,则说明已经切换到了虚拟环境。
确保自己在虚拟环境中后,使用 pip
安装 Django
。(注:如果您的电脑上之前有 python2
与 python3
两个版本的 python
,在虚拟环境中直接输入 python
,执行的就是虚拟环境中的 python
解释器,不需要再区分 python
与 python3
两种命令。同理,不需要区分 pip
与 pip3
)
python -m pip install django==3.2.11 # 版本号根据具体需要填写
检查是否成功安装了 Django
:pip freeze
可以列出当前 python
环境下所有已经被安装的包,在其中寻找 Django
对应的行即可。观察到如下内容:
Django==3.2.11
说明已经成功安装了 Django 3.2.11
。
1.4 检查 Django 版本
在 python
命令行中检查 Django
的版本:
>>> import django
>>> print(django.get_verion())
3.2.11
在命令行中检查 Django
版本:
$ python -m django --version
二、Django 项目的创建与结构
2.1 创建 Django 项目
第一步:切换到安装了 Django
的虚拟环境(具体方法见:“1.2. 使用 python venv 建立并切换到虚拟环境”);
第二步:cd
到你想要存放项目文件的目录;
第三步:命令行执行 django-admin startproject [项目名称]
创建项目。
注:如果你是在虚拟环境中部署的 Django
,那么理论上 django-admin
所在的目录会被注册到虚拟环境的 $PATH
中,也就是你可以直接使用 django-admin
这一命令。如果你是在真实环境中部署的 Django,那么可能需要手动将 django-admin 所在的目录 export
到 $PATH
中。因此不建议将 Django
部署到真实环境中。
2.2 Django 项目的文件结构
[项目名称]/ # 项目目录
|- manage.py # 项目管理程序
|- [项目名称]/ # 一个 Python 包
|- __init__.py # 告诉 Python,这个文件夹是一个 Python 包
|- settings.py # 配置文件
|- urls.py # url 调度器
|- asgi.py # 配置 ASGI web Server 入口
|- wsgi.py # 配置 WSGI web Server 入口
2.3 settings.py 中的一些内容
设置语言:LANGUAGE_CODE='zh-hans'
(设置后,Django 管理员界面将提供汉化)
设置时区:TIME_ZONE='Asia/Shanghai'
调试模式:DEBUG=True
(Django 默认采用调试模式,在工业环境中,要置 DEBUG=False
)
注册 Django 应用:INSTALLED_APPS
中列出了所有当前启用的 Django 应用,将来我们自己实现应用时,也须要将应用的注册类的名字放到这个 list
中(详见后文)。
注意:如果您正在配置云服务器上的 Django 项目,而且希望能够通过域名的方式访问您的服务器。您需要修改 settings.py
中的 ALLOWED_HOSTS=[]
,并在中括号中填入所有可用的域名。
2.3’ 配置 SMTP: 以 QQ 邮箱的 POP3/SMTP 为例
第一步:登录 mail.qq.com,开启 POP3/SMTP
功能(可能需要使用密保手机短信验证)。“设置” => “账户” => “POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”
第二步:在 settings.py
中添加以下内容:
EMAIL_HOST = "smtp.qq.com"
EMAIL_HOST_PASSWORD = "[QQ提供的邮箱授权码]"
EMAIL_HOST_USER = "[QQ账号 或 邮箱名]"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
一般来说,只有配置了以上内容才能通过Django
发送邮件。据说 Django
的 SMTP
功能本质上是对 smtplib
包的封装。
2.4 创建超级管理员
Django 提供了非常方便的后台管理界面,使用 Django 提供的接口,我们可以使用后台管理界面可视化地修改数据库中存储的内容。后台管理界面需要使用账号密码登录,因此需要先使用命令行创建一个超级管理员。
第零步:确定自己在部署了 Django
的 python
虚拟环境中;
第一步:cd
到 manage.py
所在的目录;
第二步:执行 python manage.py migrate
进行初次数据库迁移;
第三步:执行 python manage.py createsuperuser
,接下来按照提示输入即可。
2.5 Django 服务器的启动与停止
第零步:确定自己在部署了 Django
的 python
虚拟环境中;
如果你是在本地对 Django 项目进行测试:
第一步:cd
到 manage.py
所在的目录;
第二步:执行 python manage.py runserver [端口号]
,如在 Linux 系统下使用了系统敏感的端口号,请在指令前加 sudo
,不写端口号默认端口为 8000
;
停止 Django
服务器:Ctrl+C
结束正在运行的程序即可(由于 Django 会检测代码的变化从而动态重新加载,通常情况不需要停止服务器,只有当新增了文件/目录时才需要重新启动正在运行的服务器)。
如果你是在服务器上启动 Django 项目(以下方法适用于 Ubuntu 18.04
):
第一步:同上。
第二步:执行 nohup python manage.py runserver inner_ip:[端口号] &
这里的 inner_ip
是服务器在内网中的 IP。nohup
语句用于忽略命令行的输入并将程序的输出重定向到文件 nohup.out
中。
第三步:Ctrl+Z
将程序挂起,再使用 bg %1
将刚刚挂起的程序放于后台继续执行。
在不同(版本)的操作系统上,上述启动方式不一定可用。
注意:当您使用 sudo
语句提升权限时,调用的 python
默认是系统目录下的 python
(甚至是 python2
),而不是虚拟环境下的 python
。
2.6 登录 Django 管理员界面
第一步:使用 2.5 中给出的方法启动 Django 服务器;
第二步:浏览器访问 http://127.0.0.1:[端口号]/admin/
(本地测试)或 http://ip:[端口号]/admin/
访问管理员界面(ip 为服务器的公网 IP)。
现在管理员界面还几乎什么都没有,只有一个用户管理的功能,后期我们会使用管理员界面操作数据库中的数据。
三、Django 应用的创建与结构
3.1 Django 应用的创建
Django 的应用是一个 python 包,这意味着,你可以方便地将一个 Django 应用移植到其他项目中。尽管如此,在创建一个 Django 应用时,你还是需要一个已有的 Django 项目中的 manage.py
。
第一步:cd
到 manage.py
所在的目录;
第二步:执行 python manage.py startapp [应用名称]
。
第三步:在 [项目名]/settings.py
中的 INSTALLED_APP
列表中注册这个应用。即添加一行内容:
INSTALLED_APP = [
'[应用名A].apps.[应用名B]Config', # 一个字符串
... # 其他应用略
]
这里的 应用名A
就是你的应用的名字,应用名B
是采用大驼峰命名法变换后得到的名字。在创建应用时,[应用名]/apps.py
下回自动生成一个 Config
类,具体的名字详见 apps.py
。
3.2 Django 应用的文件结构
[应用名称]/
|- __init__.py
|- admin.py # 用于将数据库中的表注册到管理员界面
|- apps.py
|- migrations/
| |- ... # 存放数据库迁移文件(略)
|- model.py # 对象关系映射(ORM) 描述数据库的结构
|- test.py # 存放用于自动测试的脚本(略)
|- view.py # 存放所有 视图类/视图回调函数
|- urls.py # 根据需要,自己手动创建
|- templates/ # 自己手动创建
| |- [应用名称]/
| |- ... # 该文件夹存放 .html 模板文件
|- static/ # 自己手动创建
|- [应用名称]/
|- ... # 该文件夹存储所有需要用到的 图片/脚本/css 等静态文件
为什么需要在 templates 目录以及 static 目录下创建一个 名字与 应用名称相同的子目录:
由于 Django 提供的文件查找功能是基于路径后缀匹配的,这样设计方便使用 [应用名称]/[文件名]
获取 模板或静态文件 等资源文件。当然,直接把所有资源文件都直接放在应用目录下也不是不行,但是管理起来起来比较混乱。
3.3 Django 应用的 MVT 架构
MVT
架构与 MVC
架构在思想上有异曲同工之妙,MVT
指 Model/View/Template
结构,以下内容是对 MVT
架构的一个感性的认识(因为我写的不好,所以第一次看很可能不知所云…)。
应用的 Model
是指 “数据模型”,换言之,数据库中的表格。models.py
中的每个类(models.Model
的子类)对应着数据库中的一个表。
应用的 View
是指 “视图(回调)函数/类”,是从 HttpRequest
到 HttpResponse
的映射,是 python
的函数或者类。Django 框架会根据 urls.py
中指定的 “路由方法”,根据 HttpRequest
的 URL 为其找到指定的 views.py
中的视图函数用来回应。
应用的 Template
是指 “视图模板”,是存放在应用中的 templates/[应用名]/
目录下的 .html
文件。文件中包含了一些预处理脚本,经过 Django 的渲染,能够根据填入的 context
数据生成一个完整的 html
文件。
MVT 和 MVC 的对应关系大概是:Model -> Model, View -> Controller, Template -> View。
3.4 在应用中创建一个数据表格的基本流程
第一步:在 models.py
中新建一个类,并使用类变量指出该表格的所有字段,例如:
# ---------- [应用名]/models.py ----------
from django.db import models
class Question(models.Model): # 在数据库中创建一个表格,成员为 Question 类对象
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published') # 'date published' 是该字段的别称
def __str__(self): # __str__ 方法有助于测试与调试时看清记录的内容,可以不实现
return question_text
如果你的表格类中没有指出哪个字段是“主键”,Django
会自动为你创造一个叫做 id
的 IntegerField
字段作为主键。其值从 1 开始自动递增,不允许出现重复的值,否则会抛出异常。
第二步:在 admin.py
中注册这个数据库到后台管理界面;
# ---------- [应用名]/admin.py ----------
from django.contrib import admin
from .models import Question # 在 models.py 中已经定义好了一个叫 Question 的类
# ---------- 注册方法一:简单注册 ----------
admin.site.register(Question)
# ---------- 注册方法二:使用注册类注册 ----------
class QuestionAdmin(admin.ModelAdmin):
... # 内容详见附录,可以根据表格的特点定制管理界面
admin.site.register(Question, QuestionAdmin)
第三步:执行 python manage.py makemigrations [应用名]
,生成迁移文件;
第四步:执行 python manage.py migrate
,完成数据迁移。
由于在 models.py
中的修改并不能直接体现在数据库当中,换言之,执行完第一、第二两个步骤后,数据库中的表格结构还没有发生任何改变。需要通过迁移文件指导数据库如何进行修改,因此需要经过第三、第四两个步骤才能将 ORM
的修改迁移到数据库中。
关于字段的类型:Django 为我们提供了几十种内置类型字段,可以参考 Django 中文文档中以下内容:
模型字段参考:字段类型
尤其应该学习一下 ForeignKeyField
(外键字段)和 EmailField
(电子邮箱字段)的使用。
3.5 使用 python 命令行/程序 操作数据表格 (Model)
第一步:在虚拟环境下 cd
到 manage.py
所在的目录;
第二步:命令行执行 python manage.py shell
,虽然结果上看起来与直接命令行启动 python
大同小异,但是这种启动方式配置了更多的项目内的搜索路径,保证 Django
项目中的子应用模块能够正确导入并执行。
第三步:执行查询(方法巨多,下文只介绍最基础的几种方法)。
Django 官方文档:执行查询
虽然执行查询的 QuerySet
对象细节颇多,但是如果你的数据库中内容很少,可以使用一种傻瓜式的办法获取某个表格中的全部数据:
from [应用名].models import [表格类]
# ---------- 1. 获取表格中的全部记录 ----------
allRecords = [表格类].objects.all() # 返回一个 QuerySet 对象,使用上类似 list 但是其具有惰性
# ---------- 2. 使用主键获取表格中的一个记录 ----------
oneRecord = [表格类].objects.get(pk=primeKeyValue) # primeKeyValue 是主键值
# 请注意,如果找不到主键为 primeKeyValue 的对象,Django 会抛出一个异常
# get 方法也可以像类似 filter 方法一样进行筛选,筛选不到对象/筛选出不止一个对象,都会抛出异常
# ---------- 3. 向表格中新增一个对象 ----------
newRecord = [表格类](...) # 调用构造函数,初始化一个[表格类]对象
newRecord.save() # 将这个新增的对象存储到数据库中
这些用于 python
命令行的查询方法同样可以适用于 views.py
中,views.py
中经常需要进行数据库查询操作。(在 views.py
的试图回调函数设计中,我们经常使用 get_object_or_404
的方法替代 objects.get(...)
方法。)
四、urls.py 与 views.py 的结构、功能与设计方法
4.1 [项目名]/urls.py 的文件结构
一个典型的 [项目名]/urls.py
具有类似这样的结构:
# ---------- [项目名]/urls.py ----------
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls), # Django 默认生成的
path('[应用名]/', include('[应用名].urls')), # 对于每个应用,在这里都会对应一行内容
# path - include 结构
]
[项目名]/urls.py
的主要功能是将来自客户端的 HttpRequest
分发给下属的应用(django.conrib.admin.site
也可以认为是一个 Django
自带的应用)。[应用名]/urls.py
也具有与 [项目名]/urls.py
类似的结构。例如当客户端浏览器访问 http://你的IP:端口/service_app/check_prime/135/
时,Django 会将 service_app/check_prime/135/
与 [项目名]/urls.py
中 urlpatterns
指出的 URL 模式一一进行前缀匹配。如果 urlpatterns
中有:path('service_app', include('service_app.urls')),
这一行,则,Django 会从 URL 中截去已经匹配了的前缀,将剩余部分 check_prime/135/
发送给 service_app/urls.py
进行递归匹配,直到找到了对应的视图函数为止。
项目中的所有 urls.py
文件,以 pah('XXX/', include('XXX.urls')),
的形式被组织成了一种树状结构,每个叶子结点就对应了一个视图函数。
4.2 [应用名]/urls.py 的文件结构
与 [项目名]/urls.py
相比,[应用名]/urls.py
更加多元,也更加复杂。
# ---------- [应用名]/urls.py ----------
from django.urls import path
from . import views # 当前应用的 views.py
app_name='[应用名]' # 可以不写,但是最好写上
urlpatterns = [
path('', views.IndexView.as_view(), name = 'index'), # 调用 视图类 views.IndexView(需要自己在 views.py 中定义)
path('vote/<int:question_id>/', views.vote, name = 'vote'), # 调用 views.vote 视图函数(需要自己在 views.py 中定义)
]
可以看到,无论是视图函数,还是视图类,都需要自己在 views.py
中预先定义好。这里的 name
参数用来给 URL 模式命名,app_name
是当前文件中定义的所有 URL 模式的“名字空间”。在我们将来设计 .html
模板文件时,将可以用到现在通过 name
和 app_name
指定的 URL 模式。
上文中的 <int:question_id>
表示采用正则表达式匹配一个整数,并将这个整数传递给函数 views.vote
作为参数 question_id
。换言之,这要求我们在定义 vote
这个函数的时候,必须要有 question_id
这个形参。
4.3 views.py 的设计方法
4.3.1 使用内置的试图模板派生得到视图类
序列视图 ListView
用于生成序列型视图类。例如:博客网站的 “最近博文”,在线评测网站的 “题目列表” 页面视图等可以使用序列模型实现。
from django.views import generic
from .models import [表格类]
# ---------- 方法一:为整个表格生成序列视图 ----------
class IndexView(generic.ListView): # 派生序列视图
model = [表格类] # 为哪个表格生成序列视图
template = '[应用名]/index.html' # 调用哪个模板(现在还没实现模板)
context_object_name = 'question_list' # 对模板进行渲染时,模板脚本中 context 变量的名字
# ---------- 方法二:为某一部分数据生成序列试图 ----------
class IndexView(generic.ListView):
template = '[应用名]/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self): # get_queryset 方法用于指出为哪些对象构建序列试图
return [表格类].objects.XXX # XXX 详见 3.5 执行查询
详情视图 DetialView
用于生成详情型视图类。例如:博客网站的 “博文详情”,在线评测网站的 “题目内容” 页面视图等可以使用详情模型实现。DetailView
要求在 [应用名]/urls.py
的 URL 模式 path
中提供了一个名字叫 pk
的匹配串,并将该匹配串作为主键去寻找一个表格中的对象。如果找到了这个对象,则根据 .html
模板中约定的格式,显示该对象的详细信息;否则反馈一个 Http404
Not Found 页面。
from django.views import generic
from .models import [表格类]
class DetailView(generic.DetailView):
model = [表格类]
template = '[应用名]/detail.html'
# context_object_name = '[表格类名转小写字母下划线]'
# 默认的 context 对象名会把表格类名转换成小写字母下划线,表示该类的一个对象
4.3.2 自定义视图函数从而实现更自由的功能
无论采用下述的哪种方法给出一个视图函数的返回值,视图函数的第一个参数一定是一个 HttpRequest 对象。
方法零:使用 Http404
返回 404 NotFound 页面。
# ---------- [应用名]/views.py ----------
from django.http import Http404
def vote(request, question_id):
raise Http404("Question does not exist")
# Http404 可以认为是一个异常类
# Django 服务器 接收到 raise 抛出的 Http404 类后会反馈一个 404 not found 页面
方法一:使用 HttpResponse
直接返回 HTML 页面。
# ---------- [应用名]/views.py ----------
from django.http import HttpResponse
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
# 参数应该是一段 HTML 文本,但是这种方法一般只用于测试时使用
方法二:使用 render
函数指定渲染的模板,以及 context
对象,并返回渲染后的页面。
# ---------- [应用名]/views.py ----------
def vote(request, question_id):
context = {
'question': question,
'error_message': "you didn't select a choice."
}
return render(request, '[应用名]/detail.html', context)
# detail.html 由我们自己编写
# 在 detail.html 中的预处理脚本中,使用了 question 和 error_message 两个对象
# 因此在调用 render 时需要用一个 dict 对象给出其值(本函数中的 context 几十这个目的)
方法三:使用 HttpResponseRedirect
与 reverse
方法实现页面重定向。
# ---------- [应用名]/views.py ----------
from django.http import HttpResponseRedirect
from django.urls import reverse
def vote(request, question_id):
return HttpResponseRedirect(reverse('[应用名]:result', args=(question_id,)))
# reverse 的功能:
# 根据 '[应用名]:result' 这个名字找到对应的 URL 模式
# 将 args 中的值依次填入 URL 模式中的 “匹配段”
# HttpResponseRedirect 的功能:
# 将 reverse 计算得到的 URL 重新经过路由系统找到对应的视图函数,计算得到 HttpResponse 并返回
4.3.3 使用 request 中的 POST/GET 参数
使用 request.POST
或者 request.GET
可以得到一个 dict
类型的数据:
from .models import Question, Choice
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
selected_choice = question.choice_set.get(pk=request.POST['choice'])
4.4 设置或删除 Cookie
COOKIE
是 request
对象的一部分,可以通过 Django
提供的方法进行读与写(不是很会,贴个别人的博客)。
Django Cookie 实现登录
def login(request):
rep = redirect("/app01/home/") # salt 是加密的钥匙
rep.set_signed_cookie("is_login", "1", salt="ban", max_age=100)
return rep
def logout(request):
rep = redirect("/app01/login/")
rep.delete_cookie("is_login") # 删除一个 cookie
return rep
五、templates html 模板的设计
类似于 python
包 jinjia2
渲染器,Django
使用 {{ expr }}
向 HTML 中指明此处渲染时要填入表达式的值。使用 {% script %}
指明渲染时要执行的脚本(一般是 if
或者 for
循环。)
5.1 使用对象的值
<!-- [应用名]/templates/[应用名]/detail.html -->
<!-- context 中需要给出对象 question 的值 -->
<!DOCTYPE html>
<head>
<title>Question {{ question.id }}</title>
</head>
<body>
<p>{{ question.question_text }}</p>
</body>
</html>
5.2 使用循环与循环计数器
<!-- [应用名]/templates/[应用名]/index.html -->
<!-- html 的文件结构略 -->
<ul>
{% if question_list %} <!-- 判断 question_list 是否为空 -->
{% for question in question_list %}
<li><strong>{{ forloop.counter }}</strong>.{{ question.question_text }}</li>
<!-- forloop.counter 从 1 开始向上计数 -->
{% endfor %}
{% else %}
<p>No Question.</p>
{% endif %}
<\ul>
循环计数器在为个数不确定的对象一一设置 <label>
时比较方便,以网站中的单选题为例,每个选项都是一个 <input type='radio'>
,但是每个选项的文字内容必须写在这些 <input>
的旁边。由于选项的个数并不确定因此很难手动为这些 <input>
赋予不同的 id
。但却可以使用 id='choice{{ forloop.counter }}'
或类似的方法,循环地给每个 <input>
一个不同的 id
。
5.3 使用外键子对象
如果 class Choice(models.Model)
表格类中包含一个 Question
类的外键,那么在渲染时,Question
类型的对象可以使用 question.choice_set.all
访问 通过外键链接到这个 Question
的所有 Choice
。
在定义外键时,需要给出 related_name
(逆向函数名)的值。related_name
是逆向关系的函数名,例如上例中通过 Choice
(选项) 可以唯一确定一个 Question
(问题),但是一个 Question
可能可以确定多个 Choice
(换言之,Choice
是 Question
的子对象)。related_name
的作用是从一个 Question
对象,找到一个与它对应的 Choice
对象列表。如果不指定 related_name
,默认的 related_name
是 [表格类]_set
,例如 choice_set
。但是如果一个表格类有多个同类型的子对象,就必须分别指出两个 related_name
,例如:
# UserInfo 是一个描述用户信息的表格类
class BookInfo(models.Model):
book_name = models.CharField(max_length=32)
donator = models.ForeignKey(UserInfo, related_name="give", on_delete=models.CASCADE)
keeper = models.ForeignKey(UserInfo, related_name="keep", on_delete=models.CASCADE, blank=True)
begin_time = models.DateTimeField(blank=True) # 可为空,为空表示没人在看
这样对于一个给定的 UserInfo
对象,才能反向确定 give
和 keep
两个 BookInfo
列表(即这个人捐了哪些书,现在借了哪些书)。
5.4 使用静态文件
HTML 文件中的静态文件需要使用一种类似文件路径的方式指出,但模板中的 URL 应该尽可能避免 “硬编码”的出现(换言之,写死了),从而有利于代码的复用与修改。在文件头中使用 {% load static %}
方法,告知脚本解释器,加载 static
命令。此后,使用 {% static '[应用名]/style.css' %}
即可被编译成 Django 搜索到的路径。
5.5 使用站内链接
和 5.4 同理,防止 URL 硬编码。我们使用 {% url '[应用名]:[URL名]' [匹配段填充值序列] %}
给出一个站内链接。例如:
<a href="{% url 'polls:vote' question.id %}">Click Here to Vote on Question {{ question.id }}</a>
5.6 实现表单提交
由于 Django 内置了 CSRF
子应用,防止跨站伪 POST 请求,在表单中一定要加入{% csrf_token %}
,服务器才能正确接收到这条 POST
请求。否则这条 POST
请求会被 Django
自带中间件(MiddleWare) django.middleware.csrf.CsrfViewMiddleware
拦下。也可以在 settings.py
中注释掉该中间件,但是这样会有一定的安全隐患。
<form action="{% url 'book_server:login_check' %}" method="post">
{% csrf_token %}
<input type="text" name="email" placeholder="邮箱@mails.jlu.edu.cn">
<br>
<input type="password" name="password" placeholder="密码">
<br>
<input type="submit" value="登录">
</form>
六、附录
6.1 使用注册类注册表格类的方法简介
# ---------- [应用名]/models.py ----------
import datetime
from django.utils import timezone
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self): # 算法生成的动态字段
return self.pub_date <= timezone.now() and self.pub_date >= timezone.now() - datetime.timedelta(days=30)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE) # 外键,CASCADE(级联删除)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
我们还是以 Choice
和 Question
之间的关系为例,介绍使用注册类注册表格类的方法。由于 Choice
类中含有一个 Question
类的 ForeignKey
,因此可以认为 Choice
是 Question
的子对象。所以,逻辑上来说,应该在创建 Question 类的同一个页面上,创建对应的 Choice 对象,注册方法如下。
# ---------- [应用名]/admin.py ----------
from django.contrib import admin
from .models import Question, Choice
# admin.site.register(Question) # 简单的注册方式
class ChoiceInline(admin.StackedInline):
# 可以用 TabularInline 替换 StackedInline,在屏幕上以表格形式呈现,占据空间更小
# 在创建 Question 对象的同时,可以创建 Choice 对象
model = Choice
extra = 0 # 默认给每个 Question 再提供 0 个额外的 Choice
class QuestionAdmin(admin.ModelAdmin):
# list_display 给出了缩略显示模式下显示哪些字段
list_display = ('question_text', 'pub_date', 'was_published_recently')
search_fields = ['question_text'] # 支持搜索的字段,底层通过数据库 LIKE 操作实现
inlines = [ChoiceInline] # 在创建 Question 的同时创建 Choice
admin.site.register(Question, QuestionAdmin)
6.2 SMTP 发送邮件
在发送邮件前,首先要保证您已经根据 2.3’ 节的内容正确地配置了 SMTP 功能,此后,发送邮件就非常地容易了。
def sendEmail(subject, message, fromEmail, toEmailList): # 发送电子邮件
from django.core.mail import send_mail
send_mail(subject, message, fromEmail, toEmailList, fail_silently=False)
# 发送电子邮件
sendEmail(
"邮件主题",
"邮件内容",
"from@qq.com", # 发送邮件的邮箱地址
["to@qq.com"] # 接受邮件的邮箱地址
)
注:这里的 from@qq.com
必须和 settings.py
中指定的账户一致才能发送。