参考书籍:《Python编程从入门到实践》—Eric Matthes
目标:
-
创建一些表单,让用户能够添加主题和条目。
-
实现一个用户身份验证系统。
-
创建一个注册页面,让用户创建账户,并设置一些界面只供登录用户访问。
-
学习如何保护用户数据安全
在当前只有超级用户能够通过管理网站输入数据。因此我们添加几个页面,让用户能够输入数据
添加新主题
用于添加主题的表单
在Django中,创建表单的最简单方式时使用ModelForm,因此我们在models.py所在目录中创建forms.py文件
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
- 定义了一个名为TopicForm继承自forms.ModelForm的类,只包含一个内嵌的Metal类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。
- 根据模型Topic创建一个表单,该表单只包含字符text,并让Django不要为字段text生成标签
URL模式new_topic
把http://localhost:8000/new_topic/设置为添加新主题的url。
在learning_logs/urls.py中添加
#用于添加新主题的网页
url(r'^new_topic/$',views.new_topic,name='new_topic'),
视图函数new_topic()
视图函数new_topic()需要处理两种情形:①刚进入new_topic网页,它应该显示一个空表单;②对提交的表单数据进行处理,并将用户重定向至网页topics。
修改views.py
先添加三行头文件
from django.http import HttpResponseRedirect
from django.core.urlresolves import reverse
from .forms import TopicForm
如果出现from django.core.urlresolves import reverse ModuleNotFoundError: No module named 'django.core.urlresolves'的报错
将django.core.urlresolves
改为django.urls
添加函数new_topic()
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类,提交主题后我们就重定向到显示topics的页面
- 函数reverse()根据指定的URL模型确定URL。
GET请求和POST请求
GET请求:从服务器读取数据
POST请求:用户需要通过表单提交信息
函数new_topic()将请求对象作为参数。用户初次请求该网页时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。
所以我们判断用户的请求是POST还是GET,如果是GET就返回一个空表单,是POST就对填好的表单进行处理。
我们使用用户输 入的数据(它们存储在request.POST中)创建一个TopicForm实例,这样对象form将包含用户提交的信息。
函数is_valid()保证用户填写了所有必不可少的信息,而且输入与要求一致。如果输入正确的话就保存页面。并使用reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect(),这将重定向自页面topics。
模板new_topic
创建新模板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 %}
- action的作用是告诉服务器将提交的表单数据发送到哪里,method让POST请求的方式提交数据
- 模板{% csrf_token %}来防止攻击者利用表单来获得对服务器未经授权的访问(跨站请求伪造)
- 我们只需包含模板变量{{% forms.as_p%}}就能让Django自动创建显示表单所需的所有字段,as_p的作用是让Django以段落格式渲染所有表单元素。
- button则定义了一个按钮
链接到页面new_topic
在topics.html中添加一个到页面new_topic链接
<ul>
...
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
添加新条目
如今我们实现了添加新主题的功能,现在增加在新主题中添加信息的功能。
步骤同之前一样:定义URL->编写视图函数和模板->链接到添加新条目的网页。
添加新条目的表单
创建一个与模型Entry的相关联的表单,但这个表单的定制程度比TopicForm要高些:
修改forms.py
...
from .model import Topic,Entry
...
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text': ''}
widgets = {'text': forms.Textarea(attrs={'col': 80})}
- 多导入了Entry模型
- 新类继承了forms.ModelForm,包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。这里给字段text指定了一个空标签。
- 我们定义了属性widgets。小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。通过设置属性widgets,可覆盖Django选择的默认小部件。通过让 Django使用forms.Textarea,我们定制了字段’text’的输入小部件,将文本区域的宽度设置为80 列,而不是默认的40列。这给用户提供了足够的空间,可以编写有意义的条目。
URL模式new_entry
在添加新条目的页面的URL模式中,需要包含实参topic_id。因为条目必须与特定的主题相关联。
在lerning_logs/urls.py中修改
#用于添加新条目的页面
url(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()
在views.py中添加
...
form .forms import TopicForm,EntryForm
...
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]))
context={'topic':topic,'form':form}
return render(request,'learning_logs/new_entry.html',context)
- 头文件中添加创建的EntryForm。
- 调用save()时,我们传递了实参commit=False,让Django创建一个新的条目对象,并将其存储到new_entry中,但不将它保存到数据库中。我们将new_entry的属性topic设置为在这个函数开头从数据库中获取的主题,然后调用save(),且不指定任何实参。这将把条目保 存到数据库,并将其与正确的主题相关联。
- 其余跟new_topic()函数类似
编写new_entry.html
{% extends "learning_logs/base.html"%}
{% block content %}
<p><a href="{% '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 %}
<p><a href="{% 'learning_logs:topic' topic.id %}">{{topic}}</a></p>
在顶端显示主题,同时也是一个链接
表单的实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题
链接到页面new_entry
修改topic.html
...
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
<ul>...
...
编辑条目
现在创建一个页面让用户能够编辑既有的条目
URL模式edit_entry
这个页面的URL需要传递要编辑的条目的ID,在learning_logs/urls.py中的urlpatterns添加
#用于编辑条目的页面
url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,
name='edit_entry'),
视图函数edit_entry()
edit_entry()将返回一个表单,让用户能够对条目进行编辑。
页面edit_entry收到GET请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑。该页面收到POST请求(条目文本经过修订)时,它将修改后的文本保存到数据库中:
from .models import Topic, Entry
...
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 HttpResponseRedirect(reverse('learning_logs:topic',
args=[topic.id]))
context = {'entry':entry,'topic':topic,'form':form}
return render(request,'learning_logs/edit_entry.html',context)
我们需要导入模型Entry。entry = Entry.objects.get(id=entry.id)
获得到用户要修改的条目对象,以及与该条目相关联的主题。在请求方法为GET时将执行的if代码块中,我们使用实参instance=entry创建一个EntryForm实例。这个实参让Django创建一个表单,并使用既有条目对象中的信息填充它。 用户将看到既有的数据,并能够编辑它们。
处理POST请求时,我们传递实参instance=entry和data=request.POST,让Django根据既有条目对象创建一个表单实例,并根据request.POST中的相关数据对其进行修改。然后,我们检查表单是否有效,如果有效,就调用save(),且不指定任何实参。接下来,我们重定向到显示条目所属主题的页面,用户将在其中看到其编辑的条目的新版本。
编写模板edit_entry.html
同样在index.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
在topic.html中修改
...
{% 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 %}">editentry</a>
</p>
</li>>
...
效果图: