02-用户账户
摘要
Web 应用程序的核心是让任何用户都能够注册账户并能够使用它,不管用户身处何方。在本章中,我们将创建一些表单,让用户能够添加主题和条目,以及编辑既有的条目。我们还将学习 Django 如何防范对基于表单的页面发起的常见攻击,从而无须花太多时间考虑确保应用程序安全的问题。
你好,我是悦创。
然后,我们将实现一个用户身份验证系统。首先创建一个注册页面,供用户创建账户,并让有些页面只能供已登录的用户访问。接下来,修改一些视图函数,使得用户只能看到自己的数据。我们还将学习如何确保用户数据的安全。
1. 让用户输入数据
建立用于创建用户账户的身份验证系统之前,我们先来添加几个页面,让用户能够输入数据。我们将让用户添加新主题,添加新条目以及编辑既有条目。
- 新主题
- 新条目
- 编辑既有条目
当前,只有超级用户能够通过管理网站输入数据。我们不想让用户与管理网站交互,因此我们将使用 Django 的表单创建工具来创建让用户能够输入数据的页面。
1.1 添加新主题
首先来让用户能够添加新主题。
创建基于表单的页面的方法几乎与前面创建页面一样:定义 URL,编写视图函数并编写一个模板。一个主要差别是,需要导入包含表单的模块 forms.py
。
1. 用于添加主题的表单
让用户输入并提交信息的页面都是表单,那怕看起来不像。
用户输入信息时,我们需要进行验证,确认提供的信息是正确的数据类型,而不是恶意的信息,如中断服务器的代码。
然后,对这些有效信息进行处理,并将其保存到数据库的合适地方。这些工作很多都是由 Django 自动完成的。
在 Diango 中,创建表单的最简单方式是使用 ModelForm
,它根据我们在上章定义的模型中的信息自动创建表单。
请创建一个名为 forms.py
的文件,将其存储到 models.py
所在的目录,并在其中编写你的第一个表单:
forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm): ❶
class Meta:
model = Topic ❷
fields = ['text'] ❸
labels = {'text': ''} ❹
首先导入模块 forms
以及要使用的模型 Topic
。
在❶处,定义一个名为 TopicForm
的类,它继承了 forms.ModelForm
。
最简单的 ModelForm
版本只包含一个内嵌的 Meta
类,让 Django 根据哪个模型创建表单以及在表单中包含哪些字段。
- 在❷处,根据模型
Topic
创建表单 - 其中只包含字段
text
(见❸) - ❹处的代码让 Django 不要为字段
text
生成标签。
2. URL 模式 new_topic
新页面的 URL 应简短且具有描述性,因此当用户要添加新主题时,我们切换到 http://localhost:8000/new_topic/ 。下面是页面 new_topic
的 URL 模式,请将其添加到 learning_logs/urls.py
中:
urls.py
--snip--
urlpatterns = [
--snip--
# 用于添加新主题的页面。
path('new_topic/', views.new_topic, name='new_topic'),
]
这个 URL 模式将请求交给视图函数 new_topic()
,下面来编写这个函数。
3. 视图函数 new_topic()
函数 new_topic()
需要处理两种情形。
一是刚进入
new_topic
页面(在这种情况下应显示空表单);二是对提交的表单数据进行处理,并将用户重定向到页面
topics
:
views.py
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm
--snip--
def new_topic(request):
"""添加新主题。"""
if request.method != 'POST': ❶
# 未提交数据:创建一个新表单。
form = TopicForm() ❷
else:
# POST提交的数据:对数据进行处理。
form = TopicForm(data=request.POST) ❸
if form.is_valid(): ❹
form.save() ❺
return redirect('learning_logs:topics') ❻
# 显示空表单或指出表单数据无效。
context = {'form': form} ❼
return render(request, 'learning_logs/new_topic.html', context)
我们导入了函数 redirect
,用户提交主题后将使用这个函数重定向到页面 topics
。函数 redirect
将视图名作为参数,并将用户重定向到这个视图。我们还导入了刚创建的表单 TopicForm
。
4. GET 请求和 POST 请求
创建 Web 应用程序时,将用到的两种主要请求类型是 GET 请求和 POST 请求。对于只是从服务器读取数据的页面,使用 GET 请求;在用户需要通过表单提交信息时,通常使用 POST 请求。处理所有表单时,都将指定使用 POST 方法。还有一些其他类型的请求,但本项目没有使用。
函数 new_topic()
将请求对象作为参数。用户初次请求该页面时,其浏览器将发送 GET 请求;用户填写并提交表单时,其浏览器将发送 POST 请求。根据请求的类型,可确定用户请求的是空表单(GET 请求)还是要求对填写好的表单进行处理(POST 请求)。
❶处的测试确定请求方法是 GET 还是 POST。如果请求方法不是 POST,请求就可能是 GET,因此需要返回一个空表单。(即便请求是其他类型的,返回空表单也不会有任何问题。)
❷处创建一个
TopicForm
实例,将其赋给变量form
,再通过上下文字典将这个表单发送给模板(见❼)。由于实例化TopicForm
时没有指定任何实参,Django 将创建一个空表单,供用户填写。
如果请求方法为 POST,将执行 else
代码块,对提交的表单数据进行处理。我们使用用户输入的数据(存储在 request.POST
中)创建一个 TopicForm
实例(见❸),这样对象 form
将包含用户提交的信息。
要将提交的信息保存到数据库,必须先通过检查确定它们是有效的(见❹)。方法 is_valid()
核实用户填写了所有必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的字段类型一致(例如,字段 text
少于 200 字符,这是在上一篇 models.py
中指定的)。这种自动验证避免了我们去做大量的工作。如果所有字段都有效,就可调用 save()
(见❺),将表单中的数据写入数据库。
保存数据后,就可离开这个页面了。为此,使用 redirect()
将用户的浏览器重定向到页面 topics
(见❻)。在页面 topics
中,用户将在主题列表中看到他刚输入的主题。
我们在这个视图函数的末尾定义了变量 context
,并使用稍后将创建的模板 new_topic.html
来渲染页面。这些代码不在 if
代码块内,因此无论是用户刚进入 new_topic
页面还是提交的表单数据无效,这些代码都将执行。用户提交的表单数据无效时,将显示一些默认的错误消息,帮助用户提供有效的数据。
5. 模板 new_topic
下面来创建新模板 new_topic.html
,用于显示刚创建的表单:
new_topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Add a new topic:</p>
<form action="{% url 'learning_logs:new_topic' %}" method='post'> ❶
{% csrf_token %} ❷
{{ form.as_p }} ❸
<button name="submit">Add topic</button> ❹
</form>
{% endblock content %}
这个模板继承了 base.html
,因此其基本结构与项目“学习笔记”的其他页面相同。
在❶处,定义了一个 HTML 表单。实参 action
告诉服务器将提交的表单数据发送到哪里。这里将它发回给视图函数 new_topic()
。实参 method
让浏览器以 POST 请求的方式提交数据。
Django 使用模板标签 {% csrf_token %}
(见❷)来防止攻击者利用表单来获得对服务器未经授权的访问(这种攻击称为跨站请求伪造)。
❸处显示表单,从中可知 Django 使得完成显示表单等任务有多简单:只需包含模板变量{{ form.as_p }}
,就可让 Django 自动创建显示表单所需的全部字段。修饰符 as_p
让 Django 以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方式。
Django 不会为表单创建提交按钮,因此我们在❹处定义了一个。
6. 链接到页面 new_topic
下面在页面 topics
中添加到页面 new_topic
的链接:
topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
--snip--
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a>
{% endblock content %}
这个链接放在既有主题列表的后面。下图显示了生成的表单。请使用这个表单来添加几个新主题。
1.2 添加新条目
可以添加新主题之后,用户还会想添加几个新条目。
我们将再次定义 URL,编写视图函数和模板,并且链接到添加新条目的页面。
但在此之前,需要在 forms.py
中再添加一个类。
1. 用于添加新条目的表单
我们需要创建一个与模型 Entry
相关联的表单,但这个表单的定制程度比 TopicForm
更高一些:
forms.py
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
--snip--
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text': ' '} ❶
widgets = {'text': forms.Textarea(attrs={'cols': 80})} ❷
首先修改 import
语句,使其除导入 Topic
外,还导入 Entry
。
新类 EntryForm
继承了 forms.ModelForm
,它包含的 Meta
类指出了表单基于的模型以及要在表单中包含哪些字段。这里给字段 'text'
指定了标签 'Entry:'
(见❶)。
在❷处,我们定义了属性 widgets
。小部件(widget)是一个 HTML 表单元素,如单行文本框、多行文本区域或下拉列表。通过设置属性 widgets
,可覆盖 Django 选择的默认小部件。通过让 Django 使用 forms.Textarea
,我们定制了字段 'text'
的输入小部件,将文本区域的宽度设置为 80 列,而不是默认的 40 列。这给用户提供了足够的空间来编写有意义的条目。
2. URL 模式 new_entry
在用于添加新条目的页面的 URL 模式中,需要包含实参 topic_id
,因为条目必须与特定的主题相关联。
该 URL 模式如下,请将它添加到 learning_logs/urls.py
中:
urls.py
--snip--
urlpatterns = [
--snip--
# 用于添加新条目的页面。
path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
]
这个 URL 模式与形如 http://localhost:8000/new_entry/*id*/
的 URL 匹配,其中的 *id*
是一个与主题 ID 匹配的数。代码 <int:topic_id>
捕获一个数值,并将其赋给变量 topic_id
。
请求的 URL 与这个模式匹配时,Django 将请求和主题 ID 发送给函数 new_entry()
。
3. 视图函数 new_entry()
视图函数 new_entry()
与函数 new_topic()
很像,请在 views.py
中添加如下代码:
views.py
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm, EntryForm
--snip--
def new_entry(request, topic_id):
"""在特定主题中添加新条目。"""
topic = Topic.objects.get(id=topic_id) ❶
if request.method != 'POST': ❷
# 未提交数据:创建一个空表单。
form = EntryForm() ❸
else:
# POST提交的数据:对数据进行处理。
form = EntryForm(data=request.POST) ❹
if form.is_valid():
new_entry = form.save(commit=False) ❺
new_entry.topic = topic ❻
new_entry.save()
return redirect('learning_logs:topic', topic_id=topic_id) ❼
# 显示空表单或指出表单数据无效。
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
我们修改 import
语句,在其中包含刚创建的 EntryForm
。
new_entry()
的定义包含形参 topic_id
,用于存储从 URL 中获得的值。渲染页面和处理表单数据时,都需要知道针对的是哪个主题,因此使用 topic_id
来获得正确的主题(见❶)。
- 在❷处,检查请求方法是 POST 还是 GET。如果是 GET 请求,就执行
if
代码块,创建一个空的EntryForm
实例(见❸)。
如果请求方法为 POST,就对数据进行处理:
- 创建一个
EntryForm
实例,使用request
对象中的 POST 数据来填充它(见❹)。然后检查表单是否有效。如果有效,就设置条目对象的属性topic
,再将条目对象保存到数据库。
调用 save()
时,传递实参 commit=False
(见❺),让 Django 创建一个新的条目对象,并将其赋给 new_entry
,但不保存到数据库中。
将 new_entry
的属性 topic
设置为在这个函数开头从数据库中获取的主题(见❻),再调用save()
且不指定任何实参。这将把条目保存到数据库,并将其与正确的主题相关联。
补充
如果表单数据有效,将表单中的数据保存为一个新的 Entry
对象,但在保存到数据库之前,先将 commit
参数设为 False
。这样可以对该对象进行修改,而不会立即保存到数据库。
在❼处,调用 redirect()
,它要求提供两个参数:要重定向到的视图和要给视图函数提供的参数。这里重定向到 topic()
,而这个视图函数需要参数 topic_id
。视图函数 topic()
渲染新增条目所属主题的页面,其中的条目列表包含新增的条目。
在视图函数 new_entry()
的末尾,我们创建了一个上下文字典,并使用模板 new_entry.html
渲染页面。这些代码将在用户刚进入页面或提交的表单数据无效时执行。
4. 模板 new_entry
模板 new_entry
类似于模板 new_topic
,如下面的代码所示:
new_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p> ❶
<p>Add a new entry:</p>
<form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'> ❷
{% csrf_token %}
{{ form.as_p }}
<button name='submit'>Add entry</button>
</form>
{% endblock content %}
在页面顶端显示主题(见❶),让用户知道自己是在哪个主题中添加条目。该主题名也是一个链接,可用于返回到该主题的主页面。
表单的实参 action
包含 URL 中的 topic_id
值,让视图函数能够将新条目关联到正确的主题(见❷)。除此之外,这个模板与模板new_topic.html
完全相同。
5. 链接到页面 new_entry
接下来,需要在显示特定主题的页面中添加到页面 new_entry
的链接:
topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topic: {{ topic }}</p>
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
</p>
<ul>
--snip--
</ul>
{% endblock content %}
我们将这个链接放在条目列表前面,因为在这种页面中,执行的最常见的操作是添加新条目。下图显示了页面 new_entry
。现在用户可添加新主题,还可在每个主题中添加任意数量的条目。请在一些主题中添加新条目,尝试使用一下页面 new_entry
。
1.3 编辑条目
下面来创建让用户能够编辑既有条目的页面。
1. URL 模式 edit_entry
这个页面的 URL 需要传递要编辑的条目的 ID。修改后的 learning_logs/urls.py
如下:
urls.py
--snip--
urlpatterns = [
--snip--
# 用于编辑条目的页面。
path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
]
在 URL(如 http://localhost:8000/edit_entry/1/)中传递的 ID 存储在形参 entry_id
中。这个 URL 模式将与其匹配的请求发送给视图函数 edit_entry()
。
2. 视图函数edit_entry()
页面 edit_entry
收到 GET 请求时,edit_entry()
将返回一个表单,让用户能够对条目进行编辑;收到 POST 请求(条目文本经过修订)时,则将修改后的文本保存到数据库:
views.py
from django.shortcuts import render, redirect
from .models import Topic, Entry
from .forms import TopicForm, EntryForm
--snip--
def edit_entry(request, entry_id):
"""编辑既有条目。"""
entry = Entry.objects.get(id=entry_id) ❶
topic = entry.topic
if request.method != 'POST':
# 初次请求:使用当前条目填充表单。
form = EntryForm(instance=entry) ❷
else:
# POST提交的数据:对数据进行处理。
form = EntryForm(instance=entry, data=request.POST) ❸
if form.is_valid():
form.save() ❹
return redirect('learning_logs:topic', topic_id=topic.id) ❺
context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'learning_logs/edit_entry.html', context)
首先导入模型 Entry
。在❶处,获取用户要修改的条目对象以及与其相关联的主题。在请求方法为 GET 时将执行的 if
代码块中,使用实参 instance=entry
创建一个 EntryForm
实例(见❷)。这个实参让 Django 创建一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并且能够编辑。
处理 POST 请求时,传递实参 instance=entry
和 data=request.POST
(见❸),让 Django 根据既有条目对象创建一个表单实例,并根据 request.POST
中的相关数据对其进行修改。然后,检查表单是否有效。如果有效,就调用 save()
且不指定任何实参(见❹),因为条目已关联到特定的主题。然后,重定向到显示条目所属主题的页面(见❺),用户将在其中看到其编辑的条目的新版本。
如果要显示表单让用户编辑条目或者用户提交的表单无效,就创建上下文字典并使用模板 edit_entry.html
渲染页面。
3. 模板edit_entry
下面来创建模板 edit_entry.html
,它与模板 new_entry.html
类似:
edit_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>
<p>Edit entry:</p>
<form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'> ❶
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Save changes</button> ❷
</form>
{% endblock content %}
在❶处,实参 action
将表单发送给函数 edit_entry()
处理。在标签 {% url %}
中,将条目 ID 作为一个实参,让视图函数edit_entry()
能够修改正确的条目对象。在❷处,将提交按钮的标签设置成 Save changes,旨在提醒用户:单击该按钮将保存所做的编辑,而不是创建一个新条目。
4. 链接到页面edit_entry
现在,需要在显示特定主题的页面中给每个条目添加到页面edit_entry
的链接:
topic.html
--snip--
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
<p>
<a href="{% url 'learning_logs:edit_entry' entry.id %}">Edit entry</a>
</p>
</li>
--snip--
将编辑链接放在了每个条目的日期和文本后面。在循环中,使用模板标签 {% url %}
根据 URL 模式 edit_entry
和当前条目的 ID 属性(entry.id
)来确定 URL。链接文本为 Edit entry,它出现在页面中每个条目的后面。下图显示了包含这些链接时,显示特定主题的页面是什么样的。
至此,“学习笔记”已具备了需要的大部分功能。用户可添加主题和条目,还可根据需要查看任何条目。在下一节,我们将实现一个用户注册系统,让任何人都可向“学习笔记”申请账户,并创建自己的主题和条目。
动手试一试
练习: 博客 新建一个 Django 项目,将其命名为 Blog。在这个项目中,创建一个名为 blogs
的应用程序,并在其中创建一个名为BlogPost
的模型。这个模型应包含 title
、text
和 date_added
等字段。为这个项目创建一个超级用户,并使用管理网站创建几个简短的帖子。创建一个主页,在其中按时间顺序显示所有的帖子。
创建两个表单,其中一个用于发布新帖子,另一个用于编辑既有的帖子。尝试填写这些表单,确认它们能够正确工作。
2. 创建用户账户
本节将建立用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。为此,我们将新建一个应用程序,其中包含与处理用户账户相关的所有功能。这个应用程序将尽可能使用 Django 自带的用户身份验证系统来完成工作。本节还将对模型 Topic
稍做修改,让每个主题都归属于特定用户。
2.1 应用程序 users
首先使用命令 startapp
创建一个名为 users
的应用程序:
(ll_env)learning_log$ python manage.py startapp users
(ll_env)learning_log$ ls
db.sqlite3 learning_log learning_logs ll_env manage.py users ❶
(ll_env)learning_log$ ls users
__init__.py admin.py apps.py migrations models.py tests.py views.py ❷
这个命令新建一个名为 users 的目录(见❶),其结构与应用程序 learning_logs
相同(见❷)。
2.2 将 users
添加到 settings.py 中
在 settings.py
中,需要将这个新的应用程序添加到 INSTALLED_APPS
中,如下所示:
settings.py
--snip--
INSTALLED_APPS = [
# 我的应用程序
'learning_logs',
'users',
# Django默认创建的应用程序
--snip--
]
--snip--
这样,Django 将把应用程序 users
包含到项目中。
2.3 包含 users
的 URL
接下来,需要修改项目根目录中的 urls.py
,使其包含将为应用程序 users
定义的 URL:
urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('', include('learning_logs.urls')),
]
我们添加了一行代码,以包含应用程序 users
中的文件 urls.py
。这行代码与任何以单词 users 打头的 URL(如 http://localhost:8000/users/login/ )都匹配。
2.4 登录页面
首先来实现登录页面。我们将使用 Django 提供的默认视图 login
,因此这个应用程序的 URL 模式稍有不同。在目录 learning_log/users/
中,新建一个名为 urls.py
的文件,并在其中添加如下代码:
urls.py
"""为应用程序users定义URL模式。"""
from django.urls import path, include
app_name = 'users' ❶
urlpatterns = [
# 包含默认的身份验证URL。
path('', include('django.contrib.auth.urls')), ❷
]
导入函数 path
和 include
,以便包含 Django 定义的一些默认的身份验证 URL。这些默认的 URL 包含具名的 URL 模式,如 'login'
和 'logout'
。我们将变量 app_name
设置成 'users'
,让 Django 能够将这些 URL 与其他应用程序的 URL 区分开来(见❶)。即便是 Django 提供的默认 URL,将其包含在应用程序 users
的文件中后,也可通过命名空间 users
进行访问。
登录页面的 URL 模式与URL http://localhost:8000/users/login/ 匹配(见❷)。这个 URL 中的单词 users 让 Django 在 users/urls.py
中查找,而单词 login 让它将请求发送给 Django 的默认视图 login
。
2.4.1 模板 login.html
用户请求登录页面时,Django 将使用一个默认的视图函数,但我们依然需要为这个页面提供模板。默认的身份验证视图在文件夹 registration 中查找模板,因此我们需要创建这个文件夹。为此,在目录 learning_log/users/
中新建一个名为 templates 的目录,再在这个目录中新建一个名为 registration 的目录。下面是模板 login.html
,应将其存储到目录 learning_log/users/templates/ registration
中:
login.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %} ❶
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action="{% url 'users:login' %}"> ❷
{% csrf_token %}
{{ form.as_p }} ❸
<button name="submit">Log in</button> ❹
<input type="hidden" name="next" ❺
value="{% url 'learning_logs:index' %}" />
</form>
{% endblock content %}
这个模板继承了 base.html
,旨在确保登录页面的外观与网站的其他页面相同。请注意,一个应用程序中的模板可继承另一个应用程序中的模板。
如果设置表单的 errors
属性,就显示一条错误消息(见❶),指出输入的用户名密码对与数据库中存储的任何用户名密码对都不匹配。
我们要让登录视图对表单进行处理,因此将实参 action
设置为登录页面的URL(见❷)。登录视图将一个表单发送给模板。在模板中,我们显示这个表单(见❸)并添加一个提交按钮(见❹)。在❺处,包含了一个隐藏的表单元素 'next'
,其中的实参 value
告诉 Django 在用户成功登录后将其重定向到什么地方。在本例中,用户将返回主页。
2.4.2 链接到登录页面
下面在 base.html
中添加到登录页面的链接,让所有页面都包含它。用户已登录时,我们不想显示这个链接,因此将它嵌套在一个 {% if %}
标签中:
base.html
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a> -
{% if user.is_authenticated %} ❶
Hello, {{ user.username }}. ❷
{% else %}
<a href="{% url 'users:login' %}">Log in</a> ❸
{% endif %}
</p>
{% block content %}{% endblock content %}
在 Django 身份验证系统中,每个模板都可使用变量 user
。这个变量有一个 is_authenticated
属性:如果用户已登录,该属性将为 True
,否则为 False
。这让你能够向已通过身份验证的用户显示一条消息,而向未通过身份验证的用户显示另一条消息。
这里向已登录的用户显示问候语(见❶)。对于已通过身份验证的用户,还设置了属性 username
。这里使用该属性来个性化问候语,让用户知道自己已登录(见❷)。在❸处,对于尚未通过身份验证的用户,显示到登录页面的链接。
2.4.3 使用登录页面
前面建立了一个用户账户,下面来登录一下,看看登录页面是否管用。请访问 http://localhost:8000/admin/,如果你依然是以管理员身份登录的,请在页眉上找到注销链接并单击它。
注销后,访问 http://localhost:8000/users/login/ 将看到类似于下图所示的登录页面。输入你在前面设置的用户名和密码,将进入索引页面。在这个主页的页眉中,显示了一条个性化问候语,其中包含你的用户名。
2.5 注销
现在需要提供一个让用户注销的途径。为此,我们将在 base.html
中添加一个注销链接。用户单击这个链接时,将进入一个确认其已注销的页面。
2.5.1 在 base.html 中添加注销链接
下面在 base.html
中添加注销链接,让每个页面都包含它。将注销链接放在 {% if user.is_authenticated %}
部分中,这样只有已登录的用户才能看到它:
base.html
--snip--
{% if user.is_authenticated %}
Hello, {{ user.username }}.
<a href="{% url 'users:logout' %}">Log out</a>
{% else %}
--snip--
默认的具名注销 URL 模式为 'logout'
。
2.5.2 注销确认页面
成功注销后,用户希望获悉这一点。因此默认的注销视图使用模板 logged_out.html
渲染注销确认页面,我们现在就来创建该模板。下面这个简单的页面确认用户已注销,请将其存储到目录 templates/registration
(login.html
所在的目录)中:
logged_out.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>You have been logged out. Thank you for visiting!</p>
{% endblock content %}
在这个页面中,不需要提供其他内容,因为 base.html
提供了到主页和登录页面的链接。
下图显示了用户单击 Log out 链接后出现的注销确认页面。这里的重点是创建能够正确工作的网站,因此几乎没有设置样式。确定所需的功能都能正确运行后,我们将设置这个网站的样式,使其看起来更专业。
2.6 注册页面
下面来创建一个页面供新用户注册。我们将使用 Django 提供的表单 UserCreationForm
,但编写自己的视图函数和模板。
2.6.1 注册页面的 URL 模式
下面的代码定义了注册页面的 URL 模式,它也包含在 users/urls.py
中:
urls.py
"""为应用程序 users 定义 URL 模式。"""
from django.urls import path, include
from . import views
app_name = 'users'
urlpatterns = [
# 包含默认的身份验证URL。
path('', include('django.contrib.auth.urls')),
# 注册页面
path('register/', views.register, name='register'),
]
我们从 users
中导入模块 views
。为何需要这样做呢?因为我们将为注册页面编写视图函数。注册页面的 URL 模式与 URL http://localhost:8000/users/register/ 匹配,并将请求发送给即将编写的函数 register()
。
2.6.2 视图函数 register()
在注册页面首次被请求时,视图函数 register()
需要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。如果注册成功,这个函数还需让用户自动登录。请在 users/views.py
中添加如下代码:
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
def register(request):
"""注册新用户。"""
if request.method != 'POST':
# 显示空的注册表单。
form = UserCreationForm() ❶
else:
# 处理填写好的表单。
form = UserCreationForm(data=request.POST) ❷
if form.is_valid(): ❸
new_user = form.save() ❹
# 让用户自动登录,再重定向到主页。
login(request, new_user) ❺
return redirect('learning_logs:index') ❻
# 显示空表单或指出表单无效。
context = {'form': form}
return render(request, 'registration/register.html', context)
首先导入函数 render()
和 redirect()
,然后导入函数 login()
,以便在用户正确填写了注册信息时让其自动登录。我们还导入了默认表单 UserCreationForm
。在函数 register()
中,检查要响应的是否是 POST 请求。如果不是,就创建一个 UserCreationForm
实例,且不给它提供任何初始数据(见❶)。
如果响应的是 POST 请求,就根据提交的数据创建一个 UserCreationForm
实例(见❷),并检查这些数据是否有效(见❸)。就本例而言,有效是指用户名未包含非法字符,输入的两个密码相同,以及用户没有试图做恶意的事情。
如果提交的数据有效,就调用表单的方法 save()
,将用户名和密码的散列值保存到数据库中(见❹)。方法 save()
返回新创建的用户对象,我们将它赋给了 new_user
。保存用户的信息后,调用函数 login()
并传入对象 request
和 new_user
,为用户创建有效的会话,从而让其自动登录(见❺)。最后,将用户重定向到主页(见❻),而主页的页眉中显示了一条个性化的问候语,让用户知道注册成功了。
在这个函数的末尾,我们渲染了注册页面:它要么显示一个空表单,要么显示提交的无效表单。
2.6.3 注册模板
下面来创建注册页面的模板,它与登录页面的模板类似。请务必将其保存到 login.html
所在的目录中:
register.html
{% extends "learning_logs/base.html" %}
{% block content %}
<form method="post" action="{% url 'users:register' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Register</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
</form>
{% endblock content %}
这里也使用了方法 as_p
,让 Django 在表单中正确地显示所有的字段,包括错误消息——如果用户没有正确地填写表单。
2.6.4 链接到注册页面
下面来添加一些代码,在用户没有登录时显示到注册页面的链接:
base.html
--snip--
{% if user.is_authenticated %}
Hello, {{ user.username }}.
<a href="{% url 'users:logout' %}">Log out</a>
{% else %}
<a href="{% url 'users:register' %}">Register</a> -
<a href="{% url 'users:login' %}">Log in</a>
{% endif %}
--snip--
现在,已登录的用户看到的是个性化的问候语和注销链接,而未登录的用户看到的是注册链接和登录链接。请尝试使用注册页面创建几个用户名各不相同的用户账户。
下一节会将一些页面限制为仅让已登录的用户访问,还将确保每个主题都归属于特定用户。
注意
这里的注册系统允许用户创建任意数量的账户。有些系统要求用户确认其身份:发送一封确认邮件,用户回复后账户才生效。通过这样做,这些系统会比本例的简单系统生成更少的垃圾账户。然而,学习创建应用程序时,完全可以像这里所做的那样,使用简单的用户注册系统。
3. 让用户拥有自己的数据
用户应该能够输入其专有的数据,因此我们将创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。
本节将修改模型 Topic
,让每个主题都归属于特定用户。这也将影响条目,因为每个条目都属于特定的主题。我们先来限制对一些页面的访问。
欢迎关注我公众号:AI悦创,有更多更好玩的等你发现!
公众号:AI悦创【二维码】
AI悦创·编程一对一
AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh
C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh
方法一:QQ
方法二:微信:Jiabcdefh
- 0
- 0
- 0
- 0
- 0
- 0