第 19 章 用户帐号
Posted (野生程序员)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第 19 章 用户帐号相关的知识,希望对你有一定的参考价值。
Web应用程序的核心是让任何用户都能够注册并能够使用它,不管用户身处何方。在本章中,我们将创建一些表单,让用户能够添加主题和条目,以及编辑既有的条目。我们还将学习Django如何防范对基于表单的网页发起的常见攻击,这让我们无需花太多时间考虑确保应用程序安全的问题。
然后,我们将实现一个用户身份验证系统。我们将创建一个注册页面,供用户创建账户,并让有些页面只能供已登录的用户访问。接下来,我们将修改一些视图函数,使得用户只能看到自己的数据。我们将学习如何确保用户数据的安全。
19.1 让用户能够输入数据
建立用于创建用户账户的身份验证系统之前,我们先来添加几个页面,让用户能够输入数据。我们将让用户能够添加新主题、添加新条目以及编辑既有条目。
当前,只有超级用户能够通过管理网站输入数据。我们不想让用户与管理网站交互,因此我们将使用Django的表单创建工具来创建让用户能够输入的页面。
19.1.1 添加新主题 首先来让用户能够添加新主题。创建基于表单的页面的方法几乎与前面创建网页一样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差别是,需要导入包含表单的模块forms.py.
1.用于添加主题的表单
让用户输入并提交信息的页面都是表单,哪怕它看起来不像表单。用户输入信息时,我们需要进行验证,确认提供的信息是正确的数据类型,且不是恶意的信息,如中断服务器的代码。然后,我们再对这些有效信息进行处理,并将其保存到数据库的合适地方。这些工作很多都是Django自动完成的。
在Django中,创建表单的最简单方式是使用ModelForm,它根据我们在第18章定义的模型中的信息自动创建表单。创建一个名为forms.py的文件,将其存储到models.py所在的目录中,并在其中编写我们的第一个表单。
forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm): --(1)
class Meta:
model = Topic --(2)
fields = ["text"] --(3)
labels = {"text":""} --(4)
我们首先导入了模块forms以及要使用的模型Topic。在(1)处,我们定义了一个名为TopicForm的类,它继承了forms.ModelForm。
最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。在(2)处,我们根据模型Topic创建一个表单,该表单只包含字段text(见(3))。(4)处的代码让Django不要为字段text生成标签。
2.URL模式new_topic
这个新网页的URL应简短而具有描述行,因此当用户要添加新主题时,我们将切换到http://localhost:8000/new_topic/。下面是网页new_topic的URL模式,我们将其添加到learning_logs/urls.py中:
urls.py
\'\'\'定义learning_logs的URL模式\'\'\'
from django.conf.urls import url
from . import views
urlpatterns = [
#主页
url(r\'^$\',views.index,name=\'index\'),
#显示所有的主题
url(r\'^topics/$\', views.topics,name=\'topics\'),
#特定主题的详细页面
url(r\'^topics/(?P<topic_id>\\d+)/$\',views.topic,name=\'topic\'),
#用于添加新主题的网页
url(r\'^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
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic
from .forms import TopicForm
# Create your views here.
def index(request):
"""学习笔记的主页"""
return render(request,\'learning_logs/index.html\')
def topics(request):
"""显示所有的主题"""
topics = Topic.objects.order_by("date_added")
context = {\'topics\':topics}
return render(request,\'learning_logs/topics.html\',context)
def topic(request,topic_id):
"""显示单个主题及其所有的条目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by(\'-date_added\')
context = {\'topic\':topic,\'entries\':entries}
return render(request, \'learning_logs/topic.html\',context)
def new_topic(request):
"""添加新主题"""
if request.method != "POST: (1)
#未提交数据:创建一个新表单
form = TopicForm() (2)
else:
"""POST提交的数据,对数据进行处理"""
form = TopicForm(request.POST) (3)
if form.is_valid(): (4)
form.save() (5)
return HttpResponseRedirect(reverse(\'learning_logs:topics\')) (6)
context = {\'form\':form} (7)
return render(request,"learning_logs/new_topic.html\',context)
我们导入了HttpResponseRedirect类,用户提交主题后我们将使用这个类将用户重定向到网页topics。函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL.我们还导入了刚才创建的表单TopicForm。
4.GET请求和POST请求
创建Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求。处理所有表单时,我们都将指定使用POST方法。还有一些其他类型的请求,但这个项目没有使用。
函数new_topic()将请求对象作为参数。用户初次请求该网页时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。根据请求的类型,我们可以确定用户请求的是空表单(GET请求)还是要求对填写好的表单进行处理(POST请求).
(1)处的测试确定请求方法是GET还是POST。如果请求方法不是POST,请求就可能是GET,因此我们需要返回一个空表单(即便请求是其他类型的,返回一个空表单也不会有任何问题)。我们创建一个TopicForm实例(见2),将其存储在变量form中,再通过上下文字典将这个表单发送给模板(见(7))。由于实例化TopicForm时我们没有指定任何实参,Django将创建一个可供用户填写的空表单。
如果请求方法是POST,将执行else代码块,对提交的表单进行数据处理。我们使用用户输入的数据(它们存储在request.POST中)创建一个TopicForm实例(见(3)),这样对象form将包含用户提交的信息。
要将提交的信息保存到数据库,必须先通过检查确定它们是有效的(见(4))。函数is_valid()核实用户填写了所有必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的字段类型一致(例如,字段text少于200个字符,这是我们在第18章中的models.py中指定的)。这种自动验证避免了我们去做大量的工作。如果所有字段都有效,我们就可调用save()(见(5)),将表单中的数据写入数据库。保存数据后,就可离开这个页面了。我们使用reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect()(见(6)),后者将用户的浏览器重定向到页面topics。在页面topics中,用户将在主题列表中看到他刚输入的主题。
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"> --(1)
{% csrf_token %} --(2)
{{ form.as_p }} --(3)
<button name="submit">add topic</button> --(4)
</form>
{% endblock content %}
这个模板继承了base.html,因此其基本结构与项目"学习笔记"的其他页面相同。在(1)处,我们定义了一个HTML表单。实参action告诉服务器将提交的表单数据发送到哪里,这里我们将它发回给视图函数new_topic()。实参methon让浏览器以POST请求的方式提交数据。
Django使用模板标签{% csrf_token %}(见2)来防止攻击者利用表单来获得对服务器未经授权的访问(这种攻击被称为跨站请求伪造).在(3)处,我们显示表单,从中可知Django使得完成显示表单等任务有多简单:我们只需包含模板变量{{ form.as_p }},就可让Django自动创建显示表单所需的全部字段。
修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方式。
Django不会为并表单创建提交按钮,因此我们在(4)处定义了一个这样的按钮。
6.链接到页面new_topic
接下来,我们在页面topics中添加一个页面new_topic的链接:
topics.html
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic
from .forms import TopicForm
# Create your views here.
def index(request):
"""学习笔记的主页"""
return render(request,\'learning_logs/index.html\')
def topics(request):
"""显示所有的主题"""
topics = Topic.objects.order_by("date_added")
context = {\'topics\':topics}
return render(request,\'learning_logs/topics.html\',context)
def topic(request,topic_id):
"""显示单个主题及其所有的条目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by(\'-date_added\')
context = {\'topic\':topic,\'entries\':entries}
return render(request, \'learning_logs/topic.html\',context)
def new_topic(request):
"""添加新主题"""
if request.method != "POST":
#未提交数据:创建一个新表单
form = TopicForm()
else:
"""POST提交的数据,对数据进行处理"""
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse(\'learning_logs:topics\'))
context = {\'form\':form}
return render(request,"learning_logs/new_topic.html\',context)
19.1.2 添加新条目
现在用户可以添加新主题了,但它们还想添加新条目。我们将再次定义URL,编写视图函数和模板,并链接到添加新条目的网页。但在此之前,我们需要在forms.py中再添加一个类。
1.用于添加新条目的表单
我们需要创建一个与模型Entry相关联的表单,但这个表单的定制程度比TopicForm要高些:
forms.py
from django import forms
from .models import Topic,Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ["text"]
labels = {"text": ""}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ["text"]
labels = {"text":""} --(1)
widgets = {\'text\':forms.Textarea(attrs={\'cols\':80})} --(2)
我们首先修改了import语句,使其除了导入Topic外,还导入Entry。新类EntryForm继承了forms.ModelForm,它包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。这里也给字段\'text\'指定了一个空标签。
在(2)处,我们定义了属性widgets。小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。通过设置属性widgets,可覆盖Django选择的默认小部件。通过让Django使用forms.Textarea,我们定制了字段"text"的输入的小部件,将文本区域的宽度设置为80列,而不是默认的40列。这给用户提供了足够的空间,可以编写有意义的条目。
2.URL模式new_entry
在用于添加新条目的页面的URL模式中,需要包含实参topic_id,因为条目必须与特定的主题相关联。该URL模式如下,我们将它添加到了learning_logs/urls.py中:
ruls.py
\'\'\'定义learning_logs的URL模式\'\'\'
from django.conf.urls import url
from . import views
urlpatterns = [
#主页
url(r\'^$\',views.index,name=\'index\'),
#显示所有的主题
url(r\'^topics/$\', views.topics,name=\'topics\'),
#特定主题的详细页面
url(r\'^topics/(?P<topic_id>\\d+)/$\',views.topic,name=\'topic\'),
#用于添加新主题的网页
url(r\'^new_topic/$\', views.new_topic,name=\'new_topic\'),
#用于添加新条目的页面
url(r\'^new_entry/(?P<topic_id\\d+/$\',views.new_entry,name=\'new_entry\'),
]
这个URL模式与形式为heet://localhost:8000/new_entry/id/的URL匹配,其中id是一个与主题ID匹配的数字。代码(?P<topic_id>\\d+)捕获一个数字值,并将其存储在变量topic_id中。请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry().
3.视图函数new_entry()
视图函数new_entry()与函数new_topic()很像:
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic
from .forms import TopicForm,EntryForm
# Create your views here.
def index(request):
"""学习笔记的主页"""
return render(request,\'learning_logs/index.html\')
def topics(request):
"""显示所有的主题"""
topics = Topic.objects.order_by("date_added")
context = {\'topics\':topics}
return render(request,\'learning_logs/topics.html\',context)
def topic(request,topic_id):
"""显示单个主题及其所有的条目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by(\'-date_added\')
context = {\'topic\':topic,\'entries\':entries}
return render(request, \'learning_logs/topic.html\',context)
def new_topic(request):
"""添加新主题"""
if request.method != "POST":
#未提交数据:创建一个新表单
form = TopicForm()
else:
"""POST提交的数据,对数据进行处理"""
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse(\'learning_logs:topics\'))
def new_entry(request,topic_id):
"""在特定的主题中添加新条目"""
topic = Topic.objects.get(id=topic_id) (1)
if request.method != "POST": (2)
#未提交数据,创建一个空表单
form = EntryForm() (3)
else:
#POST提交的数据,对数据进行处理
form = EntryForm(data=request.POST) (4)
if form.is_valid():
new_entry = form.save(commit=False) (5)
new_entry.topic = topic (6)
new_entry.save()
return HttpResponseRedirect(reverse(\'learning_logs:topic\',args=[topic_id])) (7)
context = {\'topic\':topic,\'form\':form}
return render(request,"learning_logs/new_entry.html\',context)
我们修改了import语句,在其中包含了刚创建的EntryForm。new_entry()的定义包含形参topic_id,用于存储从URL中获得的值。渲染页面以及处理表单数据时,都需要知道针对的时哪个主题,因此我们使用topic_id来获得正确的主题。
在(2)处,我们检查请求方法是POST还是GET.如果是GET请求,将执行if代码块:创建一个空的EntryForm实例(见(3))。如果请求的方法为POST,我们就对数据进行处理:创建一个EntryForm实例,使用request对象中的POST数据来填充它(见(4));在检查是否有效,如果有效,就设置条目对象的属性topic,再将条目对象保存到数据库。
调用save()时,我们传递了实参commit=False(见(5)),让Django创建一个新的条目对象,并将其存储到new_entry中,但不将它保存到数据库中。我们将new_entry的属性topic设置为这个函数开头从数据库中获取的主题,然后调用save(),且不指定任何实参。这将把条目保存到数据库,并将其与正确的主题相关联。
在(7)处,我们将用户重定向到显示相关主题的页面。调用reverse()时,需要提供两个实参:要根据它来生成URL的URL模式的名称;列表args,其中包含要包含在URL中的所有实参。这里,列表args只有一个元素——topic_id。接下来,调用HttpResponseRedirect()将用户重定向到显示新增条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。
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> --(1)
<p>Add a new entry:</p>
<form action="{% url \'learning_logs:new_entry\' topic.id %} method=\'post\'> —(2)
{% csrf_token %}
{{ form.as_p }}
<buttom name="submit">add entry</button>
</form>
{% endblock content %}
我们在页面顶端显示了主题(见(1)),让用户知道他是在哪个主题中添加条目;该主题名也是一个链接,可用于返回到该主题的主页面。
表单的实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题(见(2))。除此之外,这个模板与模板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>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:\'M d,Y H:i\'}}</p>
<p>{{ entry.text|linebreaks }}</p>
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
我们在显示条目前添加链接,因为在这种页面中,执行的最常见的操作是添加新条目。
19.1.3 编辑条目
下面来创建一个页面,让用户能够编辑既有的条目。
1.URL模式edit_entry
这个页面的URL需要传递要编辑的条目的ID.修改后的learning_logs/ruls.py如下:
urls.py
\'\'\'定义learning_logs的URL模式\'\'\'
from django.conf.urls import url
from . import views
urlpatterns = [
#主页
url(r\'^$\',views.index,name=\'index\'),
#显示所有的主题
url(r\'^topics/$\', views.topics,name=\'topics\'),
#特定主题的详细页面
url(r\'^topics/(?P<topic_id>\\d+)/$\',views.topic,name=\'topic\'),
#用于添加新主题的网页
url(r\'^new_topic/$\', views.new_topic,name=\'new_topic\'),
#用于添加新条目的页面
url(r\'^new_entry/(?P<topic_id\\d+/$\',views.new_entry,name=\'new_entry\'),
#用于编辑条目的页面
url(r\'^edit_entry/(?P<entry_id>\\d+)/$\',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的请求(条目文本经过修订)时,它将修改后的文本保存到数据库中:
"""显示单个主题及其所有的条目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by(\'-date_added\')
context = {\'topic\':topic,\'entries\':entries}
return render(request, \'learning_logs/topic.html\',context)
def new_topic(request):
"""添加新主题"""
if request.method != "POST":
#未提交数据:创建一个新表单
form = TopicForm()
else:
"""POST提交的数据,对数据进行处理"""
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse(\'learning_logs:topics\'))
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 HttpResponseRedirect(reverse(\'learning_logs:topic\',args=[topic_id]))
def edit_entry(request,entry_id):
#编辑既有条目
entry = Entry.objects.get(id=entry.id) (1)
topic = entry.topic
if request.method != "POST":
#初次请求,使用当前条目填充表单
form = EntryForm(instance=entry) (2)
else:
#POST提交的数据,对数据进行处理
form = EntryForm(instance=entry,data=request.POST) (3)
if form.is_valid():
form.save() (4)
return HttpResponseRedirect(reverse(\'learning_logs:topic\',args=[topic.id])) (5)
context = {\'entry\':entry,\'topic\':topic,\'form\':form}
return render(request,"learning_logs/edit_entry.html\',context)
我们首先需要导入模型Entry。在(1)处,我们获取用户要修改的条目对象,以及与该条目相关联的主题。在请求方法为GET时将执行的if代码块中,我们使用实参instance=entry创建一个EntryForm实例。这个实参让Django创建一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并能够编辑它们。
处理POST请求时,我们传递实参instance=entry和data=request.POST(见(3)),让Django根据既有条目对象创建一个表单实例,并根据request.POST中的相关数据对其进行修改。然后,我们检查表单是否有效,如果有效,就调用save(),且不指定任何实参(见(4))。接下来,我们重定向到显示条目所属主题的页面,用户将在其中看到其编辑的条目的新版本。
3.模板edit_entry
下面是模板edit_entry.html,它与模板new_entry.html类似:
edit_entry.html
{% entends "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 %}
在(1)处,实参action将表单发回给函数edit_entry()进行处理。在标签{% url %}中,我们将条目ID作为一个实参,让视图对象能够修改正确的条目对象。我们将提交按钮命名为save changes,以提醒用户:单击该按钮将保存所做的编辑,而不是创建一个新条目。
4.链接到页面edit_entry
现在,在显示特定主题的页面中,需要给每个条目添加到页面edit_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>
{% 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>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
我们将编辑链接放在每个条目的日期和文本后面。在循环中,我们使用模板标签{% url %}根据URL模式edit_entry和当前条目的ID属性(entry.id)来确定URL.链接文本为"edit entry",它出现在页面中每个条目的后面。
至此,“学习笔记”已具备了需要的大部分功能。用户可添加主题和条目,还可根据需要查看任何一组条目。在下一节,我们将实现一个用户注册系统,让任何人都可向“学习笔记”申请账户,并创建自己的主题和条目。
19.2 创建用户账户
在这一节,我们将建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。我们将创建一个新的应用程序,其中包含与处理用户账户相关的所有功能。我们还将对模型Topic稍作修改,让每个主题都归属于特定用户。
19.2.1 应用程序users
我们首先使用命令startapp来创建一个名为users的应用程序:
(11_env)learning_log$ python3 manage.py startapp users
(11_env)learning_log$ ls
11_env db.sqlite3 learning_log learning_logs manage.py users
(11_env)learning_log$ ls users
admin.py apps.py __init__.py migrations models.py tests.py views.py
这个命令新建一个名为users的目录(见(1)),其结构与应用程序learning_logs相同。
1.将应用程序users添加到settings.py中
在settings.py中,我们需要将这个新的应用程序添加到INSTALLED_APPS中,如下所示:
settings.py
--snip--
INSTALLED_APPS = [
\'django.contrib.admin\',
\'django.contrib.auth\',
\'django.contrib.contenttypes\',
\'django.contrib.sessions\',
\'django.contrib.messages\',
\'django.contrib.staticfiles\',
#我的应用程序
"learning_logs",
\'users\',
]
这样,Django将把应用程序users包含到项目中。
2.包含应用程序users的URL
接下来,我们需要修改项目根目录中的urls.py,使其包含我们将为应用程序users定义的URL:
urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r\'^admin/\', include(admin.site.urls)),
url(r\'users/\',include(\'users.urls\',namespace=\'users\')),
url(r\'\', include(\'learning_logs.urls\',namespace=\'learning_logs\')),
]
我们添加了一行代码,以包含应用程序users中的文件urls.py。这行代码与任何以单词users打头的URL(如http://localhost:8000/users/login/)都匹配。我们还创建了命名空间\'users\',以便将应用程序learning_logs的URL同应用程序users的URL区分开来。
19.2.2 登录页面
我们首先来实现登录页面的功能。为此,我们将使用Django提供的默认登录视图,因此URL模式会稍有不同。在目录learning_log/users/中,新建一个名为urls.py的文件,并在其中添加如下代码:
urls.py
"""为应用程序users定义URL模式"""
from django.conf.urls import url
from django.contrib.auth.views import login (1)
from . import views
urlpatterns = [
#登录页面
url(r\'^login/$\',login,{\'template_name\':\'users/login.html\'},name=\'login\'), (2)
]
我们首先导入了默认视图login(见(1))。登录页面的URL模式与URL http://localhost:8000/users/login/匹配(见2).这个URL中的单词users让Django在users/urls.py中查找,而单词login让它将请求发送给Django默认视图login(请注意,视图参数为lonin,而不是views.login)。鉴于我们没有编写自己的视图函数,我们传递了一个字典,告诉Django去哪里查找我们将编写的模板。这个模板包含在应用程序users而不是learning_logs中。
1.模板login.html
用户请求登录页面时,Django将使用其默认视图login,但我们依然需要为这个页面提供模板。为此,在目录learning_log/users/中,创建一个名为templates的目录,并在其中创建一个名为users的目录。以下是模板login.html,我们将其存储到learning_log/users/templates/users/中:
login.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %} (1)
<p>Your username and password didn\'t match. Please try again.</P>
{% endif %}
<form method="post" action="{% url \'users:login\' %}> (2)
{% csrf_token %}
{{ form.as_p }} (3)
<button name="submit">log in</button> (4)
<input type="hidden" name="next" value="{% url "learning_logs:index" %}" /> (5)
</form>
{% endblock content %}
这个模板继承了base.html,旨在确保登录页面的外观与网站的其他页面相同。请注意,一个应用程序中的模板可能继承另一个应用程序中的模板。
如果表单的errors属性被设置,我们就显示一条错误消息(见(1)),指出输入的用户名--密码对与数据库中存储的任何用户名--密码对都不匹配。
我们要让登录视图处理表单,因此将实参action设置为登录页面的URL(见(2))。登录视图将一个表单发送给模板,在模板中,我们显示这个表单(见(3))并添加一个提交按钮(见(4))。在(5)处,我们包含了一个隐藏的表单元素——\'next\',其中的实参value告诉Django在用户成功登录后将其重定向到什么地方——在这里是主页。
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 %} (1)
Hello,{{ user.username }}. (2)
{% else %}
<a href="{% url \'users:login\' %}">log in</a> (3)
{% endif %}
</p>
{% block content %}{% endblock content %}
在Django身份验证系统中,每个模板都可使用user,这个变量有一个is_authenticated属性:如果用户已登录,该属性将为True,否则为False。这让我们能够向已通过验证的用户显示一条消息,而向未通过身份验证的用户显示另一条消息。
在这里,我们向已登录的用户显示一条问候语(见(1))。对于已通过身份验证的用户,还设置了属性username,我们使用这个属性来个性化问候语,让用户知道他已登录(见(2))。在(3)处,对于还未通过身份验证的用户,我们再提示一个到登录页面的链接。
3.使用登录页面
前面建立了一个用户账户,下面来登录一下,看看登录页面是否管用。请访问http://localhost:8000/admin/,如果我们依然是以管理员的身份登录的,请在页眉上找到注销链接并单击它。
注销后,访问http://localhost:8000/users/login/,我们将看到类似于下图所示的登录页面。输入我们前面设置的用户名和密码,将进入页面index。在这个主页的页眉中,显示了一条个性化问候语,其中包含你的用户名。
19.2.3 注销
现在需要提供一个让用户注销的途径。我们不创建用于注销的页面,而让用户只需要单击一个链接就能注销并返回到主页。为此,我们将为注销链接定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销链接。
1.注销URL
下面的代码为注销定义了URL模式,该模式与URL http://locallwst:8000/users/logout/匹配。修改后的users/urls.py如下:
urls.py
"""为应用程序users定义URL模式"""
from django.conf.urls import url
from django.contrib.auth.views import login
from . import views
urlpatterns = [
#登录页面
url(r\'^login/$\',login,{\'template_name\':\'users/login.html\'},name=\'login\'),
#注销
url(r\'^logout/$\',views.logout_view,name=\'logout\'),
]
这个URL模式将请求发送给函数logout_view()。这样给这个函数命名,旨在将其与我们将在其中调用的函数logout()区分开来(请确保我们修改的时users/urls.py,而不是learning_log/urls.py).
2.视图函数logout_view()
函数logout_view()很简单:只是导入Django函数logout(),并调用它,再重定向到主页。请打开users/views.py,并输入下面的代码:
views.py
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#从Django核心解析器中导入模块
from django.contrib.auth import logout (1)
def logout_view(request):
"""注销用户"""
logout(request) (2)
return HttpResponseRedirect(reverse(\'learning_logs:index\')) (3)
我们从django.contrib.auth中导入了函数logout()(见(1)).在(2)处,我们调用了函数logout(),它要求将request对象作为实参。然后,我们重定向到主页(见(3)).
3.链接到注销视图
现在我们需要添加一个注销链接。我们在base.html中添加这种链接,让每个页面都包含它;我们将它放在标签{% if user.is_authenticated %}中,使得仅当用户登录后才能看到它:
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 }}.
<a href="{% url \'users:logout\' %}">log out</a>
{% else %}
<a href="{% url \'users:login\' %}">log in</a>
{% endif %}
</p>
{% block content %}{% endblock content %}
图19-5显示了用户登录后看到的主页。这里的重点是创建能够正确工作的网站,因此没有设置任何样式。确定所需的功能都能正确运行后,我们将设置这个网站的样式,使其看起来更专业。
19.2.4 注册页面
下面来创建一个让新用户能够注册的页面。我们将使用Django提供的表单UserCreationForm,但编写自己的视图函数和模板。
1.注册页面的URL模式
下面的代码定义了注册页面的URL模式,它也包含在users/urls.py中:
urls.py
"""为应用程序users定义URL模式"""
from django.conf.urls import url
from django.contrib.auth.views import login
from . import views
urlpatterns = [
#登录页面
url(r\'^login/$\',login,{\'template_name\':\'users/login.html\'},name=\'login\'),
#注销
url(r\'^logout/$\',views.logout_view,name=\'logout\'),
#注册页面
url(r\'^register/$\',views.register,name=\'register\'),
]
这个模式与URL http://localhost:8000/users/register/匹配,并将请求发送给我们即将编写的函数register()。
2.视图函数register()
在注册页面首次被请求时,视图函数register()需要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。如果注册成功,这个函数还需让用户自动登录。请在users/views.py中添加如下代码:
views.py
from django.shortcuts import render 以上是关于第 19 章 用户帐号的主要内容,如果未能解决你的问题,请参考以下文章
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#从Django核心解析器中导入模块
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm
def logout_view(request):
"""注销用户"""
logout(request)
return HttpResponseRedirect(reverse(\'learning_logs:i