Django
1.1 面向切面编程
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。在Web项目开发中,特别是进入移动互联网时代以来,基于AOP思想,对项目进行前后端分离的基本架构,已经成为了一种必须要做的事情。
1.2 Django的前后端分离
Django的普通项目是基于MVT模式(Model View Template)开发的,而Django的前后端分离项目则是基于MVVM模式(Model View ViewModel)开发的,解耦得更彻底,彻底到前后端分离了,甚至可以说分离成了两个项目。
Django前后端分离项目原理:后端遵循restful规范开发API,与前端进行数据交互,实现多端应用。
1.2.1 如何写API
(1)如果是对同一个表进行数据操作(增、删、改、查),应该使用一条API,然后根据method的不同,进行不同的操作。GET/POST/PUT/DELETE/PATCH
(2)面向资源编程,通过API提交的参数最好是名词,比如name,尽量少用动词。
(3)体现版本,在API中加入像v1、v2这样的版本代号:
http://www.abc.com/v1/name
http://www.abc.com/v2/name
(4)体现API,让使用者一眼能看出这是API而不是URL。 http://www.abc.com/api/v1/name
(5)使用HTTPS,这一项原本是为了安全考虑,但是随着国内外互联网环境对安全性越来越重视,谷歌浏览器对所有不是HTTPS请求的链接全都会提示用户此链接为不安全链接,腾讯等平台也对小程序等产品强制要求使用HTTPS协议。
(6)响应式设置状态码,例如,200和201代表操作成功,403代表权限不够,404代表没有指定资源,500代表运行时发现代码逻辑错误等。
(7)API的参数中加入筛选条件参数,也可以理解为获取资源优先选择GET的方式。https://www.abc.com/api/v2/name?page=1&size=10
(8)返回值的规范,不同的method操作成功后,后端应该响应的返回值如下:
不同的提交方式代表对数据进行不同的操作:
http://www.abc.com/v1/name
● GET:所有列表。
● POST:新增的数据。
http://www.abc.com/v2/name
● GET:单条数据。
● PUT:更新,返回更新的数据。
● PATCH:局部更新,返回更新的数据。
● DELETE:删除,返回空文档。
(9)返回错误信息,应该加入错误代号code,让用户能直接看出是哪种类型的错误。
(10)返回的详细信息,应该以字典的形式放在data中。
RESTful规范是业内约定俗成的规范,并不是技术上定义的公式。
2.1 REST framework实现API应用
2.1.1 Django REST framework序列化
序列化(Serialization)是指将对象的状态信息转换为可以存储或传输形式的过程。在客户端与服务端传输的数据形式主要分为两种:XML和JSON。在Django中的序列化就是指将对象状态的信息转换为JSON数据,以达到将数据信息传送给前端的目的。
序列化是开发API不可缺少的一个环节,Django本身也有一套做序列化的方案,这个方案可以说已经做得很好了,但是若跟Django REST framework相比,还是不够极致,速度不够快。
2.2.1 Postman的使用
Postman是一款非常流行的API调试工具,其使用简单、方便,而且功能强大。通过Postman可以便捷地向API发送GET、POST、PUT和DELETE请求,几乎是伪资深开发人员调试API的首选。可以从Postman官网直接下载安装。
postman主界面如下:
2.3 Django 小项目
2.3.1 安装Django
pip install django
2.3.2 建立一个项目
django-admin startproject Django
manage.py:是Django用于管理本项目的命令行工具,之后进行站点运行、数据库自动生成、静态文件收集等都要通过该文件完成。
settings.py:Django的项目配置文件。默认时,在其中定义了本项目引用的Django组件、Django项目名等。在之后的开发中,还需在其中配置数据库参数、导入的其他Python包等信息。
urls.py:维护项目的URL路由映射,即定义客户端访问的URL由哪一个Python模块解释并提供反馈。在默认情况下,其中只定义了“/admin”即管理员站点的解释器。
wsgi.py:定义WSGI的接口信息,用于与其他Web服务器集成,一般本文件在生成后无须改动。
2.3.3 建立一个应用
python manage.py startapp book
admin.py:管理站点模型的声明文件,默认为空。
apps.py:应用信息定义文件。在其中生成了类AppConfig,该类用于定义应用名等Meta数据。
migrations包:用于在之后定义引用迁移功能。
models.py:添加模型层数据类的文件。
tests.py:测试代码文件。
views.py:定义URL响应函数。
以上所有文件在应用刚建立时没有实际内容,需要开发者进一步编写代码完成其功能。
2.3.4 基本视图
(1)首先在Django/book/views.py中建立一个路由响应函数:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def welcome(request):
return HttpResponse("<h1>哈哈哈,这是一个响应。</h1>")
(2)接下来,要通过URL映射将用户的HTTP访问与该函数绑定起来。
在Django/book/目录中新建一个urls.py文件,管理应用book中的所有URL映射,其文件内容为:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r"", views.welcome, name="first-url"),
]
django.conf.urls中的url()函数 ,Django中的所有路由映射由该函数生成。
变量urlpatterns,该变量是一个列表,保存所有由url()函数生成的路由映射。本代码中只设置了一个映射,把所有路由映射到view.py的welcome函数中,并把该映射命名为first-url。
(3)在项目URL文件Django/urls.py的urlpatterns中增加一项,声明对应用book中urls.py文件的引用,完整代码如下:
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.conf.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
url(r'book/', include('book.urls')),
]
在urlpatterns列表中增加一个路径book/,将其转接到book.urls包,即Django/book/urls.py文件。这样,通过include()函数就将两个urlpatterns连接了起来。
注意:url()与path()都可以用来在urlpatterns中定义路由映射;它们的区别在于url()的第1个参数用正则表达式表达URL路由,而path()的第1个参数是普通字符串。
2.3.5 内置web服务器
查看网站效果首先需要通过manage.py启动Web服务器。
cd Django
python manage.py runserver 0.0.0.0:8000
其中runserver是启动网站的关键字,后面的参数指定网站绑定的IP地址与端口号。用0.0.0.0表示绑定本机的所有IP。在命令运行的过程中将一直占用控制台,可以输入Ctrl+C组合键退出运行。
注意:用这种方式启动的Web服务器是Django内置的Web服务器,由于性能原因,一般只可用于开发人员测试。正式运行的网站应该使用本章后面介绍的WSGI方式启动。
2.3.6 模型类
现在开始Model层的处理,即设计和开发信息发布的数据访问层。
1. 配置项目INSTALLED_APPS
打开Django/settings.py文件,找到其中的INSTALLED_APPS列表,在其中添加应用book的Config类,代码如下:
INSTALLED_APPS = [
'book.apps.BookConfig', # 新增
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
2. 模型定义
打开Django/book/models.py,在其中新建一个模型类Moment用来定义信息发布表,代码如下:
from django.db import models
# Create your models here.
class Moment(models.Model):
content = models.CharField(max_length=200)
user_name = models.CharField(max_length=20)
kind = models.CharField(max_length=20)
定义了3个字符串类型的字段:content保存消息的内容、user_name保存发布人的名字、kind保存消息的类型。
3. 生成数据移植文件
Django的术语“生成数据移植文件”(makemigrations)是指将models.py中定义的数据表转换成数据库生成脚本的过程。该过程通过命令行工具manage.py完成,具体的命令及输出如下:
python manage.py makemigrations book
通过输出可以看到完成了模型Moment的建立。输出中的0001_initial.py是数据库生成的中间文件,通过它也可以知道当前的数据库版本;该文件及以后的所有migration文件都存在于目录Django/book/migrations/中。
在makemigrations的过程中,Django会对比models.py中的模型与已有数据库之间的差异,如果没有差异则不会做任何工作,比如再次执行makemigrations操作时将产生如下输出:
如果对models.py做任何修改,则在下一次makemigrations的时候会将修改的内容同步到数据库中。比如,将Moment类的content字段长度从200修改为300后,再次执行makemigrations的结果如下:
在其过程中产生了新的中间文件0002_auto_20191127_1609.py
产生的0001_initial.py
# Generated by Django 2.2.7 on 2019-11-27 08:06
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Moment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.CharField(max_length=200)),
('user_name', models.CharField(max_length=20)),
('kind', models.CharField(max_length=20)),
],
),
]
产生的0002_auto_20191127_1609.py
# Generated by Django 2.2.7 on 2019-11-27 08:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('book', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='moment',
name='content',
field=models.CharField(max_length=300),
),
]
4.移植到数据库
在模型的修改过程中可以随时调用makemigrations生成中间移植文件。而当需要使移植文件生效、修改真实的数据库schema时,则需要通过manage.py的migrate命令使修改同步到数据库中。比如:
python manage.py migrate
在命令执行的过程中将检查Django/book/migrations目录中的所有文件,逐步使历次生成的移植文件生效。
技巧:可以在每次修改models.py文件内容后运行makemigrations命令,检查改动是否符合数据库的语法规则;在调试运行之前,运行一次migrate命令使改动生效。
2.3.7 表单视图
设计和开发信息录入页面。该页面的基本功能为:提供输入界面,让用户输入名字、文本消息内容、选择消息类型,用户提交后网页自动设置该信息的时间并保存到数据库中。下面逐步进行开发。
1. 定义表单类
建立表单类文件Django/book/forms.py,在其中定义表单类MomentForm。代码如下:
from django.forms import ModelForm
from book.models import Moment
class MomentForm(ModelForm):
class Meta:
model = Moment
fields = '__all__' # 引入所有字段
引入django.forms.ModelForm类,该类是所用Django表单类的基类。
引入在本应用models.py中定义的Moment类,以便在后面的表单类中关联Moment类。
定义表单类MomentForm,在其中定义子类Meta。在Meta中声明与本表单关联的模型类及其字段。
Fields 字段可以设为’__all__'
,也可以用元组形式声明所要导入的属性,比如:fields=('content', 'user_name', 'kind')
。
技巧:Meta中的fields = '__all__'
将所有模型类中的字段引入表单类中。
2.修改模型类
如果要使用户能够以单选的方式设置消息类型,则需要在models.py文件中定义单选枚举值,并与模型类Moment相关联。把Django/book/models.py修改为如下:
from __future__ import unicode_literals
from django.db import models
# 新增元组用于设置消息类型枚举项
KIND_CHOICES = (
('Python技术', 'Python技术'),
('数据库技术', '数据库技术'),
('经济学', '经济学'),
('文体资讯', '文体资讯'),
('个人心情', '个人心情'),
('其他', '其他'),
)
class Moment(models.Model):
content = models.CharField(max_length=300)
user_name = models.CharField(max_length=20, default='匿名')
# 修改kind定义,加入choices参数
kind = models.CharField(max_length=20, choices=KIND_CHOICES, default=KIND_CHOICES[0])
主要修改内容是:
· 为kind字段增加了消息类型枚举项;
· 为user_name和kind字段用default属性配置了默认值。
注意:因为本次编辑导致模型层发生变化,所以需要用manage.py命令行工具运行makemigrations和migrate命令来更新数据库的定义。
3. 开发模板文件
建立目录Django/book/templates,在其中新建模板文件moments_input.html,文件的内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>消息录入页面</title>
</head>
<body>
<form action="?" method="post">
<fieldset>
<legend>请输入并提交!</legend>
{{ form.as_p }}
<input type="submit" value="submit" />
</fieldset>
</form>
</body>
</html>
模板文件以HTML格式为基本结构,其中的模板内容用大括号标识。本例用{{ form.as_p }}定义表单类MomentForm的输入字段。模板文件的详细语法将在后面介绍。
4. 开发视图
开发视图函数使得表单类和页面模板衔接起来。打开Django/book/views.py文件,在其中加入如下函数:
import os
from book.forms import MomentForm
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import render
def moments_input(request):
if request.method == 'POST':
form = MomentForm(request.POST)
if form.is_valid():
moment = form.save()
moment.save()
return HttpResponseRedirect(reverse('first-url'))
else:
form = MomentForm()
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
return render(request, os.path.join(PROJECT_ROOT, 'book/template',
'moments_input.html'), {'form': form})
# Create your views here.
def welcome(request):
return HttpResponse("<h1>哈哈哈,这是一个响应。</h1>")
在代码中新增了视图函数moments_input(),该函数定义了两种访问方式的不同处理方法。
· 如果是用户的Post表单提交,则保存moment对象,并重定向到欢迎页面。其中reverse()函数根据映射名称找到正确的URL地址,本例中使用的是在Djang/book/urls.py中配置过的名称’first-url’。
· 如果是普通的访问,则返回moments_input.html模板的渲染结果作为HTTP Response。注意,render()的第3个参数将form作为参数传给了模板,这样在模板文件中才能访问该MomentForm的实例。
在Django/book/urls.py文件中增加该视图函数的路由映射,内容如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r"", views.welcome, name="first-url"),
url(r'moments_input', views.moments_input)
]
注意:为url()传递的第2个参数是被调用函数的名称,不能在其后加小括号“()”。
在代码中定义了该视图的调用函数地址是moments_input,算上Django应用本身的路径,则该视图的全路径为http://localhost:8888/book/moments_input。Django表单视图示例,如图6.2所示。
提交表单时出现错误:
是因为在post提交的过程中为了防止CSRF攻击,需要设置一个Token来验证。
解决方法是:
在moments_input.html表单中加入
{% csrf_token %}
并在Django/book/views.py中给moments_input函数加上注解:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def moments_input(request):
2.3.8 使用管理界面
Django管理界面是一个通过简单的配置就可以实现的数据模型后台的Web控制台。管理界面通常是给系统管理员使用的,以完成元数据的输入、删除、查询等工作。
首先将管理界面需要管理的模型类添加到Django/book/admin.py文件中,具体如下:
from django.contrib import admin
from .models import Moment
admin.site.register(Moment)
本文件中只要通过admin.site.register()函数逐个声明要管理的模型类即可。
在第1次访问管理界面之前,需要通过manage.py工具的createsuperuser命令建立管理员用户。在命令运行的过程中按照提示输入管理员的用户名、邮箱地址、密码:
之后即可访问管理员页面http://localhost:8888/admin/。输入用户名及密码后,Django管理界面如图所示。在管理员界面提供新增Moment模型类的Add链接,单击Moments链接后,还可以看到修改和删除选项。界面中的Groups和Users涉及Django的用户管理系统,将在讲解管理界面时详述。
注意:管理界面中的模型类都以英文复数形式呈现,例如Moment模型被表达为Moments。
2.4 Django模型层
Django模型层是Django框架自定义的一套独特的ORM技术。通过以上实践,读者已经掌握了Django模型层的基本概念和开发流程。本节讲解模型层的技术细节和高级话题。
2.4.1基本操作
使用Django模型开发的首要任务就是定义模型类及其属性。每个模型类都可以被映射为数据库中的一个数据表,而类属性被映射为数据字段,除此之外,数据库表的主键、外键、约束等也通过类属性完成定义。
1.模型类定义
模型定义的基本结构如下:
解析如下。
· 所有Django模型继承自django.db.models.Model类。
· 通过其中的类属性定义模型字段,模型字段必须是某种models.XXField类型。
· 通过模型类中的Meta子类定义模型元数据,比如数据库表名、数据默认排序方式等。
Meta类的属性名由Django预定义,常用的Meta类属性汇总如下。
· abstract:True or False,标识本类是否为抽象基类。
· app_label:定义本类所属的应用,比如app_label = ‘myapp’。
· db_table:映射的数据表名,比如db_table = ‘moments’。
技巧:如果Meta中不提供db_table字段,则Django会为模型自动生成数据表名,生成的格式为“应用名_模型名”,比如应用book的模型Comment的默认数据表名为book_comment。
· db_tablespace:映射的表空间名称。表空间的概念只在某些数据库如Oracle中存在,不存在表空间概念的数据库将忽略本字段。
· default_related_name:定义本模型的反向关系引用名称,默认与模型名一致。本名称的含义将在后续的内容中说明。
· get_latest_by:定义按哪个字段值排列以获得模型的开始或结束记录,本属性值通常指向一个日期或整型的模型字段。
· managed:True或False,定义Django的manage.py命令行工具是否管理本模型。本属性默认为True,如果将其设为False,则运行python manage.py migrate时将不会在数据库中生成本模型的数据表,所以需要手工维护数据库的定义。
· order_with_respect_to:定义本模型可以按照某外键引用的关系排序。
· ordering:本模型记录的默认排序字段,可以设置多个字段,默认以升序排列,如果以降序排列则需要在字段名前加“负号”。比如,如下定义按user_name升序和pub_date降序排列。
· default_permissions:模型操作权限,默认为default_permisstions= (‘add’, ‘change’, ‘delete’)。
· proxy:True或False,本模型及所有继承自本模型的子模型是否为代理模型。
· required_db_features:定义底层数据库所必需具备的特性。比如required_db_features=[‘gis_enabled’]只将本数据模型生成在满足gis_enabled特性的数据库中。
· required_db_vendor:定义底层数据库的类型,比如SQLite、PostgreSQL、MySQL、Oracle。如果定义了本属性,则模型只能在其声明的数据库中被维护。
· unique_together:用来设置的不重复的字段组合,必须唯一(可以将多个字段做联合唯一)。
[插图]
上述代码定义每个user_name在同一个pub_date中只能有一条数据表记录。因为unique_together本身是一个元组,所以可以设置多个这样的唯一约束。
· index_together:定义联合索引的字段,可以设置多个。
· verbose_name:指明一个易于理解和表述的单数形式的对象名称。如果这个值没有被设置,则Django将会使用该model类名的分词形式作为它的对象表述名,即CamelCase将会被转换为camel case。
· verbose_name_plural:指明一个易于理解和表述的复数形式的对象名称。
2.普通字段类型
普通字段是指模型类中除外键关系外的数据字段属性。数据字段为Django使用模型时提供如下信息。
· 在数据库中用什么类型定义模型字段,比如INTEGER、VARCHAR等。
· 用什么样的HTML标签显示模型字段,比如等。
· 需要什么样的HTML表单数据验证。
所有数据字段的属性必须继承自抽象类django.db.models.Field,开发者可以定义自己继承自该类的字段类型,也可以使用Django预定义的一系列Field子类。常用的Django预定义字段类型描述如下。
· AutoField:一个自动递增的整型字段,添加记录时它会自动增长。AutoField字段通常只用于充当数据表的主键;如果在模型中没有指定主键字段,则Django会自动添加一个AutoField字段。
· BigIntegerField:64位整型字段。
· BinaryField:二进制数据字段,只能通过bytes对其进行赋值。
· BooleanField:布尔字段,相对应的HTML标签是。
· CharField:字符串字段,用于较短的字符串,相对应的HTML标签是单行输入框。
· TextField:大容量文本字段,相对应的HTML标签是多行编辑框。
· CommaSeparatedIntegerField:用于存放逗号分隔的整数值,相对于普通的CharField,它有特殊的表单数据验证要求。
· DateField:日期字段,相对应的HTML标签是、一个JavaScript日历和一个“Today”快捷按键。有下列额外的可选参数:auto_now,当对象被保存时,将该字段的值设置为当前时间;auto_now_add,当对象首次被创建时,将该字段的值设置为当前时间。
· DateTimeField:类似于DateField,但同时支持时间输入。
· DurationField:存储时间周期,用Python的timedelta类型构建。
· EmailField:一个带有检查Email合法性的CharField。
· FileField:一个文件上传字段。在定义本字段时必须传入参数upload_to,用于保存上载文件的服务器文件系统的路径。这个路径必须包含strftime formatting,该格式将被上载文件的date/time替换。
· FilePathField:按目录限制规则选择文件,定义本字段时必须传入参数path,用以限定目录。
· FloatField:浮点型字段。定义本字段时必须传入参数max_digits和decimal_places,用于定义总位数(不包括小数点和符号)和小数位数。
· ImageField:类似于 FileField,同时验证上传对象是否是一个合法图片。它有两个可选参数,即height_field和width_field。如果提供这两个参数,则图片将按提供的高度和宽度规格保存。该字段要求安装Python Imaging库。
· IntegerField:用于保存一个整数。
· IPAddressField:一个字符串形式的IP地址,比如“129.23.250.2”。
· NullBooleanField:类似于BooleanField,但比其多一个None选项。
· PhoneNumberField:带有美国风格的电话号码校验的CharField(格式为×××-×××-××××)。
· PositiveIntegerField:只能输入非负数的IntegerField。
· SlugField:只包含字母、数字、下画线和连字符的输入字段,它通常用于URL。
· SmallIntegerField:类似于IntegerField,但只具有较小的输入范围,具体范围依赖于所使用的数据库。
· TimeField:时间字段,类似于DateTimeField,但只能表达和输入时间。
· URLField:用于保存URL。
· USStateField:美国州名的缩写字段,由两个字母组成。
· XMLField:XML字符字段,是具有XML合法性验证的TextField。
3.常用字段参数
每个字段类型都有一些特定的HTML标签和表单验证参数,比如height_field、path等。但同时有一些每个字段都可以设置的公共参数,比如通过primary_key参数可以设置一个模型的主键字段:
其他这样的参数如下。
· null:定义是否允许相对应的数据库字段为Null,默认设置为False。
· blank:定义字段是否可以为空。读者需要区分blank与null的区别。null是一个数据库中的非空约束;而blank用于字段的HTML表单验证,即判断用户是否可以不输入数据。
· choices:定义字段的可选值。本字段的值应该是一个包含二维元素的元组。元组的每个元素中的第1个值是实际存储的值,第2个值是HTML页面中进行选择时显示的值。
上述代码中定义了level字段用于让用户选择满意度,其中1、2、3、4是在数据库中实际存储的数据,而Very good、Good、Normal、Bad等是在HTML的列表控件中提供给用户的选项。
· default:设定默认值,例如default=“please input here”。
· help_text:HTML页面中输入控件的帮助字符串。
· primary_key:定义字段是否为主键,为True或False。
· unique:是否为字段定义数据库的唯一约束。
除了这些有名称的字段参数,Django中的所有Field数据类型还有一个无名参数,可以设置该字段在HTML页面中的人性化名称,比如:
本例中开发者为level字段定义了人性化名称“请为本条信息评级”,如果不设置本参数,则字段的名称本身将被显示在HTML页面中作为输入提示。
4.基本查询
定义如下Django model,用于演示Django的模型基本查询技术:
Django通过模型的objects对象实现模型数据查询,比如查询Comment模型的所有数据:
Django有两种过滤器用于筛选记录。
· filter(∗∗kwargs):返回符合筛选条件的数据集。
· exclude(∗∗kwargs):返回不符合筛选条件的数据集。
比如,如下语句用于查询所有pub_date的年字段是2018的Comment:
技巧:多个filter和exclude可以连接在一起查询,比如Comment.objects.filter(pub_date__year ==2018).exclude(pub_date__month=1).exclude(n_visits_exact=0)查询所有2018年非1月的n_visits不为0的记录。
需要注意代码中的pub_date__year,它不是模型中定义的一个字段,而是Django定义的一种独特的字段查询(field lookup)表达方式,本例中该查询的含义是“pub_date字段的year属性为2018”。field lookup的基本表现形式是:
即由“字段名称__谓词”来表达查询条件。还有很多其他field lookup方式,比如:
完整的Django谓词表如表6.1所示。
表6.1 Django谓词表
除了all()、filter()、exclude()等返回数据集的函数,Django还提供了get()用于查询单条记录,比如获取id为3的记录:
Django还提供了用于查询指定条数的数据集的下标操作,该特性使得Django模型能够支持标准SQL中的LIMIT和OFFSET谓词。比如:
5.数据保存与删除
与传统SQL相比,Django的一个较大优势是定义了一个统一的方法save(),用于完成模型的Insert和Update操作。在执行模型实例的save()函数时,Django会根据模型的主键判断记录是否存在,如果存在则执行Update操作,否则执行Insert操作。
Django模型提供了delete()方法用于删除记录,该方法既可用于数据集,也可用于单条记录,比如:
2.4.2 关系操作
利用数据表之间的关系进行数据建模和业务开发是关系数据库最主要的功能。Django模型层对3种关系模型(1∶1、1∶N、M∶N)都有强大的支持。
1.一对一关系
在SQL语言中,一对一关系通过在两个表之间定义相同的主键来完成。在Django模型层,可以在任意一个模型中定义OneToOneField字段,并定义相互之间的一对一关系。如下代码在模型Account和Contact之间定义了一对一关系:
对上述代码中的关系的相关定义解析如下。
· 两个模型的关系通过Contact模型中的account字段进行定义。
· OneToOneField()的第1个参数定义被关联的模型名。
· on_delete参数定义当被关联模型(Account)的记录被删除时本模型的记录如何处理,models.CASCADE用于定义此时本记录(Contact)也被删除。
· 每个模型的__unicode__()函数用于定义模型的显示字符串。
对上述一对一关系的模型开发代码演示如下:
2.一对多关系
在SQL语言中,1∶N关系通过在“附表”中设置到“主表”的外键引用来完成。在Django模型层,可以用models.ForeignKey类型的字段定义外键。如下代码在模型Account和Contact之间定义了一对多关系:
上述代码与一对一关系的唯一不同是用models.ForeignKey定义了Contact模型中的account字段。这样,每个Account对象就可以与多个Contact对象相关联了。对模型应用代码的演示如下:
在一对多关系中,每个主模型对象可以关联多个子对象,所以本例中从主模型Account对象中寻找附模型Contact的属性是contact_set,即通过一个集合返回关联结果。
技巧:xxxx_set是Django设定的通过主模型对象访问附模型对象集合的属性名。
3.多对多关系
在SQL语言中,M∶N关系通过建立一个中间关系表来完成,该中间表中定义了到两个主表的外键。所以在Django模型层中,开发者也可以选择用两个1∶N关系来定义M∶N关系。这种方式同样可以通过models.ForeignKey来实现,此处不再赘述。
同时,Django模型层定义了一种更直接的M∶N关系建模方式,即在两个模型中的任意一个中定义models.ManyToManyField类型的字段,多对多关系的Account与Contact模型定义的如下:
上述代码通过在Contact中定义引用Acccount的ManyToManyField,实现了两个模型的多对多关联,对此模型定义的操作演示如下:
2.4.3 面向对象ORM
Django模型层ORM的一个强大之处是对模型继承的支持,该技术将Python面向对象的编程方法与数据库面向关系表的数据结构有机地结合。Django支持三种风格的模型继承。
· 抽象类继承:父类继承自models.Model,但不会在底层数据库中生成相应的数据表。父类的属性列存储在其子类的数据表中。
· 多表继承:多表继承的每个模型类都在底层数据库中生成相应的数据表管理数据。
· 代理模型继承:父类用于在底层数据库中管理数据表;而子类不定义数据列,只定义查询数据集的排序方式等元数据。
1.抽象类继承
抽象类继承的作用是在多个表有若干相同的字段时,可以使开发者将这些字段统一定义在抽象基类中,免于重复定义这些字段。抽象基类的定义通过在模型的Meta中定义属性abstract =True来实现。抽象基类的举例如下:
本例中定义了一个抽象基类MessageBase,用于保存消息的4个公用字段:id、content、user_name、pub_date。子类Moment和Comment继承自MessageBase,并分别定义了自己的一个字段。本例中的3个类映射到数据库后会被定义为两个数据表。
· 数据表moment:有id、content、user_name、pub_date、headline 5个字段。
· 数据表comment:有id、content、user_name、pub_date、level 5个字段。
在子类模型的编程中,可以直接引用父类定义的字段,比如:
2.多表继承
在多表继承技术中,无论是父表还是子表都会用数据库中相对应的数据表维护模型数据,父类中的字段不会重复地在多个子类的相关数据表中进行定义。从这种意义上讲,多表继承才是真正面向对象的ORM技术。
多表继承的定义不需要特殊的关键字。在Django内部通过在父模型和子模型之间建立一对一关系来实现多表继承技术。如下代码定义了MessageBase及其子类的多表继承版本:
本例在数据库中会实际生成3个数据表。
· 数据表MessageBase:有id、content、user_name、pub_date 4个字段。
· 数据表Moment:有id、headline两个字段。
· 数据表Comment:有id、level两个字段。
在对模型的编程过程中,子类仍然可以直接引用父类定义的字段;同时子类可以通过父类对象引用访问父类实例,比如:
技巧:多表继承时,在子类实例中通过小写的父类名字可以引用父类的实例。
3.代理模型继承
在前两种继承模型中子类模型都有实际存储数据的作用;而在代理模型继承中子类只用于管理父类的数据,而不实际存储数据。代理模型继承通过在子类的Meta中定义proxy=True属性来实现,举例如下:
在本例中定义了父类模型Moment用于存储数据,而后定义了子类模型OrderedMoment用于管理根据pub_date倒序排列的Moment。使用代理模型继承的原因是子类中新的特性不会影响父类模型及其已有代码的行为。
2.5 Django视图层
2.6 Django表单
2.6.1 表单绑定状态
Django为继承自Form类的表单维护了一个绑定(bound)状态。
· 如果一个表单对象在实例化后被赋予过数据内容,则称该表单处于bound状态。只有处于bound状态的表单才具有表单数据验证(validate data)的功能。
· 如果未被赋予过数据内容,则表单处于unbound状态。只有处于unbound状态的表单才能被赋予数据,使该表单变为bound状态。
注意:已经处于bound状态的表单不能在Python代码中修改其数据,而只能由网页用户在页面中输入数据进行修改。
可以通过Form的is_bound属性检查表单状态:
2.6.2 表单数据验证
Django表单数据验证是指在服务器端用Python代码验证表单中数据的合法性。表单验证分为如下两类。
· 字段属性验证:验证表单中的字段是否符合特定的格式要求,比如CharField字段是否满足了max_length要求、非空字段是否已经赋值等。
· 自定义逻辑验证:验证开发者自定义的一些逻辑要求,比如moment的content长度必须比headline长、不能包含某些关键字等。
1.字段属性验证
字段属性验证要求通过model中字段的约束完成,在Form渲染的过程中Django会自动根据验证约束要求验证字段内容,如果字段不符合要求,则会自动显示错误信息并提示用户。比如如果设置表单对应model的content字段不能为空,则在用户不输入Content内容并提交表单后,该页面的渲染结果如图所示。
除此之外,开发者还可以用is_valid()函数在代码中获得表单验证是否通过的信息,用errors属性获得错误提示信息,比如:
由于在MomentForm的初始化中只设置了user_name的值,而没有设置不能为空的content的值,因此此时调用表单的is_valid()结果为False。表单is_valid()函数通常在视图函数的开发中起重要的作用。下面是典型的表单视图函数的设计结构:
2.自定义逻辑验证
如果开发者需要在Django进行表数据验证时判断自定义的复杂逻辑,则可以通过重载Form子类的clean()函数进行定义。修改MomentForm的定义如下:
在MomentForm中增加了对clean()函数的定义,该函数在开发者调用Form.is_valid()函数时自动被Django调用,开发者应该将针对表单的自定义验证逻辑写在clean()函数中。如果验证检测到逻辑错误,则通过抛出ValidationError()异常结束本次验证;如果验证数据正确,则返回从基类中得到的cleaned_data。自定义表单数据验证结果,如图6.5所示。
2.6.3 检查变更字段
当视图函数收到表单的Post提交时,经常需要通过验证用户是否修改了表单数据然后进行相应的处理。Django提供了表单函数has_changed()来判断用户是否修改过表单数据,使用方法如下:
读者需注意在初始化Form实例时要传入如下两个参数。
· reqeust.POST:Django从其中解析出用户的输入数据。
· initial:Form的初始值,在调用has_changed时,Django用initial中的字段值与初始值相比较,如果有变化则返回True。
Django不仅能够判断是否有字段修改过,还能用changed_data属性精确定位用户对哪些字段进行了修改。changed_data是包含字段名的列表,比如:
2.7 个性化管理员站点
如果Django默认的管理员站点不能满足应用的需求,那么开发者可以通过继承Django定义的管理员数据模型、模板、站点类来开发出个性化的管理员站点。
2.7.1 模型
通过定义继承自django.contrib.admin.ModelAdmin的子类,可以定制个性化的数据模型管理功能,并且需要在应用的admin.py文件中注册模型类时指定该子类,示例如下:
本例中定义了一个数据模型管理类MomentAdmin,并用admin.site.register()函数指定其作为模型Moment的管理类。MomentAdmin中用属性empty_value_display定义了模型管理界面中对空值的显示方式,这些个性化的管理功能还包括指定模型中的哪些字段可以被管理及每页显示的模型实例数量等。对常用的管理类属性描述如下。
(1)date_hierarchy:设置一个日期类型字段,使其出现在按日期导航找模型实例的界面中。
(2)empty_value_display:设置一个字符串,定义空值的显示方式。除了在表级别指定空值显示的方式,还可以按字段配置,比如:
本例中将表中默认的空值显示方式设置为“空值”,同时设置headline字段的空值显示为“未设标题”。
(3)fields和exclude:分别用于设置需要管理的字段和排除管理的字段。对如下模型Moment来说:
下面的两个管理类(MomentAdmin1和MomentAdmin2)有相同的作用,都是设定管理界面只管理content和kind字段:
(4)fieldsets:配置字段分组,可以美化管理界面的模型配置界面。如下代码将Moment的字段分为两个组:
这样,在编辑Moment对象时,管理界面会把字段分为两组显示,字段分组如图6.6所示。
(5)list_editable:设置字段列表,指定模型中的哪些字段可以编辑。如果设置了本属性,则没有在本属性中定义的字段将不能在管理界面中编辑。
(6)list_per_page:设置一个整数,指定每页显示的实例数量,默认为100。
(7)search_fields:设置字段列表,出现一个搜索页面使管理员能够按照这些字段进行实例搜索。
(8)ordering:设置字段列表,定义管理页面中模型实例的排序方式。
2.7.2 模板
如果读者需要在管理站点页面中增加独特的显示内容,则可以通过继承管理站点的默认模板文件进行开发。
假设Django被安装在虚环境venv中,则Django的默认管理站点的模板文件都被保存在如下路径中:
其中包含了所有默认管理站点所使用的模板文件,它们是:
开发者可以继承它们中的任意文件,以定制自己的管理站点页面。本节以重载登录页面login.html为例演示Django管理模板文件的定制技术。
1.定义子模板文件路径
在项目目录中按如下路径生成子模板文件login.html:
2.修改项目settings.py
打开文件djangosite/djangosite/settings.py, 配置其中的TEMPLATES的DIRS项目,将新生成的模板文件路径加入其中,比如:
3.开发子模板文件
打开Django默认的login.html,检查其中可继承的block,并在子模板文件中改写其内容以达到定制目的。读者打开默认的login.html文件后,可以发现其中有很多可改写项,比如content_title、bodyclass、nav-global等,如下子模板文件可改写content_tilte块的内容:
4.测试定制效果
至此已完成管理模板的定制开发工作,此时可以打开管理网站,模板定制的效果如图6.7所示。
此时在登录框中多了一条新加入的欢迎语句。
2.7.3 站点
如果需要修改一些管理站点中的通用属性,比如管理站点头、站点标题等,则可以通过定义自己的AdminSite类来实现。
1.定义AdminSite子类
自定义的AdminSite需要放在应用的admin.py文件中,打开djangosite/app/admin.py文件,添加如下代码:
2.修改项目urls.py
在djangosite/djangosite/urls.py文件的urlpatterns列表中配置用admin_site定义的管理站点URL映射,比如:
本例中用admin_site.urls替换了原来的url.admin.urls。
3.测试定制效果
再次打开网站的admin登录页面,新的AdminSite定制效果如图6.8所示,读者可以比较图6.8与图6.7中两次实验的效果。
本例中定制的site_header属性不仅在登录页面中被站点引用,在其他所有Django管理站点页面中都会被页面引用。所以相对于管理模板定制,对于此类需求使用AdminSite定制更有开发效率。
4.AdminSite中常用的定制属性
前面演示中只有site_header属性,下面详述AdminSite类中的常用定制属性。
· site_header:每个管理网页的页头都会出现的标题,即在HTML标签<h1>
中显示的内容。
· site_title:每个管理网页在浏览器窗口栏显示的页面名称,即HTML标签<title>
中显示的内容。
· site_url:管理站中View site按钮的目标地址,默认是网站根目录“\”。
· login_form:登录页面使用的AuthenticationForm子类名。
2.8 本章总结
对本章内容总结如下。
· 讲解Django的框架特点,讲解Django各组件与MVC框架的关系。
· 讲解Django站点开发流程:建立项目,建立应用,开发视图、模型、表单、模板,管理网站。
· 在模型层中对数据的基本增、删、改的操作方法。
· 模型层的关系操作方法:一对一关系、一对多关系、多对多关系。
· 面向对象的模型层编程:抽象类继承、多表继承、代理模型继承。
· 讲解基于正则表达式的URL映射设计方案。
· 讲解视图函数和模板文件的编程方法。
· 讲解如何运用Django表单状态、数据验证、变更字段查询等技术。
· 用模型管理类的方法个性化管理站点。
· 用管理模板定制的方法个性化管理站点。
· 用管理站点类定制的方法个性化管理站点。