19.1 用户可以输入数据html
咱们不想让用户与管理网站交互,所以咱们将使用Django的表单来建立让用户可以输入数据的页面。python
19.1.1 添加新主题数据库
建立基于表单的页面的方法交互与前面建立网页同样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差异是,须要导入包含表单的模块forms.py。django
一、用于添加主题的表单服务器
在Django中,建立表单的最简单的方式是使用ModelForm。建立一个名为forms.py所在的目录中,并在其中编写你的第一个表单:app
from django import forms from .models import Topic class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text':''}
最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪一个模型建立表单,以及在表单中包含哪些字段。函数
二、URL模式new_topicpost
这个新网页的URL应简短而具备描述性,所以当用户要添加新主题时,咱们将切换到http://localhost:8000/new_topic。学习
"""定义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'), ]
三、视图函数网站
函数new_topic()须要处理两种情形:刚进入new_topic网页(应显示一个空表单),对提交的表单数据进行处理,并将用户重定向到网页topics:
from django.shortcuts import render from .models import Topic from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .forms import TopicForm # Create your views here. --ship-- 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)
四、模板new_topic
{% 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 %}
Django使用模板标签{% csrf_token %}来防止攻击者利用表单来得到对服务器未经受权的访问(这种攻击被称为跨站请求伪造)。 只须要包含模板变量{{ form.as_p}},就可以让Django自动建立显示表单所需的所有字段。修饰符as_p让Django以段落格式渲染全部表单元素,这是一种整洁地显示表单的简单方式。
六、连接到页面new_topic
接下来,咱们在页面topics中添加一个到页面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 %}
19.1.2 添加新条目
一、用于添加新条目的表单
咱们须要建立一个与模型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})}
小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。经过让Django使用forms.Textarea,咱们定制了字段‘text’的输入小部件,将文本区域的宽度设置为80,而不是默认的40列。
二、URL模式new_entry
# 用于添加新条目的页面 url(r'^new_entry/(?Ptopic_id>\d+)/$',views.new_entry,name='new_entry')
三、视图函数new_entry
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)
调用save()时,咱们传递了实参commit=False,让Django建立一个新的条目对象,并将其存储到new_entry中,但不将它保存到数据库中。咱们将new_entry的属性topic设置为在这个函数开头从数据库中获取的主题,而后调用save(),且不指定任何参数。这将把条目保存到数据库中,并将其与正确的主题相关联。
从新定向显示相关主题的页面、调用reverse()时,须要提供两个实参:要根据它来生成URL模式的URL模式的名称;列表args,其中包含要包含在URL中的全部实参。在这里,列表args只有一个元素——topic_id。接下来调用HttpResponseRedirect()将用户从新定向到显示增长条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。
四、模板new_entry.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"> {% crsf_token %} {{ form.as_p}} <button name="submit">add entry</button> </form> {% endblock content %}}
咱们在页面顶端显示了主题,让用户知道他是在哪一个主题中添加条目;该主题名也是一个连接,可用于返回该主题的主页面。
表单的实参action包含URL中的topic_id值,让视图函数可以添加新条目关联到正确的主题。
五、连接到页面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</a> </p> <ul> --ship--</ul> {% endblock content %}
19.1.3 编辑条目
下面建立一个页面,让用户可以编辑既有的条目。
一、URL模式edit_entry
这个页面的URL须要传递要编辑的条目的ID。修改后delearning_logs/urls.py以下:
# 用于编辑条目的页面 url(r'^edit_entry/(P<entry_id>\d+)/$',views.edit_entry, name='edit_entry'),
二、视图函数edit_entry()
页面edit_entry收到GET请求时,edit_entry()返回一个表单,让用户可以对条目进行编辑。该页面收到POST请求时,将修改后的文本保存到数据库中:
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.himl',context)
三、模板edit_entry
{% 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 %}}
四、连接到页面edit_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> <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 %}
将编辑连接放在每一个条目的日期和文本后面。
19.2 建立用户帐户
19.2.1 应用程序user
首先使用命令startapp来建立一个名为user的应用程序:
(11_env) D:\learning_log>python manage.py startapp users
这个命令新建了一个名为user的目录,其结构与应用程序learning_logs相同。
一、将应用程序users添加到settings.py中
# My apps 'learning_logs', 'users',
二、包含应用程序users的URL
接下来,须要修改项目根目录中的urls.py,使其包含咱们将为应用程序users定义的URL:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/',include('user.urls',namespace='users')), url(r'',include('learning_logs.urls',namespace='learning_logs')), ]
19.2.2 登陆页面
首先实现登陆页面,为此使用Django提供的默认登陆视图,所以URL模式会稍有不一样。早目录learning_log/users/中,新建一个名为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') ]
首先导入默认视图login。登陆页面的URL模式与URL http://localhost:8000/users/login匹配。这个URL中的单词users让那个Django在users/urls.py中查找,而单词login让它将请求发送给Django默认视图login(视图实参为login,而不是views.login)。
一、模板login.html
在目录learning_log/users/中,建立一个名为templates的目录,并在其中建立一个名为users的目录。一下是模板login.html,你应将其存储到目录learning_log/users/templates/users中:
{% extends "learning_logs/base.html" %} {% block content %} {% if form.errors %} <p>Your username and password didn't match.Please try again.</p> {% endif %} <form method="post" action="{% url 'users:login' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">log in</button> <input type="hidden" name="next" value="{% url 'learning_logs:index'%}" /> </form> {% endblock content %}
二、连接到登陆页面
在base.html中添加到登陆页面的连接,让全部页面都包含它。用户已登陆时,咱们不想显示这个连接,所以将它嵌套在一个{% if %}标签中:
<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 }}. {% else %} <a href="{% url 'user.login' %}}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
三、使用登陆页面
前面创建了一个用户帐户,下面来登陆一下,看看登陆页面是否管用。访问http://localhost:8000/admin/,若是你依然是以管理员身份登陆的,请在页眉上找到注销连接并单击。
注销后,访问http://localhost:8000/users/login,再登陆。
19.2.3 注销
不建立注销的页面,而让用户只需单击一个连接就能注销并返回到主页。为此,咱们将为注销连接定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销连接。
一、注销URL
# 注销 url(r'^logout/$',views.logout_view,name='logout'),
二、视图函数logout_view()
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import logout # Create your views here. def logout_view(request): """注销用户""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
三、连接到注销视图
<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.2.4 注册页面
下面建立一个让新用户可以注册的页面。咱们将使用Django提供的表单UserCreationForm,但编写本身的视图函数和模板。
一、注册页面的URL模式
# 注册页面 url(r'^register/$',views.register,name='register'),
二、视图函数register()
在注册页面首次被请求时,视图函数register()须要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。若是注册成功,这个函数还须要让用户自动登陆。
def logout_view(request): """注销用户""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index')) def register(request): """注册新用户""" if request.method !='POST': # 显示空的注册表单 form = UserCreationForm(date=request.POST) else: # 处理填写好的表单 form = UserCreationForm(data=request.POST) if form.is_valid(): new_user = form.save() # 让用户自动登陆,再从新定向到主页 authenticated_user = authenticate(username=new_user.username, password=request.POST['password1']) login(request,authenticated_user) return HttpResponseRedirect(reverse('learning_logs:index')) context = {'form':form} return render(request,'users/register.html',context)
三、注册模板
{% extends "learning_logs/base.html" %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">register</button> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/> </form> {% endblock content %}
这里也使用了as_p,让Django在表单中正确地显示全部的字段,包括错误消息——若是用户没有正确地填写表单。
四、连接到注册页面
<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:register' %}">register</a> - a <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
19.3 让用户拥有本身的数据
19.3.1 使用@login_required限制访问
Django提供了装饰器@login_required:对于某些页面,只容许已登录的用户访问它们。装饰器是放在函数定义前面的命令,Python在函数运行前,根据它来修改函数代码的行为:
一、限制对topics页面的访问
在learning_logs/views.py中添加以下代码:
--ship-- from .forms import TopicForm,EntryForm from django.contrib.auth.decorators import login_required # Create your views here. def index(request): """学习笔记的主页""" return render(request,'learning_logs/index.html') @login_required def topics(request): --ship--
为实现这种重定向,咱们须要修改setting.py,让Django知道到哪去查找登陆页面。在settings.py末尾添加:
# 个人设置 LOGIN_URL = '/users/login/'
二、全面限制对项目“学习笔记”的访问
在项目“学习笔记中”,咱们将不限制对主页、注册页和注销页面的访问,并限制对其余全部页面的访问。
在下面的learning_logs/views.py中,对除index()外的每一个视图都应用了装饰器@login_required:
若是你在未登陆的状况下尝试访问这些页面,将被重定向到登陆页面。另外,你还不能单击到new_topic等页面的连接。
19.3.2 将数据关联到用户
下面修改模型Topic,在其中添加一个关联到用户的外键。这样作后,咱们必须对数据库进行迁移。最后,咱们必须对有些视图进行修改,使其只显示与当前登陆的用户相关联的数据。
一、修改模型Topic
from django.db import models from django.contrib.auth.models import User # 定义了一个名为Topic的类,它继承了Model—Django中一个定义了模型基本功能的类 # 属性text是一个CharField—由字符或文本组成的数据。须要存储少许的文本,如名称、标题或城市时,可使用 # date_added是一个DateTimeField—记录日期和时间的数据。 class Topic(models.Model): """用户学习主题""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User)
--ship--
二、肯定当前有哪些用户
三、迁移数据库
为验证迁移符合预期,可在Shell会话中像下面这样作:
19.3.3 只容许用户访问本身的主题
在views.py中,对函数topics()作以下修改:
@login_required def topics(request): """显示全部的主题""" topics = Topic.objects.filter(owner=request.user).order_by('date_added') context = {'topics':topics} return render(request,'learning_logs/topics.html',context)
19.3.4 保护用户的主题
@login_required def topic(request,topic_id): """显示单个主题及其全部的条目""" topic = Topic.objects.get(id=topic_id) # 确认请求的主题属于当前用户 if topic.owner != request.user: raise Http404 entries = topic.entry_set.order_by('-date_added') context = {'topic':topic,'entries':entries} return render(request,'learning_logs/topic.html',context)
19.3.5 保护页面edit_entry
@login_required def edit_entry(request,entry_id): """编辑既有条目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if topic.owner != request.user: raise Http404 if request.method !='POST': --ship--
19.3.6 将新主题关联到当前用户
@login_required def new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据处理 form = TopicForm(request.POST) if form.is_valid(): new_topic = form.save(commit=False) new_topic.owner = request.user new_topic.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form':form} return render(request,'learning_logs/new_topic.html',context)