Django用户信息模块


用户信息模块

​ 项目babys的用户信息模块分为用户注册登录和个人中心页,用户注册登录均在同一个页面实现,如果用户不存在,则执行注册操作,反之则执行登录操作;个人中心页显示用户的基本信息和订单信息,而且订单信息需要设置分页显示。

内置User实现注册登录

​ 由于Django已内置了用户管理功能,即Auth认证系统,而且具有灵活的扩展性,可以满足多方面的开发需求。创建项目时,Django已默认使用内置Auth认证系统,在settings.py的INSTALLED_APPS、MIDDLEWARE和AUTH_PASSWORD_VALIDATORS中都能看到相关的配置信息。

​ Django的Auth认证系统已内置模型User,它是对应数据表auth_user,该模型一共定义了11个字段,各个字段的含义说明如表8-1所示。

​ 用户登录注册页面分为3个功能区域:商品搜索功能、网站导航、登录注册表单,如图8-1所示。登录与注册表单共用一个网页表单,如果用户账号已存在,则对用户账号密码验证并登录,如果用户不存在,则对当前的账号密码进行注册处理。

​ 项目babys的用户注册登录功能在项目应用shopper已定义了路由login,该路由对应的视图函数为loginView,因此我们在项目应用shopper的views.py编写视图函数loginView的业务逻辑,代码如下:

​ 从视图函数loginView的代码结构分析得知,它首先定义了变量title和classContent,这是用于设置共用模板base.html的模板变量;然后对用户的请求方式进行判断,如果用户是发送POST请求,那么视图函数将会执行注册登录操作,详细的执行过程说明如下:

(1)当用户在注册登录页面输入账号密码,并单击“注册/登录”按钮之后,浏览器向网站发送POST请求,并将用户输入的账号密码作为请求参数发送到网站,网站把POST请求交给视图loginView进行处理。

(2)视图函数loginView从POST请求中获取请求参数username和password,将请求参数username作为Django内置模型User的查询条件,判断用户名是否存在于模型User。

(3)如果用户输入的用户名username已存在于模型User中,说明用户在网站已有账号信息,视图函数则执行用户登录操作。用户登录使用Django内置函数authenticate验证用户输入的账号密码与模型User保存的账号密码是否一致,如果验证一致,则使用Django内置函数login使用用户登录,并使用重定向函数redirect访问路由shopper(即登录成功后自动跳转到个人中心页)。重定向函数redirect的参数必须为路由地址,而内置函数reverse是根据路由命名解析生成相应的路由地址。

(4)如果用户输入的用户名username模型User中不存在,视图函数对模型User进行数据新增操作,将请求参数username和password作为模型User的字段username和password,并设置字段is_staff和is_active。数据新增不能使用create()方法,因为字段password需要使用pbkdf2_sha256方式来存储和管理用户的密码,所以Django为此定义了create_user()方法,最后调用save()方法完成整个用户的新增过程,并将用户数据保存到数据表auth_user。

(5)最后视图函数loginView将模板文件login.html作为响应内容,只要在浏览器输入路由login的路由地址(即向路由login发送GET请求)或者执行用户注册操作(即上述的步骤(4)),视图函数都会使用模板文件login.html生成响应内容。如果是执行用户注册操作,用户注册登录页面还可以看到“注册成功”的提示,如图8-2所示。

​ 由于视图函数使用模板文件login.html生成响应内容,下一步我们需要对模板文件login.html编写相应的模板语法。在PyCharm中打开模板文件login.html编写以下代码:

模板文件login.html按照功能划分可分为3个部分,在代码中依次标记了①②③,每个部分的功能说明如下:

(1)标注①是调用模板文件base.html并重写接口content,然后使用HTML语言编写网页表单,一个完整的表单主要由4部分组成:提交地址、请求方式、元素控件和提交按钮,分别说明如下:

