Django3.2.x 学习实践

前言

最近因为学校要用,需要写一个 web 服务器,但是我不想学 java 系列的 spring 框架,又有之前使用 python socket 框架的老底,于是打算学习一下 Python 下的重量级框架 Django。

Django 官方教程: 快速上手(中文)
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.17Python 3.6.9(这两个版本我决定不了)。最终决定在本地和云端均部署 Django 3.2.x

查询本地已经部署的 python 版本,命令行输入:python3 --versionWindows 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,切换到虚拟环境需要使用命令如下:
cshsource tutorial-env/bin/activate.csh
fishsource tutorial-env/bin/activate.fish

1.3 在虚拟环境中部署 Django

为什么一定要在虚拟环境中部署 Django

  1. 避免 Django 使用的包与原有环境内的包发生版本冲突;
  2. 如果在虚拟环境与真实环境中同时安装了 Django,会发生那一预测的麻烦。

如何判断自己在虚拟环境中:

  1. 观察命令行中命令提示符的变化:
    在虚拟环境外:目录$
    在虚拟环境中:(虚拟环境名) 目录$

  2. python 命令行中输入以下语句:
    >>> import sys; print(sys.path)
    观察到 site-package 目录在虚拟环境目录内,则说明已经切换到了虚拟环境。

确保自己在虚拟环境中后,使用 pip 安装 Django。(注:如果您的电脑上之前有 python2python3 两个版本的 python,在虚拟环境中直接输入 python,执行的就是虚拟环境中的 python 解释器,不需要再区分 pythonpython3 两种命令。同理,不需要区分 pippip3

python -m pip install django==3.2.11 # 版本号根据具体需要填写

检查是否成功安装了 Djangopip 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 发送邮件。据说 DjangoSMTP 功能本质上是对 smtplib 包的封装。

2.4 创建超级管理员

Django 提供了非常方便的后台管理界面,使用 Django 提供的接口,我们可以使用后台管理界面可视化地修改数据库中存储的内容。后台管理界面需要使用账号密码登录,因此需要先使用命令行创建一个超级管理员

第零步:确定自己在部署了 Djangopython 虚拟环境中;
第一步cdmanage.py 所在的目录;
第二步:执行 python manage.py migrate 进行初次数据库迁移;
第三步:执行 python manage.py createsuperuser,接下来按照提示输入即可。

2.5 Django 服务器的启动与停止

第零步:确定自己在部署了 Djangopython 虚拟环境中;

如果你是在本地对 Django 项目进行测试:
第一步cdmanage.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

第一步cdmanage.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 架构在思想上有异曲同工之妙,MVTModel/View/Template 结构,以下内容是对 MVT 架构的一个感性的认识(因为我写的不好,所以第一次看很可能不知所云…)。

应用的 Model 是指 “数据模型”,换言之,数据库中的表格。models.py 中的每个类(models.Model 的子类)对应着数据库中的一个表。

应用的 View 是指 “视图(回调)函数/类”,是从 HttpRequestHttpResponse 的映射,是 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 会自动为你创造一个叫做 idIntegerField 字段作为主键。其值从 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)

第一步:在虚拟环境下 cdmanage.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.pyurlpatterns 指出的 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 模板文件时,将可以用到现在通过 nameapp_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 几十这个目的) 

方法三:使用 HttpResponseRedirectreverse 方法实现页面重定向。

# ---------- [应用名]/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

COOKIErequest 对象的一部分,可以通过 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 模板的设计

类似于 pythonjinjia2 渲染器,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(换言之,ChoiceQuestion 的子对象)。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 对象,才能反向确定 givekeep 两个 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


我们还是以 ChoiceQuestion 之间的关系为例,介绍使用注册类注册表格类的方法。由于 Choice 类中含有一个 Question 类的 ForeignKey,因此可以认为 ChoiceQuestion 的子对象。所以,逻辑上来说,应该在创建 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 中指定的账户一致才能发送。

未完待续 …

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值