Django学习笔记 (2)
1. 简单的博客系统
...
2. 用户管理
2.1 自定义模板和静态文件位置
2.1.1 自定义模板位置
2.1.2 自定义静态文件位置
2.1.3 通用静态文件和基础模板
2.1.4 重置管理后台模板
2.1.5 知识点
2.2 用户登录
2.2.1 创建应用
2.2.2 理解表单类
2.2.3 登陆的视图函数
2.2.4 登陆的前端界面
2.2.5 知识点
2.3 用内置方法实现登录和退出
2.3.1 内置的登录方法
2.3.2 判断用户是否登录
2.3.3 内置的退出方法
2.3.4 知识点
2.4 用户注册
2.4.1 简单注册
2.4.2 增加注册内容
2.4.3 管理新增的注册内容
2.4.4 知识点
2.5 关于密码的操作
2.5.1 修改密码
2.5.2 重置密码
2.5.3 利用第三方应用重置密码
2.5.4 知识点
2.6 维护个人信息
2.6.1 个人信息的数据模型类和表单类
2.6.2 展示个人信息
2.6.3 编辑个人信息
2.6.4 上传和裁剪头像图篇
2.6.5 优化头像上传功能
2.6.6 对个人信息进行管理
2.6.7 知识点
3. 文章管理和展示
...
附. 第一章操作步骤速查
安装Django
创建项目
创建应用
修改项目设置
编写数据模型类
迁移生成数据表和数据库
创建超级管理员
运行服务器
进行登录设置
测试“发布文章”功能
丰富列表页的信息
视图(业务逻辑系统) --> 模板文件(展示系统) --> 匹配URL(业务逻辑系统)
2. 用户管理
前言: 首先确定,自己能够根据记忆和理解重建一次第一章的项目和博客应用。然后再开始新学习。 从本章开始,将不再做单一用户的博客网站,而是要一步一步地做一个多用户的文章管理系统。 多用户,首要的是对众多用户进行管理,许可用户注册、登录、退出,并且超级管理员也能够对所有用户进行管理。任何一个大系统都是由许多小功能组成的。
2.1 自定义模板和静态文件位置
依然使用第一章中创建的项目,不过因为是大一点的系统了,所以会创建多个应用,这种情况下模板、静态文件等都要指定放在某个位置。笔者不赞成将模板和静态文件分散到各个应用内,虽然这样也可以,但笔者还有另外一个目的,就是介绍如何在 ./mysite/settings.py 中设置模板和静态文件的位置,以备后用。自定义模板和静态文件位置的基本知识如下如所示:
自定义模板和静态文件位置
模板位置
静态文件位置
jQuery
bootstrap
基础模板
管理后台模板
settings.py/TEMPLATES
创建模板目录
settings.py/STATIC_URL
创建静态文件目录
模板文件
header.html
footer.html
base.html
静态文件
2.1.1 自定义模板位置
编辑 ./mysite/settings.py 文件,修改 TEMPLATES 的值 (【注】有且仅有语句[1]和[2]两处改动)
TEMPLATES = [
{
'BACKEND':'django.template.backends.django.DjangoTemplates',
'DIRS':[os.path.join(BASE_DIR, 'templates'),],# [1]
'APP_DIRS':False,#[2]
'OPTIONS':{
'context_processors':[
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
语句[1]定义了模板文件的位置( ‘./mysite/mysite/’ + 'templates/),并且将该目录的名称 templates 也在这里定义了, BASE_DIR 表示本项目的根目录( ./mysite/mysite/ ),用 os.path.join() 函数将两者连接起来,即模板目录的位置是相对项目根目录的 templates 目录—— ./templates 。显然,读者可以按照自己的意愿来定义模板目录的名称和位置。 语句[2]将 APP_DIRS 设置为 False ,既不再允许 Django 按照默认方式(默认方式为???)寻找模板文件。
根据上述配置要求,在网站根目录建立 templates 目录:
[mysite]$ ls
blog db.sqlite3 manage.py mysite templates
重新运行服务
[mysite]$ python manage.py runserver
浏览器访问 http://localhost:8000/blog ,发现报错,因为我们仅仅建立了一个空文件夹,还没有创建模板。 将文件夹 ./blog/templates/ 中的所有内容移动到 ./mysite/mysite/templates 中,再刷新页面即可。 最后,可以将所有应用的模板都放在 ./templates 中。
2.1.2 自定义静态文件位置
在网站开发中,经常讲网页中的 CSS、JavaScript 文件及网页上的图片称为静态文件——与被渲染的模板文件比较。
打开 ./mysite/settings.py 文件,在最后几行代码中有
# Static files (CSS, JavaScript, Images)
# https://docs.djangoprojects.com/en/dev/howto/static-files/
STATIC_URL = '/static/'
先看上面两行注释的含义,第一行说明了 Django 中认定的静态文件是什么,第二行告诉我们想要知道怎么做可以去查看一个地址。最后一行代码的意思是如果通过 URL 访问静态文件,则可以通过(以刚刚建立的 blog 引用为例子)" http://localhost:8000/ + blog/ + static/ + newton.jpg " 的模式进行访问(类似 [hostname] + [path] + static + [filename] )。要实现这个目的,需要在 ./blog 目录中建立一个名为 static 的目录,然后将静态文件(如 newton.jpg )放在里面。
这种方式让每个应用都有自己的静态文件存放地。在实践中 ,通常也采用与模板文件一样的方式,把所有静态文件都放到指定的目录中。一次,继续编辑 ./mysite/settings.py 文件,在 STATCI_URL=’/static/’ 的下面增加如下代码:
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
通过上述配置,指定了静态文件存放目录的位置,即在项目根目录处建立子目录 /root/mysite/mysite/static/ ,并在该目录中放置一个图片文件,用来测试效果:
[mysite]$ ls
blog db.sqlite3 manage.py media mysite static templates
完成上述操作,需要重新启动 Django 服务,用浏览器再次打开 http://localhost:8000/static/newton.jpg ,是否访问到静态文件了?
之所以能够如此设置,是因为 Django 的 settings.py 配置变量中的 STATICFILES_FINDERS 的默认值 规定了静态文件的查找顺序和内容。
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder"
)
按照这个默认值,首先读取 STATICFILES_DIR 中规定的静态文件( django.contrib.staticfiles.finders.FileSystemFinder ),如果没有设置此值,就找不到什么,但不会对网站运行造成影响。然后通过 django.contrib.staticfiles.finders.AppDirectoriesFinder 在每个应用中查找有没有默认的静态文件目录 static 。如果读者在命名时将两处都做了设置,而且静态文件的名字还都一样,name Django 会在找到第一个后终止查询。
将所有的静态文件都归到同一个目录,为了区分不同应用 的静态文件,通常还会在里面再建立子目录,后续会看到此类操作。
2.1.3 通用静态文件和基础模板
在第一章的示例中,已经使用了 bootstrap.css 静态文件,但是这个静态文件是放在第三方服务器上的,我们通过网址引用。这样做虽然简单,却不能保证对方永久提供该链接,为此我们需要自己的通用静态文件。
所谓通用静态文件 ,就是会在很多应用中使用到的,比如 jquery 和 bootstrap 相关文件。先到各自官方网站上下载相应的文件,然后放到已经建立的 static 目录中。为了区分不同类型的文件,要分门别类的建立目录,目录结构如下:
[root@hserver1 static]# tree
├── static
│ ├── css
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css.map
│ │ ├── bootstrap.min.css
│ │ ├── bootstrap.min.css.map # 原版没有
│ │ ├── bootstrap-theme.css
│ │ ├── bootstrap-theme.css.map
│ │ ├── bootstrap-theme.min.css
│ │ └── bootstrap-theme.min.css.map # 原版没有
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2 # 原版没有
│ └── js
│ ├── bootstrap.js
│ ├── bootstrap.min.js
│ ├── jquery.js # jquery.min.js 也可以
│ └── npm.js
这样就将通用的部分静态文件存放好了。还有一些图片,比如网站的 LOGO ,可以放到 images 文件夹里面。
这些通用的静态文件常常在基础模板中使用,比如第一章示例中的 base.html 。针对本章所要创建的项目,我们要重新制作其基础模板。
{% block title %}{% endblock %} # [5]
# [6]
# [7]
{% block content %}# [8]
{% endblock %}
# [9]
# [10]
一般来讲,网站的界面可以大致分为上、中、下三个部分。上部就是头部(header),显示网站的 LOGO 和导航等;中部就是要显示的具体内容(body);下部就是底部(footer),显示网站的版权信息或者诸如联系方式、购买流程等不怎么变化的信息或者超链接。
按照上面的划分,头部和底部都可以做成基础模板。
在 ./templates 目录中创建 header.html 和 footer.html 文件,其中 header.html 文件代码如下:
{% load staticfiles %}
这就是所谓的头部代码(hreder.html)。
语句[1]是模板中声明引入静态文件的标签,只有使用它,语句[2]中才能使用 {% static ‘images/logo.png’ %} ,而不是用“硬编码”的方式写图片地址。语句[2]中的这个写法也是 Django 模板的一个语法, {% static %} 可以用来引入某个静态文件,此处表示引入了静态文件中的一张图片,注意使用格式和引用路径。语句[1]写在语句[2]前面即可,此处之所以写在顶部,只是习惯使然,并非一定。 【注】硬编码:如果在开发中,在某个需要做超链接的地方使用了类似 Django
的方式,则随着 URL 目录结构发生变化,这个超链接将报错。这种就称之为“硬编码”。
语句[3]中的 {% url ‘blog:blog_title’ %} 使用了在 ./mysite/urls.py 的 URLconf 中为应用设置的 namespace 和相关应用中的 name 值,拼接成该应用的入口链接 URL 。这样做同样是要避免“硬编码”。 【注】 难以理解 blog_title是视图文件里的函数,但正常顺序是 视图调用模板,那么模板怎么能反调用视图的内容呢???
这里的 footer.html 代码就相当简约了:
copy right www.itdiffer.com
头部代码编写完之后,把他们放到一个 base.html 的文件中进行整合,这个文件在前面已经创建了,此处结合刚才编写 header.html 和 footer.html 文件,将 base.html 文件重写:
{% load staticfiles %}
{% block title %}{% endblock %}
{% include "header.html" %}
{% block content %}{% endblock %}
{% include "footer.html" %}
{% block javascript %}{% endblock %}
关于应用 blog 本身的模板就不需要做任何修改了。重启 Django 服务器,访问 http://localhost:8000/blog/ ,即可看到章节1.3.1 中的页面“显示文章标题”。
2.1.4 重置管理后台模板
现在再次访问 http://localhost:8000/admin/ ,将发现报错“找不到模板 ./templates/admin/index.html ”,这是因为我们已经将默认的文件模板位置都指向了自定义的模板位置,但里面还没有后台管理 所对应的模板。
Template-loader postmortem
Django tried loading these templates, in this order:
Using engine django:
django.template.loaders.filesystem.Loader:
/home/qiwsir/DjangoPracticeProject/mysite/templates/admin/index.html (Source does not
exist)
下一步将后台管理的模板放入自定义模板位置 中,一共有两个—— admin 和 registration ,他们都在 Django 的安装目录的 ./contrib/admin/templates 里。复制过来即可。
2.1.5 知识点
.1 模板 前面已经在开发过程中用到模板了,这里对相关知识进行总结归纳。 Django 中的模板本质上也是一个独立的文本文件 (其实不仅是 Django ,其他的 Web 框架模板也是如此),文件中包含了静态内容 (比如 HTML , CSS )和动态标记的数据 。 DJango 中的视图函数(或者视图类)决定了哪个模板(例如"blog/title.html")以及向模板渲染什么数据(例如"{‘blogs’:blogs}")。
编写模板所用的语言以 HTML , CSS , JavaScript 为主。模板不是嵌入 Python 语言,但是为了增强其表现力,里面也会通过一些模板语言来实现类似循环那样的逻辑,例如:
{% for blog in blogs %}# [14]
{{blog.title}} # [15]
{% endfor %}
.2 contexts contexts 目前不方便直译,读者可以把它看做一个专有名词。
模板是一个动态文档,只有在显示动态信息时才能感受到他的存在。 Django 的视图函数在渲染模板(将有关数据传给模板)时使用的中介物就是 contexts 。contexts 是一个包含了键值对的字典 ,例如前面曾经使用过的
return render(request, "bolg/title.html", {"article":article, "publish":pub})
这条代码通过 render() 函数声明了所使用的模板—— “bolg/title.html” ,和contexts—— {“article”:article, “publish”:pub} 。在 contexts 的键值对中,“键”是传给模板的变量,这个变量可以引用任何在视图函数中 确定的对象。
.3 模板: for 和 if 所为模板语法 ,就是根据 Django 的规定,在模板中使用通过 contexts 传入模板的数据对象。一般情况下有两种语法,一类是单独的变量,也称为变量标签 ;另一类是块命令 ,也称为块便签。
例如,前面使用的 {{publish}} 就是变量标签, publish 是 contexts 中的一个“键”,通过它将 contexts 键值对中的值传入模板,即在该位置显示所渲染的数据内容。注意,这种变量标签必须用 {{variable}} 的形式——使用双花括号(大括号)。 【注】即“ contexts 中的键对应模板中的变量名,值对应视图文件中的变量名
除了变量标签外,还有“块便签”,下面的代码实现 for 循环,就是一种块标签:
{% for blog in blog %}
{{blog.t itle}}
{% endfor %}
块标签以 {% command %} 开头( {% for blog in blogs %} ),并以 {% endname %} 结尾( {% endfor %} ),中间是块的内容,上例中的 {{blog.title}} 依然是变量标签,只不过这个变量的内容来自 contexts 的键 blog 应用的对象的一个属性( title )。
对于 for 循环标签,在模板中应用时,常常会使用循环计数功能,为此 Django 模板提供了默认的 {{ forloop.counter }} 从1开始技术的方式,不需要视图函数中的 contexts 提供 forloop 键值对,只要是模板中的循环,都可以使用这样的方法实现对被循环内容的计数功能。另外,还有 {{forloop.counter() }} 是从零开始计数。
在模板中可以实现 for 循环外,还可以实现 if 循环,其样式如下:
{% if condition %}
something
{% endif %}
注意,在 Django 模板的 if 标签中也有类似 Python 的 if…else 结构:
{% if condition %}
something
{% else %}
others
{% endif %}
以上两种标签在模板中比较常用,而且他们也能够被嵌套使用。
.4 文档导读
.4.1 Managing static files
.4.2 The Django template language
.4.3 Built-in template tag and filters
2.2 用户登录
前面我们创建了一个超级管理员用户 admin (当然也可以命名为其他名称),并且可以通过管理员登录页面( http://localhost:8000/admin/ )登录到管理后台,而且在这个后台发布了博客。
注意到这个后台由用户管理功能,有且仅有管理员admin可以通过后台的 Users 选项向本网站中增加用户。
对于一个多用户的文章系统,非管理员用户最好是从前台登录,登录之后仅具有所规定的功能。
下面首先解决登陆问题,如下图所示:
用户登录
创建 account 应用
表单
登录视图函数
前端界面
表单的请求响应过程
表单类
内置用户登录
request
form.as_p
form.username
CSRF
字段和属性
绑定表单
未绑定表单
authenticate
login
HttpRequest 对象
request.method
方法
GET
POST
2.2.1 创建 account 应用
为了实现用户登录、退出、注册等功能,从而进行用户管理, 第一步 创建一个新的应用。
[mysite]$ python manage.py startapp account
[mysite]$ ls
account blog db.sqlite3 manage.py mysite static templates
创建新应用的表现就是在项目根目录中多了一个 account 目录,这与前述创建 blog 应用是一样的。
第二步 依然是在 ./mysite/settings.py 中对新应用进行配置:
INSTALLED_APPS=[
...,
'blog',
'account',
]
第三步 在 ./mysite/urls.py 中进行 URL 配置:
urlpatterns = [
...,
url(r'^account/', include('account.urls', namespace='account', app_name='account')),
]
第四步 在 ./account中创建 urls.py 文件,并设置好本应用中的路径:
from django.conf.urls import url
from . import views
from django.conf import settings
urlpatterns = [
url(r'^login/$', views.user_login, name="user_login"),
]
views.user_login 意味着必须要在 views.py 中创建一个名为 user_login 的函数来响应请求。
至此,基本配置以完成,下面开始设计用户登录过程。
2.2.2 理解表单类
在很多网站中,都有用户登录框(暂不讨论新兴的扫二维码登录),用户登录框用行话说就是表单 。这个表单可以用纯粹前端的HTML代码来编写,也可以用我们下面要介绍的表单类来编写。这两种方式在实践中都可以使用,这里我们首先应用表单类。 【注】在 web.py 框架中,一个简单的输入框表单的后端代码为 "form = web.input(action=None)"
在 ./account 目录中创建一个文件 forms.py ,这个文件是专门存放各种与表单有关的类的,登录表单相关的类的代码如下:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
为了理解这个表单类,下面在交互模式中进行操作,一步一步地实验:
[mysite]$ python manage.py shell
>>>from account.forms import LoginForm
>>>login_form = LoginForm()
>>>print(login_form)
Username:
Password:
用 from account.forms import LoginForm 引入 ./account/forms.py 中建立的表单类 LoginForm ,然后创建这个类的实例,只不过没有向类中传递任何参数,这个实例可以称为未绑定(数据)实例,然后打印此未绑定实例,结果就是 HTML 代码。这些 HTML 代码构成了前端表单,其对话框与我们在 ./account/forms.py 中所写的 LoginForm 类的属性是对应的。因此,我们可以认为不论是前端的表单,还是后端的表单类,都是对象。这个对象的属性就是表单中的对话框(input)。
下面分析细节部分。
LoginForm 中的 username=forms.CharField() 就是 ,而在 password=forms.CharField(widgte=forms.PasswordInput) 中,因为使用了 widget=forms.PasswordInput ,故其对应 。在表单类的属性值中,通常使用 widget 规定相对应的 HTML 元素的类型 ,如本例中规定的 input 的类型为 password 。
对象除了属性,还有一些方法。比如表单类的未绑定实例 login_form ,可以使用 Python 中已经熟知的自省方法查看其方法。
>>>dir(login_form)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__html__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', '_bound_fields_cache', '_clean_fields', '_clean_form', '_errors', '_html_output', '_post_clean', 'add_error', 'add_initial_prefix', 'add_prefix', 'as_p', 'as_table', 'as_ul', 'auto_id', 'base_fields', 'changed_data', 'clean', 'data', u'declared_fields', 'default_renderer', 'empty_permitted', 'error_class', 'errors', 'field_order', 'fields', 'files', 'full_clean', 'get_initial_for_field', 'has_changed', 'has_error', 'hidden_fields', 'initial', 'is_bound', 'is_multipart', 'is_valid', 'label_suffix', 'media', 'non_field_errors', 'order_fields', 'prefix', 'renderer', 'use_required_attribute', 'visible_fields']
选择几个后面要用到的关键点来学习。
>>>login_form.is_bound
False
该代码说明 login_form 是未绑定的实例。如果我们在实例化时,向类提供初始化的参数,情况就发生改变了:
>>>data_login = {"username":"django", "password":"123456"}
>>>lf = LoginForm(data_login)
>>>lf.is_bound
True
有两个方法,我们将在随后的视图函数中使用,需要在这里熟悉他们。
>>>lf.is_valid()
True
在实例化时,如果传递给表单类的数据是正确的,则 is_valid() 返回 True 。换个角度说,当前端提交到后端的数据是符合表单类属性要求的,则 is_valid() 返回 True ,这个方法用来检验数据是否合法:
>>>data_error = {"username":"django", "password":''}
>>>error_lf = LoginForm(data_error)
>>>error_lf.is_valid()
False
password 是不能为空的(参见前面打印出来的 HTML ,没有用专门的参数声明可以为空,默认都是不能为空的),所以上述数据相对的表单类不合法,检验结果是 False 。
>>>lf.cleaned_data
{'password':'123456', 'username':'django'}
cleaned_data 是实例的属性,他以字典形式返回实例的具体数据,即经过检验之后的属性及其值。如果传入的某项数据不合法,则在 cleaned_data 的结果中不予显示。
>>>error_lf.cleaned_data
{'username':'django'}
2.2.3 登录的视图函数
POST
是
否
GET
开始
前端请求
请求类型
数据合法性
成功登录
结束
错误信息
登录框
根据图中所表达的逻辑过程,编辑 ./account/views.py 文件,实现新的视图函数 user_login():
#coding:utf-8
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth import authenticate, login # [1]
from .forms import LoginForm
def user_login(request): # [2]
if request.method == "POST": # [3]
login_form = LoginForm(request.POST) # [4]
if login_form.is_valid(): # [5]
cd = login_form.cleaned_data # [6]
user = authenticate(username = cd['username'], password = cd['password']) # [7]
if user:
login(request, user) #[8]
return HttpResponse("Wellcome You. You have been authenticated successfully") # [9]
else:
return HttpResponse("Sorry. Your username or password is not right.")
else:
return HttpResponse("Invalid login")
if request.method == "GET":
login_form = LoginForm() # [10] 多余的实例化表单
return render(request, "account/login.html",{"form":login_form})
下面对此段代码的部分内容进行解释。
语句【1】
from django.contrib.auth import authenticate, login # [1]
是引入 django 默认的(或者说是)两个方法:用户认证和管理应用。此处两个方法来自 django.contrib.auth 。所有方法对应 ./mysite/settings.py 中 INSTALLED_APPS 的值。
语句【2】
def user_login(request): # [2]
命名了一个视图函数,在这个视图函数中要处理前端提交的数据,调用处理模块,并支持前端的显示请求。当浏览器通过URL向服务器发送请求时,django会创建一个 HttpRequest 对象, request 是 HttpRequest 的替代。在 request 对象(或者说 HttpRequest )对象的诸多方法中,常用的有 request.method、 request.GET 和 request.POST 。 【注意!!】视图函数的第一个参数必须是request。
语句【3】
if request.method = "POST": # [3]
中的 request.method 就是 request 对象的一个常用属性,它返回 HTTP 请求类型的字符串,比如前段向服务器发出的是 GET请求,那么 request.method 得到 “GET” 这个字符串。所以,通常像下面这样来使用它:
if request.method == "GET":
do_something()
if request_method == "POST":
do_something()
request.GET 也是常用属性,虽然上述代码中没有用到。
当浏览器向服务器发送 GET请求后, request.GET 得到一个类字典对象 。比如客户端发出 http://www.host.com/?name=zxj&course=28 的 GET 请求,也就是在 URL 中访问该地址,这个请求其实携带了两个参数及其值,即 name=zxj 和 course=28 ,两个参数之间用&符号连接。请求发出后, django 就通过request.GET 得到两个参数及其值,结果是类字典对象。所以,还可以用 request.GET.get(“name”) 得到参数 name 的值 zxj 。
注意区分 GET 和 get() ,request.GET 得到的是包含所提交请求所有参数和值的类字典对象 ; get() 是字典中的一个方法。
语句【4】
login_form = LoginForm(request.POST) # [4]
中用到了 request.POST 。一般的,GET请求用于数据查询(数据在请求url中),而POST用于数据写入或者更新等(数据在请求体中)。
在本应用中,前端浏览器向服务器端提交表单内容,采用了 POST 方式。用 POST 方式提交数据,浏览器的地址栏不会改变,这与 GET 方式不同。在 django 中,通过 request.POST 得到提交的表单数据 ,也是一个类字典对象 。
在 django 中,可以给表单类传入字典类型的数据(request.POST),从而建立一个绑定实例 (login_form = Login_Form(xxx));在此处中传入的是 request.POST 返回的类字典数据,效果与前面 request.GET 一样,依然是建立了一个绑定实例 。
语句【5】
if login_form.is_valid(): # [5]
验证所传入的数据是否合法,如果合法就执行后续的语句【6】
cd = login_form.clean_data # [6]
,这里的 cd 引入的是一个字典类型数据 ,其中以键值对的形式记录了用户名和密码。
语句【7】
user = authenticate(username = cd['username'],password = cd['password'])
中用到了语句【1】所引入的用户认证函数 authenticate() ,其作用是检验此用户是否为本网站项目的用户,以及其密码是否正确。如果都对上号了,就返回 User 的一个实例对象(类似实例化 user = User(…) );否则返回 None 。
可以在交互模式下尝试
>>> from django.contrib.auth import authenticate, login
>>> user = authenticate(username = 'admin', password = 'qq?0112138')
>>>user
>>> user = authenticate(username = 'admin', password = 'SHITSHIT')
>>>user
>>>
语句【8】
login(request, user) #[8]
中使用了语句【1】引入的 login() 函数,以语句【7】所得到的 User 实例对象作为参数,实现用户登录。用户登录之后, django 会自动调用默认的 session 应用,将用户 ID 保存在 session 中,完成用户登录操作。 通常情况下,两个方法函数 login() 和 authenticate() 配个使用。
语句【9】调用 HttpResponse 类 ,参数为字符串 ,结果为返回一个页面 给客户端。这和 render 有所不同。
语句【10】当客户端对服务器发出的是 GET 请求,则将 login_form = LoginForm() 传给指定模板( {“form”:login_form} ),从而在前端呈现相应的对话框。
编写完登录的视图函数,为了能够在前端显示登录对话框,还要编写相应的前端模板。
2.2.4 登陆的前端界面
接下来要编写 ./templates/account/login.html 模板(建立 account 子目录):
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
Login
Input your username and password
{% endblock%}
测试之前,首先启动 django 服务,然后输入 http://localhost:8000/account/login ,即可进入登录界面。用之前注册的账号密码登录。
接下来分析这个前段模板 login.html 。
语句【1】中
是创建一个前端显示的表单,其中的 action="." 用于显示当前表单的提交地址 ,这里表示的是当前地址,即调用此模板的视图函数所对应的URL。
其中的 {% csrf_token %} 在表单中是必不可少的,不写在这个位置也行,只要在
语句里面即可。
所谓 CSRF(Cross-Site Request Forgery),中文名称是跨站请求伪造,简言之就是攻击者盗用了用户的身份,以用户的名义发送恶意请求。Django内置了一个 CSRF 中间件,请打开 ./mysite/settings.py ,找到 MIDDLEWARE 部分,其中有一个参数 django.middleware.csrf.CsrfViewMiddleware ,有了这个中间件,就能保证 Django 免受 CSRF 攻击了。如果前端通过 POST 方式提交数据,就会被禁止,而 POST 数据又是必不可少的,解决方法之一就是在表单中使用 {% csrf_token %} ,与表单内容一同被提交。为了验证,可以暂时将其删除,观察是否或出现 Forbidden(403) 错误。
前面曾经打印了一个未绑定表单实例对象,结果就是 HTML 的表单框。当请求为 GET 时,视图函数中对应产生的就是未绑定对象,用这个对象来渲染前端模板,得到一个等待输入的表单,这就是语句【2】,只不过是使用了实例对象 as_p 方法,它使得表单显示为一系列
标签,每个表单元素在一对
标签内。与之类似的还有 as_u 和 as_table 。如果此时查询页面源码 http://localhost:8000/account/login ,机会发现一系列被
标签所包裹的元素。
接下来,给页面的导航条中预设的 LOGIN 添加超链接,指向现在做的登录页面。
首先修改 ./templates/headers.html 中的部分代码:
{% load staticfiles %}
...
...
语句 [3] 做了一个超链接 href="{% url ‘account:user_login’ %}" ,注意这里采用“软编码”,而没有直接将Login地址写上去(即“硬编码”)。这是因为将来把代码部署到生产环境中时,必须要修改域名部分 ,否则这个访问连接就会出错。而 {% url “account:user_login” %} 这种写法可以满足代码的随意迁移需要。这里的URL是模板中的标签 ,表示网址,它将引导程序到 ./mysite/urls.py 和 ./account/urls.py (目前本项目中配置)中“按图索骥”。观察 {% url “account:user_login” %} 的结构, account 是本应用的 namespace (配置在 ./mysite/urls.py 中),在 namespace 的名字后面是冒号,冒号后面是 URL 的 name (配置在 ./account/urls.py 中)。如此,就告诉程序去找那个 URL 了。
为了查看效果,先访问 http://localhost:8000/blog/ ,因为在模板中使用的都是 head.html ,所以导航条右边 同样有 LOGIN。单机 LOGIN ,就回跳转到刚刚创建的用户登录页面。
接下来对展示内容做优化。在这里我们使用 {{ form.as_p }} 的方式得到了前段模板所要渲染的表单,但是这种方式略显死板。比如,要求 Username 用红色字而 Password 用蓝色字,上面写法就不能实现了。于是就需要对代码进行修改,以下为修改 ./templates/account/login.html 文件后的代码:
{% extends "base.html" %}
{% load staticfiles %
{% block title %}Login{% endblock %}
{% block content %}
Login
Input your username and password
{% endblock%}
上述代码看上去很多,其实只是更多的使用了 Bootstrap 的样式。关键是里面的 {{ form.username }} 和 {{ form.password }} ,用这种方式单独实现了 < input > 元素 。
满足需求的代码主要是这部分:
2.2.5 知识点
内置用户权限管理
利用 Django 开发网站,可以使用其内置的用户权限功能,也可以自己重新编写一个用户管理的应用。在本教程所展示的项目中,都是使用 Django 默认的用户管理应用。如果读者是跟着本教程的流程来实践的,那么打开 ./mysite/settings.py 文件,会在 INSTALLED_APPS 的列表中发现 django.contrib.auth 应用,这是创建本项目时默认就有的,它就是 Django 默认(内置)的用户权限管理应用。这个应用的所有源码都存放在 Django 本机上的安装目录内,比如在笔者的计算机中就位于 /usr/local/lib/python3.7/site-packages/django/contrib/auth 内。
当完成数据迁移后,可以在数据库中看到以 auth 作为前缀的数据库表,其中 auth_user 是我们在本节中要关注的,打开它,会看到这个表的基本结构,
Column ID
Name
Type
Not Null
Default Value
Primary Key
0
id
integer
1
null
1
1
password
varchar(128)
1
null
0
2
last_login
datetime
0
null
0
3
is_superuser
bool
1
null
0
4
first_name
varchar(30)
1
null
0
5
last_name
varchar(30)
1
null
0
6
email
varchar(254)
1
null
0
7
is_staff
bool
1
null
0
8
is_active
bool
1
null
0
9
date_joined
datetime
1
null
0
10
username
varchar(150)
1
null
0
完成用户的登录或者 2.4 节中的注册,就是针对这个表进行的操作,它对应着 django/contrib/auth/models.py 文件中的数据模型类 User。
用超级管理员身份登录( http://localhost:8000/admin ) ,在页面找到对用户进行管理的地方,单击进入,可以查看当前用户,并且能够对用户进行增删改等操作。这些都是 Django 默认的 auth 应用中所具有的功能。
Django默认的用户管理应用功能非常强大,本节仅使用了登录和退出两个简单的功能。
读者如果想全面了解,可以阅读官方文档要阅读官方文档,不推荐阅读翻译的文档,毕竟跟原文那年有出入。不管现在是否能够全面理解,把其内容阅读一遍,以备不时之需。
表单的请求响应过程
在 Web 上,表单的用途非常多。 Django 响应前端发送表单请求的详细过程如下图:
(1)用户通过自己的浏览器(客户端)第一次向服务器发出含有表单页面的请求, Django 会创建一个未绑定数据的表单实例 (例如 form = LoginForm() , form 实例就是未绑定实例),然后反馈到前端页面,等待用户填写内容。 (2)用户在客户端填写了表单内容之后,将其提交给服务器,在 Django 的视图中接受数据( 例如 form = LoginForm(request.POST) ),然后验证表单数据是否合法 ( form.is_valid() )。 (3)通过验证 之后,可以对表单进行进一步操作,例如保存、 URL 转向等 。结束之后,本次表单提交过程完毕。 (4)未通过验证 则将返回绑定表单实例 (携带已经提交的数据和错误信息)让用户修改之后再次提交。注意,本节教程不包含这一步,返回的只有一段文字,提示“username or password is not right“”。
文档导读 (1) Using the django authentication system
(2) User Authentication in Django
2.3 用内置方法实现登录和退出
前面花费不少时间学习用户登录功能,对于网站而言登录是非常普遍的,而且功能雷同,都是输入用户名和密码。在 Django 中,凡是通用的功能,都尽可能提供了默认的方法,不需要用户编写代码,只需调用即可实现。
下面学习 Django 内置的登录方法。在使用之前,要找到这个方法写在什么地方了,并且要看看源码。用内置方法实现登录和退出的基本知识如下图所示:
内置登录退出
登录
判断登录状态
退出
django.contrib.auth.views.login
指定模板
登陆后转向
直接在 URL 中配置
django.contrib.auth.views.logout
退出模板
直接在 URL 中配置
参数 template_name
2.3.1 内置的登录方法
先找到 Django 的安装位置,以笔者的计算机为例,他在 /usr/lib64/python2.7/site-packages/ 中。打开其中写有内置登录函数的文件,即 /usr/lib64/python2.7/site-packages/django/contrib/auth/views.py 文件(寻找方法如 help() ),可以查看 login() 函数的源码:
@deprecate_current_app
@sensitive_post_parameters()
@csrf_protect
@never_cache
def login(request, template_name='registration/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
extra_context=None, redirect_authenticated_user=False):
"""
Displays the login form and handles the login action.
"""
redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, ''))
if redirect_authenticated_user and request.user.is_authenticated:
redirect_to = _get_login_redirect_url(request, redirect_to)
if redirect_to == request.path:
raise ValueError(
"Redirection loop for authenticated user detected. Check that "
"your LOGIN_REDIRECT_URL doesn't point to a login page."
)
return HttpResponseRedirect(redirect_to)
elif request.method == "POST":
form = authentication_form(request, data=request.POST)
if form.is_valid():
auth_login(request, form.get_user())
return HttpResponseRedirect(_get_login_redirect_url(request, redirect_to))
else:
form = authentication_form(request)
current_site = get_current_site(request)
context = {
'form': form,
redirect_field_name: redirect_to,
'site': current_site,
'site_name': current_site.name,
}
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context)
必须关注这个视图函数所要渲染的模板位置 template_name = 'registration/login.html ,因为后面我们要用到。
虽然本节不解释上述函数,重点在于应用它,不过还是要推荐读者逐行进行阅读并作必要注释,理解代码的含义。
在 ./account/urls.py 文件中做如下编辑:
from django.conf.urls import url
from . import views
from django.conf import settings
from django.contrib.auth import views as auth_views # [1]
urlpatterns = [
#url(r'^login/$', views.user_login, name = "user_login"), # customized login
url(r'^login/$', auth_views.login, name = "user_login"), #django's built-in login
]
语句 [1] 引入了刚才找到的 Django 内置视图文件,并且将其重命名为 auth_views ,然后在路由配置中,使用 auth_views.login 调用该视图函数中的 login() 函数。
不需要像前面那样编写登陆的视图函数了,可以看一下登陆的模板文件。
在刚才看到的 login() 函数中规定了模板,即 registration/login.html ,但是不要忘记,我们在前面已经将本项目的所有模板都转移到了项目目录中的 ./templates 里面,所以需要在 ./templates/registration 中建立 login.html 模板。可以直接将2.2中的 ./templates/account/login.html 复制过来,并做如下修改:
...
Input your username and password
这里修改的语句 [2] 中的 action = ‘{% url ‘account:user_login’ %}’ ,用具体的 URL 指明了数据的 POST 目标。
但是,此时访问登录页面成功,却在输入正确用户名和密码、点击登录后报错 (404) " http://localhost:8000/accounts/profile/ not found "。
也就是说,成功登陆后,自动重定向到了这个地址。这时再观察 Django 内置的 login() 函数,在他的参数中有一项 redirect_field_name = REDIRECT_FIELD_NAME ,这就是登录后重定向的设置。上述跳转的地址,就是这个重定向设置的默认值,要想改变重定向的值,需要在 ./mysite/settings.py 文件中设置 LOGIN_REDIRECT_URL 的值,在文件中添加此配置(可以放在最后):
LOGIN_REDIRECT_URL = '/blog/'
这行代码的含义是登陆后重定向到 http://localhost:8000/blog/ 页面,读者当然可以设定为任何一个可以访问的地址作为重定向目标地址。
再次登录,发现登陆成功之后已经转到了指定页面。
到这里还没有结束。问题虽然只有一个(用内置方法实现登录),解决方法有多个,前面演示的只是方法之一。
仔细观察前面的 login() 源码,发现这个函数的参数由若干个,现在我们姑且只关心模板问题,可以给函数的参数 template_name 传值,就是让 url 对应 的视图函数对应 一个新的模板文件 。
编辑 ./account/urls.py 文件,代码如下:
from django.conf.urls import url
from . import views
from django.conf import settings
from django.contrib.auth import views as auth_views # [1]
urlpatterns = [
#url(r'^login/$', views.user_login, name = "user_login"), # customized login
url(r'^login/$', auth_views.login, name = "user_login"), #django's built-in login
url(r'^new-login/$',auth_views.login, {'template_name':'account/login.html'}),
]
为了进行对比,原来的URL不变,新增加一个URL配置,即语句 [3] ,以一个字典类型,默认的 auth_views.login 函数对象传 template_name 的值, account/login.html 就是此前已经写好的登录模板文件。
就这么多,不用再修改。保存后访问 http://localhost:8000/account/new-login/ ,登录界面赫然展现于眼前。
而第一个方法虽然有些绕远,却仍我们更加认清源码的道理。
2.3.2 判断用户是否登录
在模板中,常常需要根据用户是否登录进行不同的显示。下载就来修改 ./templates/header.html 模板,使 LOGIN 所在的位置 (即‘头部位置’)根据用户登录状态显示不同的内容 。如果用户已经登陆,则显示用户名和 Logout 字样;如果用户尚未登录,则显示 LOGIN 字样,并做超链接到登录界面。
...
...
【注】这里的 user 对应 2.2.3 中 authenticate() 函数的返回对象,若名码正确则返回一个 User 实例对象,否则返回 None。
在模板中,语句 [1] 类似 Python 中的 if 语句,请读者注意观察这种模板语言的书写规则。在模板中可以通过 request.user 得到用户对象, user.is_authenticated 则是返回用户登录的状态,已经登录返回 True ,否则返回 False 。
上述代码中 Logout 和用户名的超链接都留空了,后续会填补上。登陆之后的导航条状态可以用浏览器查看。
2.3.3 内置的退出方法
Django 不仅有内置的登录函数,还有退出函数,因为退出也是所有网站必备的,故 Django 把这个功能内置了。
与登录类似,首先配置 URL 。 编辑 ./account/urls.py 文件,增加如下内容:
urlpatterns = [
...
url(r'^logout/$', auth_views.logout, name = 'user_logout'),
]
此处配置好后,把 ./templates/header.html 文件中那个针对 Logout 的超链接修改一下。
Logout
检查 Django 服务是否在运行,刷新页面并登陆。一切准备就绪,单机 Logout 按钮,该用户退出,并且打开页面 http://localhost:8000/account/logout/ 。这个页面是 Django 为退出函数定义好的界面,对应使用的是 ./templates/registration/logged_out.html 默认模板,可以重写这个模板文件,但这种方法这里暂时不用,而是创建一个退出的模板文件 ./templates/account/logout.html ,其中的代码如下:
{% extends "base.html" %}
{% block title %}Logout{% endblock %}
{% block content %}
You have log out
You can login again
{% endblock %}
仿照前面登陆页面( new-login )的 URL 配置,修改退出的 URL 配置:
url("^logout/$", auth_views.logout, {'template_name':'account/logout.html'}, name = 'user_logout'),
再从登录状态执行退出操作,即可看到自定义的退出页面。
2.3.4 知识点
Django uses request and response objects to pass state through the system. 这句话源自 https://docs.djangoproject.com/en/1.10/ref/request-response/,请读者认真阅读。
HttpRequest 对象 当客户端向服务器发送请求时, Django 会创建一个包含请求数据的 HttpRequest 对象,并以参数 request 传给视图函数 ,即参数 request 所引用的对象就是 HttpRequest 对象 。HttpRequest 对象由很多属性,下面列出几项,供读者参考查阅(除 session 外的其他属性都应该被看作是只读的,并且下列各项属性区分大小写)。
if request.user.is_anonymous():
#Do something for anonymous users.
else:
#DO something for logged-in users.
session :返回可读写的类字典对象,表示当前的 session 。
除上述属性外, HttpRequest 对象还有一些方法,读者可以到官方文档中查看。
在一个 HttpRequest 对象中, GET 和 POST 属性都是 django.http.QueryDict 的实例 。 QueryDict 是一个类字典的对象,可以处理同一个键有多个值 的情况。
HttpResponse 对象 对应着 HttpRequest 对象, HttpResponse 是视图向客户端 返回的对象。典型的用法就是将页面的内容作为字符串传递给 HttpResponse 函数。例如(以下示例来自官方文档):
>>> from django.http import HttpResponse
>>> reponse = HttpResponse("Here's the text of the Web page.")
>>> response = HttpResponse("Text only, please.", content_type = "text/plain")
如果要随时增加内容,还可以像操作文件一样来操作 HttpResponse 对象。
>>> response = HttpResponse()
>>> response.write("Here's the text of the Web page.
")
>>> response.write("Here's another paragraph.
")
HttpResponse 对象也有一些属性和方法,在这里就省略了,请读者认真阅读前面所提到的官方文档,详细了解相关属性和方法,此外,这个对象还有一些子类,在某些情况下也会用到,请读者自行浏览。
文档导读
(1) Request and response objects
(2) How to Work With AJAX Request With Django
2.4 用户注册
此前所使用的账户是通过超级管理员设置的,这种方式通常被某些比较封闭的网站采用, 比如机构内部管理网站,目的是要严格审查用户身份。对于社会性网站,一般允许通过自行注册的方式成为网站用户。
所以,要学习如何实现注册功能,如下图所示
用户注册
注册表单类
视图函数
附加注册内容
在后台管理附加内容
基于数据模型User
内部类
clean_variablename 方法
form.save, 默认参数commit=False
数据模型类
前段模板
list_display
list_filter
model
fields
不同模型的one2one关系
数据库表字段
2.4.1 简单注册
不能急功近利,先看一个简单的例子。
最简单的就是注册用户名和密码,并且也不单独创建数据库,就利用原有的数据库,即 Django 默认的用户数据模型。
实现用户注册功能与实现登录功能类似,都要填写表单。所以,编写一个表单类时必须的。编辑 ./account/forms.py 文件,增加一个注册用户的表单类:
# ./account/forms.py
from django import forms
from django.contrib.auth.models import User # [1]
...
class RegistrationForm(forms.ModelForm): # [2]
password = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(label="Confirm Password", widget=forms.PasswordInput)
class Meta: # [3]
model = User # [4]
fields = ("username", "email")
def clean_password2(self): # [5]
cd = self.cleaned_data
if cd['password'] != cd['password2'] :
raise forms.ValidationError("Password do not match.")
return cd['password2']
这个表单类比前面定义的 LoginForm 类复杂,并且有一些差别。
语句 [1] 引入了 Django 默认的用户模型 User 类 ,在这个表单类中就应用 User 模型,不需要再新建用户数据模型了。
前面的登录表单类中曾经继承了 forms.Form 类,而这里的注册表单类继承了 forms.Model ,它是 forms 里面的另外一个常用的类,这两个类在应用上是有区别的。一般情况下,如果要将表单中的数据写入数据库 或者修改默写记录的值 ,就要让表单类继承 forms.ModelForm 类 ;如果提交表单之后,不修改 数据库,则继承 forms.Form 类 。其实,读者从这两个类的名称上也能看出来, ModelForm 是和该应用的 Model 有关的表单。
语句 [3] 是一个内部类 ,其中我们需要声明本表单类所应用的数据模型 ,也就是将来表单的内容会写入到哪个数据库中的哪些记录里面,这就是语句 [4] model = User
的作用。表单类中的属性和数据模型类中的属性一一对应 ,有时候我们不必 在表单中向数据模型类中的所有字段赋值 (或者不需要把数据库表中所有的字段写入数据),可以用 fields = (“username”,“email”) 来说明这里所选用的属性 (数据库表中的字段),或者用 exclude 列表说明所排除的属性。
password 和 password2 两个密码属性是为了让用户确认自己所设置的密码 ,这两个属性是在这里重新定义的。如同前面 LoginForm 表单里面定义的两个属性,这里定义了就意味着覆盖或者不需要 在内部类 Meta 中声明 数据模型类中的字段。
随后,在类内部还定义了语句 [5] 中的方法 clean_password2() ,这个方法的作用是检验用户所输入的两个密码是否一致 。检查的动作在我们调用表单实例对象的 is_valid() 方法时会被执行。以 " clean_ + 属性名称 " 命名方式所创建的方法,都有类似的功能。
【注意】两个表单类继承自不同的forms.xxx ,但其实例都可以调用 .is_valid, .cleaned_data 方法。
数据模型类( User )和表单类( RegistrationForm )都已经有了,接下来就是视图函数 和相应的前端模板 。
继续编辑 ./account/views.py 文件,编写注册的视图函数:
# ./account/views.py
from .forms import LoginForm, RegistrationForm
def register(request):
if request.method == "POST":
user_form = RegistrationForm(request.POST)
if user_form.is_valid():
new_user = user_form.save(commit=False)# [1]
new_user.set_password(user_form.cleaned_data['password'])# [2]
new_user.save()
return HttpResponse("successfully")
else:
return HttpResponse("sorry, your can not register.")
else:
user_form = RegistrationForm()
return render(request, "account/register.html", {"form":user_form})
这个视图函数和此前编写的 user_login() 函数的基本结构相似,只是在对表单的处理上稍有差别。
ModelForm 类或者它的子类都具有 save()方法 ,它的效果是将表单数据保存到数据库 ,并生成该数据对象。但是,语句 [1] 应用此方法时,在参数里多了 commit=False ,其结果是数据并没有被保存到数据库 ,而仅生成了一个数据对象。紧接着看后面两句,语句 [2] 设置了该数据对象的密码 (这个密码是经过校验的),这个操作得益于前面的 commit=False 的设置,如果没有此参数,数据被写到数据库中了,再设置密码就需要重新写入,稍嫌浪费且慢了。
密码已经保存到数据对象了,再执行 new_user.save() 就可以把数据保存到数据库中。
前端模板 与登录模板类似,在 ./templates/account/ 里面建立一个名为 register.html 的文件,注意不是在之前搬运的 ./templates/registration/ 目录中,因为这个注册功能完全是我们自己写的,代码如下:
# ./templates/account/register.html
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}register{% endblock %}
{% block content %}
Register
If you are a user, Login , please
or register.
{% endblock %}
最后不要忘记配置 url :
# ./account/url.py
from django.conf.urls import url
from . import views
urlpatterns = [
...
url(r'^register/$', views.register, name="user_register"),
]
最后开始运行 Django 服务,访问 http://localhost:8000/account/register ,输入用户名和密码进行注册,如果看到 “successfully” ,就说明注册成功了。这是根据视图函数 register() 的规定返回的结果。
虽然上述注册信息比较简单,但是读者了解了注册的流程,稍微复杂的实例在后面。
2.4.2 增加注册内容
对于注册的过程,不同的网站有不同的需求,所以 Django 并没有像登录、退出那样设置一个内置的方法,这就给开发者留下了很多发挥的空间,比如针对前面的注册过程,如果觉得仅让用户在注册时填写用户名、邮箱和密码有点简单,那么还可以增加其他的项目,在默认的 User 数据模型中还有用户姓氏、名字等。如果要增加,可以在表单类的内部类 fields 的值中继续增加属性(数据库表的字段)名称。
但是,要增加一个 User 数据模型中没有的字段(比如手机号)该怎么办?下面就解决这个问题。
Column ID
Name
Type
Not Null
Default Value
Primary Key
0
id
integer
1
null
1
1
password
varchar(128)
1
null
0
2
last_login
datetime
0
null
0
3
is_superuser
bool
1
null
0
4
first_name
varchar(30)
1
null
0
5
last_name
varchar(30)
1
null
0
6
email
varchar(254)
1
null
0
7
is_staff
bool
1
null
0
8
is_active
bool
1
null
0
9
date_joined
datetime
1
null
0
10
username
varchar(150)
1
null
0
如上表所示为 User 对应的数据库表 auth_user 中的字段。
假如要增加注册项目的实际需求,首先要有数据模型 。在 ./account/models.py 中增加如下代码:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):# [1]
user = models.OneToOneField(User, unique=True)# [2]
birth = models.DateField(blank=True, null=True)
phone = models.CharField(max_length=20, null=True)
def __str__(self):
return 'user {}'.format(self.user.username)# ???
语句 [1] 所定义的数据模型类对应着要在数据库中建立的名为 account_userprofile 的数据库表。
语句 [2] 中定义了名为 user 的字段, OneToOneField() 的含义是通过 user 这个字段(作为纽带)声明 UserProfile 类 与 User 类之间的关系是“一对一 ”的。
在项目根目录下依次执行如下命令,迁移数据:
[root$mysite] python manage.py makemigrations account
Migrations for 'account':
account/migrations/0001_initial.py:
- Create model UserProfile
[root$mysite] python manage.py migrate account
Operations to perform:
Apply all migrations: account
Running migrations:
Applying account.0001_initial... OK
读者计算机的显示结果可能与笔者计算机的显示结果有所差异,但不必为此惊讶,重点看能不能实现最终界面的显示效果。
用 Firefox 浏览器的插件 SQLite Manager 查看本项目的数据库,会看到刚刚同步的数据库表。表的主体结构如下:
Column ID
Name
Type
Not Null
Default Value
Primary Key
0
id
integer
1
null
1
1
phone
varchar(20)
0
null
0
2
user_id
integer
1
null
0
3
birth
date
0
null
0
的确生成了一个名为 account_userprofile 的数据库表,并且它的表结构跟刚才设计的 UserProfile 类 中的属性一致。
对照刚才生成的数据库表,读者不妨总结一下这个表的命名特点 : account 是应用名称; userprofile 是数据模型类中的类名称,只不过都是小写字母;这个数据库表的结构是按照 ./account/models.py 中的 UserProfile 类的规定建立的。
按照流程,下面应该编写表单类了,编辑 ./account/forms.py 文件,代码如下:
from django import forms
from django.contrib.auth.models import User
from .models import UserProfile
...
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ("phone", "birth")
这个表单类非常简单,其内部类声明其调用了 UserProfile 中的 phone 、 birth 。
下面要完善视图函数,编辑 ./account/views.py ,代码如下:
# ./account/views.py
...
from .forms import LoginForm, RegistrationForm, UserProfileForm
...
def register(request):
if request.method == "POST":
user_form = RegistrationForm(request.POST)
userprofile_form = UserProfileForm(request.POST)
if user_form.is_valid() && userprofile_form.is_valid():
new_user = user_form.save(commit=False)# [1]
new_user.set_password(user_form.cleaned_data['password'])
new_user.save()
new_profile = userprofile_form.save(commit=False)
new_profile.user = new_user# ???
new_profile.save()
return HttpResponse("successfully")
else:
return HttpResponse("sorry, your can not register.")
else:
user_form = RegistrationForm()
userprofile_form = UserProfileForm()
return render(request, "account/register.html", {"form":user_form, "profile":userprofile_form})
修改后的视图函数跟原来相比,主要是兼顾 了另外一个表单类,所以只是在代码行数上增加了,并没有增加新的内容。
可以在交互式命令行中联系啊,结果如下:
>>> from account.forms import UserProfileForm
>>> data = {"birth":"1997-07-02", "phone":"4"}
>>> up = UserProfileForm(data)
>>> up.is_valid()
True
>>> up.cleaned_data
{'phone': u'4', 'birth': datetime.date(1997, 7, 2)}
下面修改模板,重新编辑 ./templates/account/register.html 文件,代码如下:
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}register user{% endblock %}
{% block content %}
Register
If you are a user, Login , please
or register.
{% endblock %}
只是多了几行代码,用于处理 birth 和 phone 。
最后开始运行 Django 服务,访问 http://localhost:8000/account/register ,依次填写各项内容进行注册,如果成功注册了,那么用户信息将被保存到数据库中。查一下数据库,会看到新注册的用户信息。
查看 auth_user 这个内置的用户数据库,用户 id 为8的那个用户是刚刚注册的,如下图所示。
再来看跟它具有一对一关系的、我们自定义的 account_userprofile 数据库表,可以看到 user_id 为8的用户,这里记录了对应用户的 id 和新增加的 birth 和 phone 两个字段。
2.4.3 管理新增的注册内容
还记得前面曾经创建了本项目的超级管理员么,当项目运行起来之后( runserver ),访问 http://localhost:8000/admin ,用超级管理员账户登录,进入可对全站内容进行管理的界面,比如管理网站用户。
当我们增加用户注册信息后,所增加的内容能不能也在超级管理员界面中进行集中管理呢?要实现这个功能,需要对 ./account/admin.py 文件进行编辑,代码如下:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from .models import UserProfile
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'birth', 'phone')
list_filter = ("phone",)
admin.site.register(UserProfile, UserProfileAdmin)
保存代码之后,刷新会看到新增的 ACCOUNT 选项和其下面的 User profiles 选项,他们关联到 admin.py 中的代码。单击 User profiles 选项,可以看到对新增加的注册内容的管理。
结合前端的结果,对刚才所编写的 UserProfileAdmin() 函数进行简要说明。随着后续学习,还会不断深化对类似功能的理解。
当然,作为超级管理员也有权限增加用户的信息,单机“ ADD USER PROFILE ”按钮可以进行增加用户信息的操作。
通过上述操作可以看出,在 admin.py 文件中所编写的代码,主要是将某些内容注册到后端管理界面,从而让超级管理员对该内容进行管理。
注册功能对网站的重要性不言而喻,不同的网站会根据自己的要求对注册功能提出不同的需求。读者在自己的项目中,可以使用类似本项目的方法实现,也可以不使用 Django 默认的 User 表,完全自己来编写用户管理相关应用,只不过这时要考虑到有关的周边问题,不仅仅是把用户的信息保存到数据库,比如 session 等。
2.4.4 知识点
模型:字段属性类型
数据模型类( Model )依然是 Python 中的类,可以在其中命名属性(变量)和方法。每个 属性(变量)对应着数据库中的一个 字段 ,所以在 Django 的数据模型类中,也将变量称为字段。数据库中的字段要有自己的属性,那么数据模型类中的字段也应该有属性 ,这些属性在 django.db.models 中,比如 title = models.CharField(max_length=300) 。
在默认情况下, Django 会为每个数据模型类设置一个自增的 id 字段,并将其作为该数据库表的主键,因此 id 不需要在数据模型类中写出来 。当然,开发者也可以在数据模型类中自行指定主键,方法是在该字段属性中增加 primary_key=True 。除这个默认的 id 字段外,其他字段都需要开发者在数据模型类中声明其名称和属性。
CharField :用于保存字符串 ,在使用时一定要声明字符串的长度
TextField :与 CharField 一样,区别在于保存的字符串长度是无限的(严格说应该受制于数据库程序和硬件系统),比如 BlogArticle 中的 body 字段。
EmailField 、 URLField :都继承了 CharField ,只不过其中包含了验证它们的值是否是 E-mail 和 URL 的方法。将某个字段设置为该类型,不需要再编写对输入数据的验证方法。
FieldField :表示该字段接受上传文件同时将上传的文件保存到服务器上。
DateField 、 DateTimeField :用于保存时间,有一个常用参数 auto_now_add ,如果 auto_new_add=True ,那么当数据模型实例被保存时,当前时间将自动被存储,而不需要为该字段进行赋值。
换个角度看,每个字段其实也是建立了一个属性类的实例 ,比如 title=models.CharField(max_length=300) , CharField 就是一个类,以这种方式建立了一个实例,并赋值给变量 title 。在创建实例时,传入类的参数 会有所不同,从而创建不同的实例,比如可以使用 unique=True ,即意味着该实例或者该字段在数据库中的值必须是唯一的 (但不一定是主键)。比如用户名或者用户邮箱,就可以做如此设置。
关于字段属性,上述仅列出几项,随着项目深入读者还会遇到很多。
用户注册拓展知识
Django 默认的 django.contrib.auth 应用中有登录 login() 方法,但是没有用户注册的方法,只能自己动手。本节介绍了一种简单的用户注册过程,所采用的数据模型是 Django 内置的用户管理应用中的数据模型 ( from django.contrib.auth.models import User ),也就是将用户注册信息存入上节所使用的数据库表中。
用户注册是一个比较个性化的功能,如果所需要的用户注册信息超出了默认数据模型的范畴,还要自己完成扩展部分,本节也做了这种演示。
虽然 Django 没有默认的注册功能,但是注册是网站开发所不可或缺的。全世界众多的 Django 开发者对这种常用而 Django 官方不提供的功能往往是痛心疾首的,于是奋起改变,就出现了很多第三方的 Django 应用,比如注册的 django-registration 就是一个典型例子,读者可以访问官方网站( http://django-registration.readthedocs.io/en/2.0.4/install.html )了解这个应用的使用方法,可以按照网站的说明自己进行调试。
现在是社交媒体泛滥的时代,很多网站都支持用社交媒体账号登录,用 Django 开发的网站项目当然也能做到。 Django 继承了 Python 的优良传统——开发,这就让很多人有机会为 Django 提供“轮子”。所以,如果需求是某种比较通用的功能,可以先在网上找找有没有“轮子”。 Django Social Auth 就是一个专门用于社交媒体账户登录 Django 项目的第三方应用。 githum.com 上的源码地址是 https://github.com/omab/django-social-auth ,其中有较为详细的说明。建议读者在基本完成本书的学习后,再来学习这个内容。
文档导读
(1)Model field reference
(2)用 django-social-auth 做中国社交网站第三方登录
2.5 关于密码的操作
密码与我们形影不离,简直都成为记忆负担了。网站上一般都要有关于密码的操作,常见的就是重置密码、修改密码和找回密码。关于密码的操作如下图所示
关于密码的操作
修改密码
重置密码
本地邮件服务器配置
第三方应用
内置方法
制定模板
内置方法
相关模板
django-password-reset
reverse 函数
password_reset
password_reset_done
password_reset_confirm
password_reset_complete
template_name
email_template_name
subject_template_name
password_reset_confirm.html
password_reset_complete.html
2.5.1 修改密码
在 Django 中默认有修改密码的方法,其使用方法与前述的用户登录的内置方法类似。为了能够理解这个方法,还是先浏览一下其源码,其位置与内置的 login() 在同一个文件中。
# /usr/lib64/python2.7/site-packages/django/contrib/auth/views.py
...
@sensitive_post_parameters()
@csrf_protect
@login_required
@deprecate_current_app
def password_change(request,
template_name='registration/password_change_form.html',
post_change_redirect=None,
password_change_form=PasswordChangeForm,
extra_context=None):
...
@login_required
@deprecate_current_app
def password_change_done(request,
template_name='registration/password_change_done.html',
extra_context=None):
...
函数的详细代码从略。
重点来看看这两个函数的参数 ,尤其是看模板的位置和文件名,然后到我们设置的模板文件 ./templates/registrtion 中查看是否存在上述两个文件( password_change_form , password_change_done) ,如果没有,可以自己创建或者从 Django 安装文件的模板中复制过来。
接下来,读者可以自己尝试写一下修改密码的功能,然后看下文进行对照。很可能你的方法更好。
设定修改密码的 URL ,这与使用内置登录方法的步骤是一样的,编辑 ./account/urls.py 文件,代码如下:
urlpatterns = [
url(r'^password-change/$', auth_views.password_change, name='password-change'),
url(r'^password-change-done', auth_views.password_change_done, name='password_change_done'),
]
观察默认视图函数指向的模板文件 ./templates/registration/ ,不难发现他们都是默认的模板文件格式。我们还是自定义模板吧,方法有两个,一个是修改 ./templates/registration/ 里面的模板文件,另一个是在 ./templates/account/ 中创建新的模板文件,并利用前面已经学习的方法,在 URL 配置中制定模板文件。
为了不跟前面的方法重复,使读者了解另外一种方法,这次我们采用前者。为了突出学习的重点,笔者删除了原有模板的很多内容,重点表现修改密码的部分,当然也兼顾了使用 bootstrap 的一贯风格。 ./templates/registration/password_change_form.html 的代码如下:
{% extends "base.html" %}
{% block title %}password change{% endblock %}
{% block content %}
Change Password
Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.
{% if form.new_password1.help_text %}
{{ form.new_password1.help_text|safe }}
{% endif %}
{% endblock %}
同样,也把表现很复杂的 ./templates/registration/password_change_done.html 重写,代码如下:
{% extends "base.html" %}
{% block title %}
password change done
{% endblock %}
{% block content %}
Your password was change.
{% endblock %}
按照前面登陆的经验,现在就可以调试了。不过,一定要保证 Django 服务区已经运行。先用一个已经存在的账户事先登陆,然后另外打开一个网页,输入 http://localhost:8000/account/password-change/ ,结果发现报错 NoReverseMatch at /account/password-change/
。
报错并不可怕。通过 Traeback ,可以看到报错信息的倒数第三项:
/usr/lib64/python2.7/site-packages/django/contrib/auth/views.py in password_change
308. post_change_redirect = reverse('password_change_done')
这个报错位置指向了上文那个保存默认登录、修改密码等内置视图函数的文件,查看源码的第308行,会发现有语句post_change_redirect = reverse('password_change_done')
,这句话中的 reverse() 是关键。其作用是什么?下面通过一个例子来说明。
假设在某个应用中做了如下 URL 设置
from blogs import views
url(r'^article/', views.article, name='my_article')
如果要得到上述 URL 路径 ,一种比较死板的方法就是直接拼写一个路径字符串article_url = "myapp/article/"
。这种做法虽然可行,但是死板。如果修改了 URL 中的路径设置,就要修改此处设置,一旦忘记就会报错,显然不符合程序编写规则。
所以就有了函数 reverse() 。同样,针对上面的路径, reverse() 的做法为article_url = reverse("my_article")
。这里使用了在 URL 设置中规定的别名 。这种方式比较常用,因为这样做之后,不管路径如何修改,只要别名不变就能顺利找到该路径。
但是,引用这个别名的时候要特别注意,必须要按照从 ./mysite/urls.py 中配置的应用 namespace 开始向下 找,才能准确找到路径。一般一个项目里面可能有多个应用。所以,我们可以这样修改第308行的语句:
post_change_redirect = reverse('account:password_change_done')
这样做之后,就避免了前面的错误。但这是不好的解决之道。
细心的读者是不是注意到在 password_change() 函数中,有一个参数 post_change_redirect=None ,再看源码,是这样描述的:
if post_change_redirect is None:
post_change_redirect = reverse('password_change_done')
else:
post_change_redirect = resolve_url(post_change_redirect)
所以,我们完全可以不用修改这个源码,而是在 URL 配置中声明 post_change_redirect 参数的值即可。于是将上述对 password_change() 函数的修改还原,然后修改 ./account/urls.py 文件:
urlpatterns = [
...
url(r'^password-change/$', auth_views.password_change, {"post_change_redirect":"/account/password-change-done"}, name='password_change'),
url(r'^password-change-done/$', auth_views.password_change_done, name='password_change_done'),
]
在保证用户已经登陆的状态下,重复访问修改密码的界面,成功。还是用这种方法(修改传参 )比较好,不要去修改内置方法的源码。
已经完成了修改密码的页面,但是,对于一个网站来说,总要有一个地方能够让用户进入到修改密码的页面,这就是入口。当用户登录之后,在导航条上 会显示该用户的用户名,可以将修改密码的入口放在这里。单击“用户名”,出现一个下拉菜单,单击“修改密码”,跳转到刚才已经建立的修改密码页面。
所要修改的仅仅是一个模板文件 ./templates/header.html ,修改之后其代码如下:
{% load staticfiles %}
...
{% if user.is_authenticated %}
...
上述代码中的
部分不要丢失,因为我们在修改密码的功能上使用了 bootstrap 提供的下拉菜单功能,需要 JavaScript 脚本支持。
修改好后,保证用户处于退出状态,然后重新登录,在导航条的右侧显示用户名的位置单击鼠标,在下拉菜单中就能看到“修改密码”了。单击修改密码,就会跳转到前面已经做好的修改密码页面,用户可以在该页面中修改自己的密码。
【注意!!!】按钮代码的位置 不能乱改,必须紧跟上面所示上一行代码{% if user.is_authenticated %}
;别忘了引入JavaScript 的代码。
2.5.2