--snip-- 1 def topic(request, topic_id): """显示单个主题及其所有的条目""" 2 topic = Topic.objects.get(id=topic_id) 3 entries = topic.entry_set.order_by('-date_added') 4 context = {'topic': topic, 'entries': entries} 5 return render(request, 'learning_logs/topic.html', context)
这是第一个除request对象外还包含另一个形参的视图函数。这个函数接受正则表达式 (?P\d+)捕获的值,并将其存储到topic_id中(见1)。在2处,我们使用get()来获取 指定的主题,就像前面在Django shell中所做的那样。在3处,我们获取与该主题相关联的条目, 并将它们按date_added排序:date_added前面的减号指定按降序排列,即先显示最近的条目。我们将主题和条目都存储在字典context中(见4),再将这个字典发送给模板topic.html(见5)。
注意
2处和3处的代码被称为查询,因为它们向数据库查询特定的信息。在自己的项目中编 写这样的查询时,先在Django shell中进行尝试大有裨益。相比于编写视图和模板,再在 浏览器中检查结果,在shell中执行代码可更快地获得反馈。
3. 模板
这个模板需要显示主题的名称和条目的内容;如果当前主题不包含任何条目,我们还需向用 户指出这一点:
topic.html
{% extends 'learning_logs/base.html' %} {% block content %} 1 <p>Topic: {{ topic }}</p> <p>Entries:</p> 2 <ul> 3 {% for entry in entries %} <li> 4 <p>{{ entry.date_added|date:'M d, Y H:i' }}</p> 5 <p>{{ entry.text|linebreaks }}</p> </li> 6 {% empty %} <li> There are no entries for this topic yet. </li> {% endfor %} </ul> {% endblock content %}
像这个项目的其他页面一样,这里也继承了base.html。接下来,我们显示当前的主题(见1), 它存储在模板变量{{ topic }}中。为什么可以使用变量topic呢?因为它包含在字典context中。 接下来,我们开始定义一个显示每个条目的项目列表(见2),并像前面显示所有主题一样遍历 条目(见3)。
每个项目列表项都将列出两项信息:条目的时间戳和完整的文本。为列出时间戳(见4), 我们显示属性date_added的值。在Django模板中,竖线(|)表示模板过滤器——对模板变量的值 进行修改的函数。过滤器date: 'M d, Y H:i'以这样的格式显示时间戳:January 1, 2015 23:00。 接下来的一行显示text的完整值,而不仅仅是entry的前50个字符。过滤器linebreaks(见5)将 包含换行符的长条目转换为浏览器能够理解的格式,以免显示为一个不间断的文本块。在6处, 我们使用模板标签{% empty %}打印一条消息,告诉用户当前主题还没有条目。
4. 将显示所有主题的页面中的每个主题都设置为链接
在浏览器中查看显示特定主题的页面前,我们需要修改模板topics.html,让每个主题都链接 到相应的网页,如下所示:
topics.html
--snip-- {% for topic in topics %} <li> <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> </li> {% empty %} --snip--
我们使用模板标签url根据learning_logs中名为topic的URL模式来生成合适的链接。这个 URL模式要求提供实参topic_id,因此我们在模板标签url中添加了属性topic.id。现在,主题列 表中的每个主题都是一个链接,链接到显示相应主题的页面,如http://localhost:8000/topics/1/。 如果你刷新显示所有主题的页面,再单击其中的一个主题,将看到类似于图18-5所示的页面。
18.5 小结
在本章中,你首先学习了如何使用Django框架来创建Web应用程序。你制定了简要的项目规 范,在虚拟环境中安装了Django,创建了一个项目,并核实该项目已正确地创建。你学习了如何 创建应用程序,以及如何定义表示应用程序数据的模型。你学习了数据库,以及在你修改模型后, Django可为你迁移数据库提供什么样的帮助。你学习了如何创建可访问管理网站的超级用户,并 使用管理网站输入了一些初始数据。
你还探索了Django shell,它让你能够在终端会话中处理项目的数据。你学习了如何定义URL、 创建视图函数以及编写为网站创建网页的模板。最后,你使用了模板继承,它可简化各个模板的 结构,并使得修改网站更容易。
在第19章,我们将创建对用户友好而直观的网页,让用户无需通过管理网站就能添加新的主 题和条目,以及编辑既有的条目。我们还将添加一个用户注册系统,让用户能够创建账户和自己 的学习笔记。让任意数量的用户都能与之交互,是Web应用程序的核心所在。
第 19 章
用户账户
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 1 class TopicForm(forms.ModelForm): class Meta: 2 model = Topic 3 fields = ['text'] 4 labels = {'text': ''}
我们首先导入了模块forms以及要使用的模型Topic。在1处,我们定义了一个名为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-- # 用于添加新主题的网页 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 --snip-- def new_topic(request): """添加新主题""" 1 if request.method != 'POST': # 未提交数据:创建一个新表单 2 form = TopicForm() else: # POST提交的数据,对数据进行处理 3 form = TopicForm(request.POST) 4 if form.is_valid(): 5 form.save() 6 return HttpResponseRedirect(reverse('learning_logs:topics')) 7 context = {'form': form} 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将包含 用户提交的信息。
要将提交的信息保存到数据库,必须先通过检查确定它们是有效的(见)。函数is_valid() 核实用户填写了所有必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的 字段类型一致(例如,字段text少于200个字符,这是我们在第18章中的models.py中指定的)。这 种自动验证避免了我们去做大量的工作。如果所有字段都有效,我们就可调用save()(见), 将表单中的数据写入数据库。保存数据后,就可离开这个页面了。我们使用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> 1 <form action="{% url 'learning_logs:new_topic' %}" method='post'> 2 {% csrf_token %} 3 {{ form.as_p }} 4 <button name="submit">add topic</button> </form> {% endblock content %}