跟书《python编程:从入门到实践》,学习用Django编写名为“学习笔记”的Web应用程序。
Web应用程序的核心是让任何用户都能够注册账户并能够使用它,不管用户身处何方。我们可以创建一些表单,让用户能够添加主题和条目,以及编辑既有的条目。
让用户能够输入数据
先来添加几个页面,让用户能够输入数据。可以让用户能够添加新主题、添加新条目以及编辑既有条目,但不能通过管理网站来输入,因为只有超级用户才可以这样做。
添加新主题
首先来让用户能够添加新主题。创建基于表单的页面的方法几乎与前面创建网页一样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差别是,需要导入包含表单的模块forms.py。
- 用于添加主题的表单:
在Django中,创建表单的最简单方式是使用ModelForm。创建一个名为forms.py的文件,将其存储到models.py所在的目录中,并在其中编写第一个表单。
# vim learning_logs/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生成标签。
- URL模式
new_topic
:
# vim learning_logs/urls.py
"""定义learning_logs的URL模式"""
from django.urls import path, re_path
from . import views
app_name='learning_logs'
urlpatterns = [
# 主页
path('', views.index, name='index'),
# 显示所有的主题
path('topics/', views.topics, name='topics'),
# 特定主题的详细页面
re_path(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# 用于添加新主题的页面
path('new_topic/', views.new_topic, name='new_topic'),
]
这个URL模式会将请求交给视图函数new_topic()
。
- 视图函数
new_topic()
:
函数new_topic()
需要处理两种情形:刚进入new_topic
网页,它应显示一个空表单;对提交的表单数据进行处理,并将用户重定向到网页topics。
# vim learning_logs/views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Topic
from .forms import TopicForm
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)
导入了HttpResponseRedirect
类和刚才创建的表单TopicForm
,用户提交主题后我们将使用HttpResponseRedirect
类将用户重定向到网页topics。函数reverse()
根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL。
首先判断请求方法是否是POST——如果请求方法不是POST,返回一个空表单;如果请求方法是POST,将使用用户输入的数据(存储在request.POST中)创建一个TopicForm
实例,这样对象form将包含用户提交的信息。
然后要将提交的信息保存到数据库,必须先通过检查确定它们是有效的。函数is_valid()
核实用户填写了所有必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的字段类型一致。如果所有字段都有效,我们就调用save()
将表单中的数据写入数据库。
最后使用reverse()
获取页面topics的URL,并将其传递给HttpResponseRedirect()
,后者将用户的浏览器重定向到页面topics。在页面topics中,用户将在主题列表中看到他刚输入的主题。
- GET请求和POST请求:
创建Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求。处理所有表单时,我们都将指定使用POST方法。
函数new_topic()
将请求对象作为参数。用户初次请求该网页时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。根据请求的类型,我们可以确定用户请求的是空表单(GET请求)还是要求对填写好的表单进行处理(POST请求)。
- 模板
new_topic
:
# vim learning_logs/templates/learning_logs/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 %}
来防止攻击者利用表单来获得对服务器未经授权的访问(这种攻击被称为跨站
请求伪造)。接着显示表单,只需包含模板变量{ { form.as_p }}
,就可让Django自动创建显示表单所需的全部字段。修
饰符as_p
让Django以段落格式渲染所有表单元素。
- 链接到页面
new_topic
:
# vim learning_logs/templates/learning_logs/topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{
{ topic }}</a>
</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
{% endblock content %}
- 访问网页:
添加新条目
现在已经可以添加新主题了,但是还不能添加新条目,再次定义URL,编写视图函数和模板,并链接到添加新条目的网页。
- 用于添加新条目的表单:
创建一个与模型Entry相关联的表单。
# vim learning_logs/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': ''}
wgdgets = {
'text': forms.Textarea(attrs={
'cols': 80})}
除了导入Topic外,还需要导入Entry。新类EntryForm继承了forms.ModelForm,它包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。
后面定义了属性widgets
,小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。通过设置属性widgets
,可覆盖Django选择的默认小部件。通过让Django使用forms.Textarea,我们定制了字段'text'
的输入小部件,将文本区域的宽度设置为80列,而不是默认的40列。
- URL模式
new_entry
:
在用于添加新条目的页面的URL模式中,需要包含实参topic_id ,因为条目必须与特定的主题相关联。
# vim learning_logs/urls.py
"""定义learning_logs的URL模式"""
from django.urls import path, re_path
from . import views
app_name='learning_logs'
urlpatterns = [
# 主页
path('', views.index, name='index'),
# 显示所有的主题
path('topics/', views.topics, name='topics'),
# 特定主题的详细页面
re_path(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# 用于添加新主题的页面
path('new_topic/', views.new_topic, name='new_topic'),
# 用于添加新条目的页面
re_path(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
]
这个URL模式与形式为http://localhost:8000/new_entry/id/
的URL匹配,其中id是一个与主题ID匹配的数字。代码(?P<topic_id>\d+)
捕获一个数字值,并将其存储在变量topic_id
中。请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry()
。
- 视图函数
new_entry()
:
视图函数new_entry()
与视图函数new_topic()
比较类似。
# vim learning_logs/views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Topic
from .forms import TopicForm, EntryForm
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(re