​ 提交地址(form标签的action属性)用于设置用户提交的表单数据应由哪个路由接收和处理。当用户向服务器提交数据时,若属性action为空,则提交的数据应由当前的路由来接收和处理,否则网页会跳转到属性action所指向的路由地址。

​ 请求方式用于设置表单的提交方式,通常是GET请求或POST请求,由form标签的属性method决定。元素控件是供用户输入数据信息的输入框,由HTML的<input>控件实现,控件属性type用于设置输入框的类型,常用的输入框类型有文本框、下拉框和复选框等。提交按钮供用户提交数据到服务器,该按钮也是由HTML的<input><button>控件实现的。

由于表单采用POST方式发送HTTP请求,而Django为我们默认开启CSRF防护。CSRF防护只适用于POST请求,并不防护GET请求,因为GET请求是以只读形式访问网站资源的,一般情况下并不破坏和篡改网站数据,因此我们还需要在表单中加入{%csrf_token %}设置CSRF防护。

(2)标注②重写base.html定义的接口footer。接口footer是生成首页的底部信息,效果如图8-3所示。

(3)标注③重写base.html定义的接口script,JavaScript脚本代码是验证用户输入的用户名是否为手机号码,验证方式使用正则表达式,比如`if(!/^1\d{10}$/.test($("#username").val())),其中$("#username").val()定位id=username的input控件,然后获取该控件的属性value的值,最后使用if(!/^1\d{10}$/.test()对属性value的值进行正则判断,验证该值是否为长度等于11的手机号码。

CSRF防护

CSRF(Cross-Site Request Forgery,跨站请求伪造)也称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,这是一种对网站的恶意利用,其目的是窃取网站的用户信息来制造恶意请求。Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrfmiddlewaretoken隐藏控件,这个隐藏控件的值会与网站后台保存的csrfmiddlewaretoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:

(1)在用户访问网站时,Django在网页表单中生成一个隐藏控件csrfmiddlewaretoken,控件属性value的值是由Django随机生成的。

(2)当用户提交表单时,Django校验表单的csrfmiddlewaretoken是否与自己保存的csrfmiddlewaretoken一致,用来判断当前请求是否合法。

(3)如果用户被CSRF攻击并从其他地方发送攻击请求,由于其他地方不可能知道隐藏控件csrfmiddlewaretoken的值,因此导致网站后台校验csrfmiddlewaretoken失败,攻击就被成功防御。

在Django中使用CSRF防护功能,首先在配置文件settings.py中设置CSRF防护功能。它是由settings.py的CSRF中间件CsrfViewMiddleware实现的,在创建项目时已默认开启,如图8-4所示。

​ 如果表单中设置了CSRF防护功能,我们可以打开浏览器的开发者工具,查看表单设有隐藏控件csrfmiddlewaretoken,隐藏控件是由模板语法{% csrf_token %}生成的。用户每次提交表单或刷新网页时,隐藏控件csrfmiddlewaretoken的属性value都会随之变化,如图8-5所示:

​ 如果想要取消表单的CSRF防护,那么可以在模板文件上删除{%csrf_token %},并且在对应的视图函数中添加装饰器@csrf_exempt,代码如下:

​ 如果只在模板文件上删除{% csrf_token %},并没有在对应的视图函数中设置过滤器@csrf_exempt,那么用户提交表单时,程序会因CSRF验证失败而抛出403异常的页面,如图8-6所示。

​ 如果想取消整个网站的CSRF防护,那么可以在settings.py的MIDDLEWARE注释的CSRF中间件CsrfViewMiddleware。但在整个网站没有CSRF防护的情况下,又想对某些请求设置CSRF防护,那么可以在模板文件上添加模板语法{% csrf_token%},然后在对应的视图函数中添加装饰器@csrf_protect实现,代码如下:

​ 如果网页表单是使用前端的Ajax向Django提交表单数据的,由于Django设置了CSRF防护功能,Ajax发送POST请求必须设置请求参数csrfmiddlewaretoken,否则Django会将当前请求视为恶意请求。Ajax发送POST请求的功能代码如下:

使用Form实现注册登录

​ 在模板文件中,通过HTML语言编写表单是一种较为简单的实现方式,如果表单元素较多或一个网页里使用多个表单,就会在无形之中增加模板的代码量,对日后的维护和更新造成极大的不便。为了简化表单的实现过程和提高表单的灵活性,Django提供了完善的表单功能。Django的表单功能由Form类实现,主要分为两种:

​ django.forms.Form和django.forms.ModelForm。前者是一个基础的表单功能,后者是在前者的基础上结合模型所生成的数据表单,不管哪一种表单类型,我们都能选择任意一种类型实现表单开发。

​ 以项目babys的用户注册登录为例,我们将HTML编写的网页表单改由Django的django.forms.Form实现。首先在项目应用shopper的文件夹里新建form.py文件,目录结构如图8-7所示,该文件用于定义表单类Form,实现用户注册登录的网页表单。

​ 在PyCharm中打开form.py文件,然后定义表单类LoginForm,它继承django.forms.Form,详细的定义过程如下:

表单类LoginForm定义了表单字段username和password以及定义数据清洗函数clean_username(),表单字段的定义方式与模型字段的定义方式相同,首先定义表单字段的数据类型,然后再根据开发需求设置表单字段的属性;最后表单类LoginForm还定义了数据清洗函数clean_username(),该函数只适用于表单字段username的数据处理,详细说明如下:

(1)表单字段username和password的数据类型均设为CharField,这是文本框类型,它转换成HTML的<input type="text">控件。

(2)表单字段的参数max_length是设置文本框能输入的文本长度,它对应<input type="text">控件的属性maxlength;而参数label是为表单字段添加说明,它用于在HTML中独立生成<label>标签。

(3)表单字段的参数widget是一个forms.widgets对象,其作用是设置表单字段的CSS样式或其他属性,参数的对象类型必须与表单字段类型相符合。例如表单字段为CharField,参数widget的类型应为forms.widgets.TextInput,两者的含义与作用是一致的,都是文本输入框,若表单字段改为ChoiceField,而参数widget的类型不变,前者是下拉选择框,后者是文本输入框,则在网页上会优先显示为文本输入框。

(4)自定义表单字段username的数据清洗函数clean_username()只适用于表单字段username的数据清洗,函数名的格式必须为clean_表单字段名称(),而且函数必须有return返回值。如果在函数中设置主动抛出异常ValidationError,那么该函数可视为带有数据验证的数据清洗函数。完成表单类LoginForm的定义之后,下一步在视图函数loginView中使用表单类LoginForm实现用户注册登录功能。我们在项目应用shopper的views.py重新定义视图函数loginView,其代码如下:

视图函数loginView根据请求方式设置了两种处理方法,GET请求和POST请求的处理过程说明如下:

(1)当访问127.0.0.1:8000时,Django接收一个不带请求参数的GET请求,视图函数loginView将表单类LoginForm实例化,然后在模板文件login.html中使用表单类LoginForm实例化对象infos生成用户注册登录表单。

(2)当用户在注册登录表单填写账号密码并单击“注册/登录”按钮之后,浏览器向网站发送POST请求,视图函数loginView实例化表单类LoginForm,并将请求参数作为表单类LoginForm的参数data,生成实例化对象infos。

(3)由实例化对象infos调用is_valid()方法进行表单验证,如果表单验证成功,我们就可以使用infos['username']或infos.cleaned_data['username']来获取某个HTML控件的数据。由于表单字段username设有自定义的数据清洗函数,因此使用v.is_valid()验证表单数据时,Django自动执行数据清洗函数clean_username()。

(4)从表单对象infos中获取用户输入的账号密码之后,下一步是根据账号密码与模型User的数据进行查询匹配,从而完成用户注册登录功能,整个用户注册登录的业务逻辑与8.1节的业务逻辑相同,此处不再重复讲述。

(5)如果表单对象infos调用is_valid()方法验证表单失败,程序就使用errors.as_json()方法获取验证失败的错误信息,然后在控制台输入错误信息。

​ 用户注册登录表单的元素控件交由表单类LoginForm实现,因此模板文件login.html无须使用HTML语言编写网页表单的元素控件,我们在PyCharm中打开模板文件login.html,将元素控件<input>改由模板变量infos生成,详细代码如下:

​ 在上述代码中,我们分别将用户账号的控件改为{{infos.username }}和用户密码的控件改为{{infos.password }}。模板变量infos是视图函数loginView对表单类LoginForm实例化生成的对象;infos.username和infos.password对应表单字段username和password,它们能自动转换生成控件。除此之外,我们还可以在模板文件中使用内置方法生成其他的HTML元素控件,详细的使用方法如下所示:

分析Form的机制和原理

我们发现表单类的定义过程与模型有相似之处,只不过两者所继承的类有所不同,也就是说,Django内置的表单功能也是使用自定义元类实现定义过程的。在PyCharm里打开表单类Form的源码文件,其定义过程如图8-8所示。

​ 表单类Form继承BaseForm和元类DeclarativeFieldsMetaclass,而该元类又继承另一个元类,因此表单类Form的继承关系如图8-9所示。

​ 从图8-9得知,元类没有定义太多的属性和方法,大部分的属性和方法都是由父类BaseForm定义的,模板文件index.html使用的方法也是来自父类BaseForm。我们分析父类BaseForm的定义过程,列举开发中常用的属性和方法。

data:默认值为None,以字典形式表示,字典的键为表单字段,代表将数据绑定到对应的表单字段。

auto_id:默认值为id_%s,以字符串格式化表示,若设置HTML元素控件的id属性,比如表单字段job,则元素控件id属性为id_job,%s代表表单字段的名称。

prefix:默认值为None,以字符串表示,设置表单的控件属性name和id的属性值,如果一个网页里使用了多个相同的表单,那么设置该属性可以区分每个表单。

initial:默认值为None,以字典形式表示,在表单的实例化过程中设置初始化值。label_suffix:若参数值为None,则默认为冒号,以表单字段job为例,其HTML控件含有label标签(<label for="id_job">职位:</label>),其中label标签里的冒号由参数label_suffix设置。field_order:默认值为None,则以表单字段定义的先后顺序进行排列,若要自定义排序,则将每个表单字段按先后顺序放置在列表里,并把列表作为该参数的值。

field_order:默认值为None,则以表单字段定义的先后顺序进行排列,若要自定义排序,则将每个表单字段按先后顺序放置在列表里,并把列表作为该参数的值。

use_required_attribute:默认值为None(或为True),为表单字段所对应的HTML控件设置required属性,该控件为必填项,数据不能为空,若设为False,则HTML控件为可填项。

errors():验证表单的数据是否存在异常,若存在异常,则获取异常信息,异常信息可设为字典或JSON格式。

is_valid():验证表单数据是否存在异常,若存在,则返回False,否则返回True。

as_table():将表单字段以HTML的<table>标签生成网页表单。

as_ul():将表单字段以HTML的<ul>标签生成网页表单。

as_p():将表单字段以HTML的<p>标签生成网页表单。

has_changed():对比用于提交的表单数据与表单初始化数据是否发生变化。

​ 了解表单类Form的定义过程后,接下来讲述表单的字段类型。表单字段与模型字段有相似之处,不同类型的表单字段对应不同的HTML控件,在PyCharm中打开表单字段的源码文件,如图8-10所示。

​ 从源码文件fields.py中可找到26个不同类型的表单字段,每个字段的说明如下。

CharField:文本框,参数max_length和min_length分别设置文本长度。

IntegerField:数值框,参数max_value设置最大值,min_value设置最小值。

FloatField:数值框,继承IntegerField,验证数据是否为浮点数。

DecimalField:数值框,继承IntegerField,验证数值是否设有小数点,参数max_digits设置最大位数,参数decimal_places设置小数点最大位数。

DateField:文本框,继承BaseTemporalField,具有验证日期格式的功能,参数input_formats设置日期格式。

TimeField:文本框,继承BaseTemporalField,验证数据是否为datetime.time或特定时间格式的字符串。

DateTimeField:文本框,继承BaseTemporalField,验证数据是否为datetime.datetime、datetime.date或特定日期时间格式的字符串。

DurationField:文本框,验证数据是否为一个有效的时间段。

RegexField:文本框,继承CharField,验证数据是否与某个正则表达式匹配,参数regex设置正则表达式。

EmailField:文本框,继承CharField,验证数据是否为合法的邮箱地址。

FileField:文件上传框,参数max_length设置上传文件名的最大长度,参数allow_empty_file设置是否允许文件内容为空。

ImageField:文件上传控件,继承FileField,验证文件是否为Pillow库可识别的图像格式。

FilePathField:文件选择控件,在特定的目录选择文件,参数path是必需参数,参数值为目录的绝对路径;参数recursive、match、allow_files和allow_folders为可选参数。

URLField:文本框,继承CharField,验证数据是否为有效的路由地址。

BooleanField:复选框,设有选项True和False,如果字段带有required=True,复选框就默认为True。

NullBooleanField:复选框,继承BooleanField,设有3个选项,分别为None、True和False。

ChoiceField:下拉框,参数choices以元组形式表示,用于设置下拉框的选项列表。TypedChoiceField:下拉框,继承ChoiceField,参数coerce代表强制转换数据类型,参数empty_value表示空值,默认为空字符串。MultipleChoiceField:下拉框,继承ChoiceField,验证数据是否在下拉框的选项列表。

TypedMultipleChoiceField:下拉框,继承MultipleChoiceField,验证数据是否在下拉框的选项列表,并且可强制转换数据类型,参数coerce代表强制转换数据类型,参数empty_value表示空值,默认为空字符串。

ComboField:文本框,为表单字段设置验证功能,比如字段类型为CharField,为该字段添加EmailField,使字段具有邮箱验证功能。

MultiValueField:文本框,将多个表单字段合并成一个新的字段。

SplitDateTimeField:文本框,继承MultiValueField,验证数据是否为datetime.datetime或特定日期时间格式的字符串。

GenericIPAddressField:文本框,继承CharField,验证数据是否为有效的IP地址。SlugField:文本框,继承CharField,验证数据是否只包括字母、数字、下画线及连字符。

UUIDField:文本框,继承CharField,验证数据是否为UUID格式。

​ 表单字段除了转换HTML控件之外,还具有一定的数据格式规范,比如EmailField字段,它设有邮箱地址验证功能。不同类型的表单字段设有一些特殊参数,但每个表单字段都继承父类Field,因此它们具有以下的共同参数。

required:输入的数据是否为空,默认值为True。

widget:设置HTML控件的样式。

label:用于生成label标签的网页内容。

initial:设置表单字段的初始值。

help_text:设置帮助提示信息。

error_messages:设置错误信息,以字典形式表示,比如{'required': '不能为空','invalid': '格式错误'}。

show_hidden_initial:参数值为True/False,是否在当前控件后面再加一个隐藏的且具有默认值的控件(可用于检验两次输入的值是否一致)。

validators:自定义数据验证规则。以列表格式表示,列表元素为函数名。

localize:参数值为True/False,设置本地化,不同时区自动显示当地时间。

disabled:参数值为True/False,HTML控件是否可以编辑。

label_suffix:设置label的后缀内容。

​ 在上述参数中,参数widget是一个forms.widgets对象(forms.widgets对象也称为小部件),而且forms.widgets的类型必须与表单的字段类型相互对应,不同的表单字段对应不同的forms.widgets类型,对应规则分为4大类:文本框类型、下拉框(复选框)类型、文件上传类型和复合框类型,如表8-2所示。

​ 当我们为表单字段的参数widget设置对象类型时,可以根据实际情况进行选择。假设表单字段为SlugField类型,该字段继承CharField,因此可以选择文本框类型的任意一个对象类型作为参数widget的值,如Textarea或URLInput等。

使用ModelForm实现注册登录

​ 我们已使用表单类Form和模型User实现了用户注册功能,表单类Form和模型实现数据交互最主要的问题是表单字段和模型字段的匹配,比如表单字段为CharField,而模型字段为IntegerField,那么两者在进行数据交互的时候,程序可能会提示异常信息,但是将表单类Form改为ModelForm,我们就无须考虑字段匹配的问题。

​ 表单类ModelForm根据模型的模型字段定义相应的表单字段,不仅能解决模型字段与表单字段的数据类型匹配问题,还能减少代码量。以项目babys的用户注册登录为例,将表单类LoginForm改为模型表单类LoginModelForm,在项目应用shopper的form.py文件定义模型表单类LoginModelForm,定义过程如下:

​ 模型表单类LoginModelForm继承django.forms.ModelForm,然后再重新定义Meta类,分别设置了属性model、fields、labels、error_messages和widgets,并且自定义表单字段username的数据清洗函数clean_username(),详细说明如下:

(1)属性model是ModelForm的特有属性,它是将模型表单类与某个模型进行绑定。

(2)属性fields是选取模型某些字段生成表单字段,上述代码的('username', 'password')代表只将模型字段username和password转换成表单字段,设置模型的部分模型字段转换表单字段可以使用元组或列表表示,元组或列表的每个元素代表一个模型字段;如果需要设置所有的模型字段,属性fields的值可以等于字符串'all'

(3)属性labels是为每个表单字段设置HTML元素控件的label标签,它以字典格式表示,字典的每个键值对的键为表单字段名称,值为label标签的值。

(4)属性error_messages是设置表单字段的错误信息,它以字典格式表示,上述代码的'all'代表所有表单字段的错误信息;如果只需设置某个表单字段的错误信息,可将'all'改为具体的表单字段,比如error_messages={'username':{'required': 'XX','invalid':'XX'}}。

(5)属性widgets设置表单字段对应的HTML元素控件的属性,它以字典格式表示,字典的每个键值对的键为表单字段名称,值为forms.widgets对象。

(6)自定义表单字段username的数据清洗函数clean_username()与8.3节的表单类LoginForm定义的数据清洗函数clean_username()相同。

​ 完成模型表单类LoginModelForm的定义之后,下一步在视图函数loginView中使用LoginModelForm实现用户注册登录功能。我们在项目应用shopper的views.py重新定义视图函数loginView,其代码如下:

​ 视图函数loginView与8.3节定义的视图函数loginView在业务逻辑上有很大的相似之处,由于模型字段username具有唯一性,导致模型表单类LoginModelForm的字段username也具有唯一性,因此视图函数loginView无法调用is_valid()方法验证表单数据。当用户输入的账号信息已记录在模型User中,使用is_valid()方法验证表单数据的时候,程序会提示异常信息,如图8-11所示。

​ 我们在8.3节已对模板文件login.html的网页表单的元素控件进行了修改,它同样适用模型表单类,所以在PyCharm中可直接运行项目babys验证用户注册登录是否符合开发要求。在浏览器中访问路由login的路由地址(http://127.0.0.1:8000/shopper/login.html),输入新的账号密码并单击“注册/登录”按钮,网站将会重新访问注册登录页面,并提示“注册成功”,如图8-12所示。最后再次输入正确的用户密码并单击“注册/登录”按钮,网站将会访问个人中心页面。

分析ModelForm的机制和原理

​ 我们已经学习了如何使用模型表单类ModelForm实现表单数据与模型数据之间的交互开发。模型表单类ModelForm继承父类BaseModelForm,其元类为ModelFormMetaclass。在PyCharm中打开类ModelForm的定义过程,如图8-13所示。

​ 在源码文件里分析并梳理模型表单类ModelForm的继承过程,将结果以流程图的形式表示,如图8-14所示。

​ 从模型表单类ModelForm的继承关系得知,元类没有定义太多的属性和方法,大部分的属性和方法都是由父类BaseModelForm和BaseForm定义的。表单类BaseForm的属性方法在8.4节已讲述过了,因此这里只列举BaseModelForm的核心属性和方法。

instance:将模型查询的数据传入模型表单,作为模型表单的初始化数据。

clean():重写父类BaseForm的clean()方法,并将属性_validate_unique设为True。_

_validate_unique():验证表单数据是否存在异常。_save_m2m():将带有多对多关系的模型表单保存到数据库里。save():将模型表单的数据保存到数据库里。如果参数commit为True,就直接保存在数据库;否则生成数据库实例对象。

​ 模型表单类ModelForm与Form对比,前者只增加了数据保存方法,但是ModelForm与模型之间没有直接的数据交互。模型表单与模型之间的数据交互是由函数modelform_factory实现的,该函数将自定义的模型表单与模型进行绑定,从而实现两者之间的数据交互。函数modelform_factory与ModelForm定义在同一个源码文件中,它定义了9个属性,每个属性的作用说明如下。

model:必需属性,用于绑定Model对象。

fields:可选属性,设置模型内哪些字段转换成表单字段,默认值为None,代表所有的模型字段,也可以将属性值设为'all',同样表示所有的模型字段。若只需部分模型字段,则将模型字段写入一个列表或元组里,再把该列表或元组作为属性值。

exclude:可选属性,与fields相反,禁止模型字段转换成表单字段。属性值以列表或元组表示,若设置了该属性,则属性fields无须设置。

labels:可选属性,设置表单字段的参数label,属性值以字典表示,字典的键为模型字段。

widgets:可选属性,设置表单字段的参数widget,属性值以字典表示,字典的键为模型字段。

localized_fields:可选参数,将模型字段设为本地化的表单字段,常用于日期类型的模型字段。

field_classes:可选属性,将模型字段重新定义,默认情况下,模型字段与表单字段遵从Django内置的转换规则。

help_texts:可选属性,设置表单字段的参数help_text。

error_messages:可选属性,设置表单字段的参数error_messages。

​ 除此之外,我们知道模型字段转换表单字段遵从Django内置的规则进行转换,两者的转换规则如表8-3所示。

个人中心页

当我们在注册登录页面成功登录的时候,网站会自动跳转到个人中心页面。个人中心页面分为4个功能区域:商品搜索功能、网站导航、用户基本信息和订单信息,如图8-15所示,用户基本信息和订单信息的设计说明如下:

(1)用户基本信息:在网页的左侧位置,展示了用户的头像、名称和登录时间,按钮功能分别有购物车页面链接和退出登录。

(2)订单信息:以数据列表展示,每行数据包含了订单编号、订单价格、购买时间和订单状态,并设置分页功能,每一页显示7条订单信息。

​ 综合上述,个人中心页需要实现用户基本信息和订单信息,由于项目应用shopper的urls.py已定义路由shopper,所以在项目应用shopper的views.py定义视图函数shopperView,代码如下:

​ 按照业务逻辑划分,视图函数shopperView可以分为5个部分,每部分实现的功能说明如下:(1)使用Django的内置装饰器login_required设置用户登录访问权限,如果当前用户在尚未登录的状态下访问个人中心页,程序就会自动跳转到指定的路由地址,只有用户完成登录后才能正常访问个人中心页。login_required的参数有function、redirect_field_name和login_url,参数说明如下:

参数function:默认值为None,这是定义装饰器的执行函数。

参数redirect_field_name:默认值是next。当登录成功之后,程序会自动跳回之前浏览的网页。

参数login_url:设置用户登录的路由地址。默认值是settings.py的配置属性LOGIN_URL,而配置属性LOGIN_URL需要开发者自行在settings.py中配置。

(2)变量title、classContent分别对应模板base.html的模板变量title和classContent,而变量p来自请求参数p,它代表订单信息的某一页页数。

(3)变量t来自请求参数t,它代表用户购买商品的支付时间;变量payTime来自会话session,这也是代表用户购买商品的支付时间;通过对比变量t和payTime,如果两者不为空而且相等,则说明该订单已支付成功,从会话session获取订单内容payInfo再写入模型OrderInfos,完成用户订单的购买记录;最后在会话session中删除该订单的支付时间payTime和订单内容payInfo。

(4)变量orderInfos是在模型OrderInfos中查询当前用户的订单信息,由于视图函数使用了内置装饰器login_required,当前请求必须完成用户登录才能执行视图函数的业务逻辑,请求对象request的user.id必能获取当前请求的用户主键id,所以变量orderInfos必定能查询当前用户的订单信息。

(5)最后使用Django内置分页功能对变量orderInfos进行分页处理,生成分页对象pages,每一页设置了7条订单信息。

​ 视图函数shopperView使用模板文件shopper.html作为响应内容,下一步在PyCharm中打开模板文件shopper.html,针对视图函数shopperView定义的变量编写相应的模板语法,详细代码如下:

我们将模板文件shopper.html按照功能划分可分为3个部分,在代码中依次标记了①②③,每个部分的功能说明如下:

(1)标注①是调用模板文件base.html并重写接口content,然后使用模板变量user生成当前用户的基本信息。在视图函数shopperView中,我们并没有定义变量user,但在模板变量中却能使用user生成当前用户的基本信息,因为模板变量user是在Django解析模板文件的过程中自动生成的,它与配置文件settings.py的TEMPLATES设置有关。我们查看settings.py的TEMPLATES配置信息,代码如下:

​ 由于配置属性TEMPLATES定义了处理器集合context_processors,所以在解析模板文件之前,Django依次运行处理器集合的程序。当运行到处理器auth时,程序会自动生成变量user和perms(变量perms可以获取用户权限,它由内置模型Permission实例化,对应数据表auth_permission),并且将这些变量传入模板变量集合TemplateContext中,因此模板中可以直接使用模板变量user和perms。

​ 除了当前用户的基本信息之外,我们还设置了“我的购物车”和“退出登录”按钮。“我的购物车”按钮是访问购物车页面,即路由shopcart,它对应的视图函数为shopcartView,该视图函数的业务逻辑将在第9章详细讲述。“退出登录”按钮是访问用户退出登录链接,即路由logout,它对应的视图函数为logoutView,因此我们还需要为其定义视图函数logoutView,在项目应用shopper的views.py定义视图函数logoutView,代码如下:

(2)标注②遍历变量pages的object_list方法生成商品列表,由于变量pages已设置每页的订单显示数量,因此遍历完成后只会显示7条订单信息,每次遍历对象p代表模型OrderInfos的某条数据,每一条订单信息展示了订单编号、订单费用(订单费用使用内置过滤器floatformat保留小数点后两位数据)、创建时间和订单状态。

(3)标注③使用变量pages的方法实现分页功能列表,比如判断当前页数是否存在上一页,则可以使用变量pages的has_previous方法判断;获取上一页的页数则使用变量pages的previous_page_number实现。

​ 变量pages调用paginator.page_range方法获取数据分页后的总页数,然后在遍历过程中,每次遍历对象page与pages.number进行对比,pages.number首先使用过滤器add进行自增1或自减1,再与遍历对象page对比,如果判断成功,则生成分页按钮。比如当前页数是第二页,那么分页功能则会生成第一页和第三页的按钮,如图8-16所示。