你将建立一些表单,让用户可以添加主题和条目,以及编辑既有的条目。你还将学习Django如何防范对基于表单的网页发起的常见攻击,这让你无需花太多时间考虑确保应用程序安全的问题。html
而后,咱们将实现一个用户身份验证系统。你将建立一个注册页面,供用户建立帐户,并让有些页面只能供已登陆的用户访问。接下来,咱们将修改一些视图函数, 使得用户只能看到本身的数据。你将学习如何确保用户数据的安全。python
创建用于建立用户帐户的身份验证系统以前,咱们先来添加几个页面,让用户可以输入数据。咱们将让用户可以添加新主题、添加新条目以及编辑既有条目。
当前,只有超级用户可以经过管理网站输入数据。咱们不想让用户与管理网站交互,所以咱们将使用Django的表单建立工具来建立让用户可以输入数据的页面。数据库
建立基于表单的页面的方法几乎与前面建立网页同样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差异是,须要导入包含表单的模块forms.py。django
(1)建立表单编程
让用户输入并提交信息的页面都是表单,哪怕它看起来不像表单。用户输入信息时,咱们须要进行验证,确认提供的信息是正确的数据类型,且不是恶意的信息,如中断服务器的代码。而后,咱们再对这些有效信息进行处理,并将其保存到数据库的合适地方。这些工做不少都是由Django自动完成的。 在Django中,建立表单的最简单方式是使用ModelForm,它根据咱们在python-Django实践的模型中的信息自动建立表单。建立一个名为forms.py的文件,将其存储到models.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中:app
"""定义learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' 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'), ]
(3)视图函数new_topic()函数
修改views.py,函数new_topic() 须要处理两种情形:刚进入new_topic 网页(在这种状况下,它应显示一个空表单);对提交的表单数据进行处理,并将用户重定向到网页topics :
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 类,用户提交主题后咱们将使用这个类将用户重定向到网页topics 。函数reverse() 根据指定的URL模型肯定URL,这意味着Django 将在页面被请求时生成URL。咱们还导入了刚才建立的表单TopicForm 。
(4)GET请求和POST请求
建立Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户须要经过表单提交信息时,一般使用POST请求。
处理全部表单时,咱们都将指定使用POST方法。还有一些其余类型的请求,但这个项目没有使用。
函数new_topic() 将请求对象做为参数。用户初次请求该网页时,其浏览器将发送GET请求;
用户填写并提交表单时,其浏览器将发送POST请求。
根据请求的类型,咱们能够肯定用户请求的是空表单(GET请求)仍是要求对填写好的表单进行处理(POST请求)。
❶处的测试肯定请求方法是GET仍是POST。若是请求方法不是POST,请求就多是GET,所以咱们须要返回一个空表单(即使请求是其余类型的,返回一个空表单也不会有任何 问题)。咱们建立一个TopicForm 实例(见❷),将其存储在变量form 中,再经过上下文字典将这个表单发送给模板context(见❼)。因为实例化TopicForm 时咱们没有指定任何 实参,Django将建立一个可供用户填写的空表单。
若是请求方法为POST,将执行else 代码块,对提交的表单数据进行处理。咱们使用用户输入的数据(它们存储在request.POST 中)建立一个TopicForm 实例(见❸), 这样对象form 将包含用户提交的信息。
要将提交的信息保存到数据库,必须先经过检查肯定它们是有效的(见❹)。函数is_valid() 核实用户填写了全部必不可少的字段(表单字段默认都是必不可少的),且输入 的数据与要求的字段类型一致(例如,字段text 少于200个字符,这是咱们在第18章中的models.py中指定的)。这种自动验证避免了咱们去作大量的工做。若是全部字段都有 效,咱们就可调用save() (见❺),将表单中的数据写入数据库。保存数据后,就可离开这个页面了。咱们使用reverse() 获取页面topics 的URL,并将其传递 给HttpResponseRedirect() (见❻),后者将用户的浏览器重定向到页面topics 。在页面topics 中,用户将在主题列表中看到他刚输入的主题。
5. 模板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 %}
这个模板继承了base.html,所以其基本结构与项目“学习笔记”的其余页面相同。
六、连接到页面new_topic
接下来,咱们在页面topics.html 中添加一个到页面new_topic 的链:
{% 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,编写视图函数和模板,并连接到添加新条目的网页。但在此以前,咱们须要在forms.py中再添加一个 类。
(1)用于添加新条目的表单
咱们须要在forms.py建立一个与模型Entry 相关联的表单,但这个表单的定制程度比TopicForm 要高些:
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': ''} widgets = {'text': forms.Textarea(attrs={'cols': 80})}
(2) URL模式new_entry
在用于添加新条目的页面的URL模式中,须要包含实参topic_id ,由于条目必须与特定的主题相关联。该URL模式以下,咱们将它添加到了learning_logs/urls.py中:
"""定义learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' 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模式与形式为http://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.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(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context) 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)
(4)模板new_entry
模板new_entry.html 相似于模板new_topic.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
咱们须要在topic.html显示特定主题的页面中添加到页面new_entry 的连接:
{% 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 %}
咱们在显示条目前添加连接,由于在这种页面中,执行的最多见的操做是添加新条目。下图显示了页面new_entry 。如今用户能够添加新主题,还能够在每一个主题中添加任 意数量的条目。请在一些既有主题中添加一些新条目,尝试使用一下页面new_entry 。
下面来建立一个页面,让用户可以编辑既有的条目。
(1)URL模式edit_entry
这个页面的URL须要传递要编辑的条目的ID。修改后的learning_logs/urls.py以下:
"""定义learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' 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()
修改视图views.py,页面edit_entry 收到GET请求时,edit_entry() 将返回一个表单,让用户可以对条目进行编辑。该页面收到POST请求(条目文本通过修订)时,它将修改后的文本保存到数据库中:
from django.shortcuts import render from django.http import HttpResponseRedirect from django.urls import reverse from .models import Topic, Entry 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(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context) 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) 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 。咱们获取用户要修改的条目对象,以及与该条目相关联的主题。在请求方法为GET时将执行的if 代码块中,咱们使用实 参instance=entry 建立一个EntryForm 实例。这个实参让Django建立一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并可以编辑它们。
处理POST请求时,咱们传递实参instance=entry 和data=request.POST ,让Django根据既有条目对象建立一个表单实例,并根据request.POST 中的相关数 据对其进行修改。而后,咱们检查表单是否有效,若是有效,就调用save() ,且不指定任何实参。接下来,咱们重定向到显示条目所属主题的页面,用户将 在其中看到其编辑的条目的新版本。
(3)模板edit_entry
下面是模板edit_entry.html,它与模板new_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做为一个实参,让视图对象可以修改正确的条目对象。咱们将提交按 钮命名为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",它出如今页面中每一个条目的后面。下图显示了包含这些连接时,显示特定主题的页面是什么样的。
参考:
一、python编程,从入门到实践