19.1 让用户能够输入数据
1、添加新主题
与前面创建网页基本一样:定义一个URL,编写一个视图函数,编写一个模版。主要差别是需要导入包含表单的模块forms.py
1、用于添加主题的表单
让用户输入并提交信息的页面都是表单,哪怕它看起来不像表单。
用户输入信息时,我们需要进行验证,确认数据非恶意,正确。然后再对信息进行处理,将其保存到数据库合适的地方。
创建表单最简单的方式是使用ModelForm。创建文件forms.py将其保存到models.py的目录下。
from django import forms from .models import Topic class TopicForm(forms.ModelForm): #继承ModelForm class Meta: #ModelForm里面包含一个内嵌的Meta类 model = Topic #根据Topic模型创建表单 fields = ['text'] #包含text字段---可能是Topic模型中的text属性。 labels = {'text':''} #不要为text字段生成标签
2、URL模式new_topic
当用户要新建主题时跳转到该URL上。在urls.py中创建链接,
url(r'^new_topic/$', views.new_topic, name='new_topic')
3、视图函数new_topic()
在视图文件中创建new_topic()函数,该函数需要处理两种情形:刚进入时显示一个空表单;对提交表单数据处理,并且重新定向到topics网页。
from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .forms import TopicForm def new_topic(request): if request.method != 'POST': #初次请求时,非POST请求,判断条件成立。 form = TopicForm() #创建一个空表单,存储在form中,再通过后面的上下文context,发送到模版中。 else: #条件判断请求方式为POST请求。 form = TopicForm(request.POST) #使用用户输入的数据(即实参request.POST),创建实例,保存到form中。 if form.is_valid(): #函数is_valid()核实用户是否填写了所有必要字段(表单字段默认为都是必要的),且字段类型有效。 form.save() #调用save()函数,将表单数据写入数据库。 #reverse()函数获取页面topics的URL,将其传递给HttpResponseRedirect(),字面翻译http响应重新定位,将重新定位到topics页面。 return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form':form} #上下文,将form传递给form模版。 return render(request, 'learning_logs/new_topic.html', context)
4、GET请求和POST请求(针对上述代码做的解释)
对于只服务器读取数据的页面,使用GET请求;对于需要通过表单提交信息时,使用POST请求。处理表单时,使用POST请求。
用户初次请求时,浏览器发送GET请求;用户填写表单并提交时,浏览器发送POST请求。
5、模版new_topic
这里有一个来回的情况,先是get请求通过url地址或者topics页面点击进入new_topic页面,里面的表单提示以post请求将表单数据提交给回new_topic,视图函数new_topic接受post请求将form储存,并返回到topics页面,并跳出函数。
{% extends 'learning_logs/base.html' %} {% block content %} <p>Add a new topic:</p> #form定义一个表单,实参action告诉服务器将表单数据提交去哪里。实参method表明提交数据的请求方式为POST。 <form action="{% url 'learning_logs:new_topic' %}" method='post'> {% csrf_token %} #防止攻击者利用表单来获得对服务器未经授权的访问----跨站请求伪造。 {{ form.as_p }} #as_p修饰符让Django以段落格式渲染所有表单元素。 <button name="submit">add topic</button> #创建一个按钮用于提交表单。 </form> {% endblock content %}
6、topics.html
在topics页面中添加一个页面new_topic的链接。
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a>
2、添加新条目
1、用于添加新条目的表单
from .models import Topic, Entry class EntryForm(form.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text':''} #定义属性widgets(小部件)是html表单元素,通过让django使用forms.Textarea()函数,定义文本宽度为80列。 widgets = {'text': forms.Textarea(attrs={'cols': 80})}
2、URL模式new_entry
用于添加新条目的页面URL模式中,需要包含实参topic_id,因为条目必须与特定的主题相关联。
url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry')
3、视图函数new_entry()
from .forms import TopicForm, EntryForm #导入刚创建的EntryForm模块。 def new_entry(request, topic_id): #包含形参topic_id,用于存储从URL获得的值。 topic = Topic.objects.get(topic_id) #渲染页面和处理表单时获取正确的主题。 '''检查请求是不是post,如果不是建立一个空的EntryForm实例;如果是,data用request中的post数据来填充---从下面的模版中能了解到,并 且如果有效就设置条目对象的属性topic,再将条目对象存储到数据库中。''' if request.method != 'POST': form = EntryForm() else: form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) #传递了实参commit=False,也就是不发送,意思是将django把数据保存在new_entry中,但不发送到数据库中。 new_entry.topic = topic #确定该条目的主题。 new_entry.save() #完成上述之后,再将条目数据保存到数据库中。 return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id])) #topic的URL模式有参数,所以需要后面的args来传递所有实参。 context = {'topic':topic, 'form':form} return render(request, 'learning_logs/new_entry.html', context)
4、模板new_entry
注意一下,下述代码中,对于topic的id用的是topic.id这种表达方式,有机会的话研究一下,这里的topic指的是什么,肯定不是变量。
{% 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 'leaning_logs:new_entry' topic.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name='submit'>add entry</button> </form> {% endblock content %}
5、链接到页面new_entry
在topic模板中添加链接到new_entry页面的链接。
<p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a> </p>
3、编辑条目
创建一个页面,让用户可以编辑既有条目。
1、URL模式edit_entry
在URL中传递的ID存储在形参entry_id中,将匹配的请求发送给视图函数edit_entry()
url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry')
2、视图函数edit_entry()
页面收到get请求,返回一个对应entry条目的页面,可以进行编辑;页面收到post请求,将修改后的文本保存。
from .models import Topic, Entry def edit_entry(request, entry_id): entry = Entry.objects.get(entry_id) #获取需要修改的条目对象。 topic = entry.topic #这是反过来获取该条目对应的主题。 if request.method != 'POST': form = EntryForm(instance=entry) #instance=entry创建一个EntryForm实例,让django用既有条目信息填充表单。 else: form = EntryForm(instance=entry, data=request.POST) #两个参数,第一个让django用既有条目信息创建表单,第二个用post数据进行修改。 if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))#重新定向页面,注意要有topic的id参数,而且是个列表。 context = {'entry': entry, 'topic': topic, 'form': form} #这里context定义了三个键值对,应该是用于在模板中直接调用这三个变量。 return render(request,'learning_logs/edit_entry.html', context)
3、模板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'> #将条目id作为一个实参,让视图对象正确修改条目对象。 {% csrf_token %} {{ form.as_p }} <button name='submit'>save changes</button> </form> {% endblock content %}
4、链接到页面edit_entry
在topic.html中,在遍历每个条目的for循环中,给每个条目添加页面edit_entry的链接
<p> <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a> #注意仍然需要用entry.id来确定URL,明确 </p>
19.2 创建用户账户
1、应用程序users
创建一个users的应用程序
(ll_env)learning_logs$ python3 manage.py startapp users
1、将应用程序users添加到settings.py中
这样,django将把应用程序users包含到项目中。
2、包含应用程序users的URL
在项目的urls.py中加入users的URL,根目录为users/,包含users.urls中的URL 地址。
2、登录页面
在learning_log/users/目录下新建urls.py文件。
from django.conf.urls import url from django.contrib.auth.views import login #导入默认视图login,在升级后的django里,修改为导入类的模式,而不是导入函数。变成LoginView from . import views urlpatterns = [ url(r'^login/$', login, {'template_name':'users/login.html'}, name='login') ]
1、模板login.html
在learning_log/users/目录下创建templates目录,并在其中创建一个users的目录。在里面创建模板。继承base.html模板格式。
{% 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' %}" /> #实参value告诉django在成功登录后定向为主页。 </form> {% endblock content %}
2、链接到登录页面
在base.html中添加到登录页面的链接,让所有的页面都包含它。对于已登录用户,不想显示这个链接,故嵌套在一个if标签中。
<a href="{% url 'learning_logs:topic' %}">Topics</a> - {% if user.is_authenticated %} #django身份验证系统下每个模板都可以使用user变量,其中有一个is_authenticated属性。 Hello, {{ user.username }} {% else %} <a href="{% url 'users:login' %}">log in</a> #对于else条件,定向至登录链接。 {% endif %}
3、使用登录页面
3、注销
1、注销URL
不创建用于注销的页面,而让用户只需单击一个链接就能注销并返回主页。为此,我们定义一个URL模式,编写一个视图函数,并在base.html中添加注销链接。
在users/urls.py中添加如下链接
url(r'^logout/$', views.logout_view, name='logout'),
2、视图函数logout_view()
导入django函数logout(),并且调用它,再重新定向到主页。在users/views.py中添加。
from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse #新版本改成from django.urls import reverse from django.contrib.auth import logout #导入函数logout,估计新版本会有变化。 def logout_view(request): logout(request) #以request为参数,调用logout return HttpResponseRedirect(reverse('learning_logs:index')) #定向到主页。
3、链接到注销视图
在base.html的if条件判断中,加入如果已登录的,显示注销链接。
<a href="{% url 'users:logout' %}">log out</a>
4、注册页面
创建一个能够让用户注册的页面。使用django的表单UserCreationForm
1、注册页面的URL模式,在users/urls.py中添加。
url(r'^register/$', views.register, name='register')
该代码与URL:http://localhost:8000/users/register/相匹配。
2、视图函数register()
需要显示一个空的注册表单,在用户提交时进行处理,如果注册成功还要自动登录。在views.py文件中新建register函数。
from django.shortcuts import render from django.contrib.auth import login,logout,authenticate #authenticate:证……是真的。 #导入函数login为了在注册成功时自动登录。 from django.contrib.auth.forms import UserCreationForm #导入默认用户注册表单。 def register(request): if request.method != 'POST': form = UserCreationForm() else: form = UserCreationForm(data=request.POST) if form.is_valid(): new_user = form.save() #如输入有效,将用户名和密码的散列值(即哈希函数)保存到数据库。 ## authenticate()函数将返回一个通过了身份验证的对象,并将其存储在authenticated_user中。 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)
3、注册模板
在login.html同一个目录中创建register.html
{% 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 %}
4、链接到注册页面
在base.html中添加,添加在if判断里面,如果是已登录用户,则不用看到此链接,非用户可以看到此链接。
<a href="{% url 'users:register' %}">register</a>
19.3 让用户拥有自己的数据
本节将修改模型Topic,让每个主题都归属于特定的用户。也将影响条目,每个条目属于不同的主题。
1、使用@login_required限制访问
1、限制对topics页面的访问
只允许已登录用户访问topics页面。在learning_logs/views.py中添加。
from django.contrib.auth.decorators import login_required #decorators:装饰器,导入login_required @login_required #让python在运行topics()代码前先运行login_required()代码。 def topics(request): ......
login_required()代码检查用户是否登录,只有登录时django才继续运行topics(),如果未登录,则重新定向到主页。需要继续在settings.py中修改,让django知道去哪里查找登录页面。settings.py中。
LOGIN_URL = '/users/login/'
2、全面限制对项目“学习笔记”的访问
在views.py中,除了主页,其他页面都需要限制,即加上装饰器。不能点击到相关的视图链接,也不能通过URL来直接链接,都会重新定向到登录页面。
2、将数据关联到用户
最高层归属用户,低层的会自动归属。本例中,主题归属用户,条目自动会归属用户。
1、修改模型Topic,增加一个外键关联。
from django.contrib.auth.models import User owner = models.ForeignKey(User)
2、确定当前有哪些用户
我们在迁移数据库时,将存储主题和用户之间进行关联。
用shell先查询到User.objects.all()下有几个id,进行遍历print每个的username和id,知道每个用户的序号。
通过makemigrations learning_logs迁移数据库。django会要求对每个topic提供默认值,即提供相应的user编号。
接下来django使用该值来迁移数据库,并生成迁移文件,在模型topic中添加字段owner。
在虚拟环境下执行迁移,migrate
PS:截止到目前,我们把所有的topic都归属为admin用户了,但是这并没有限制其他用户对其访问,因为login_required的要求是需要登录之后才能显示页面,并没有限制必须登录相应的owner才能看到相应的页面。
3、只允许用户访问自己的主题
在views.py中对函数topics()进行修改
topics = Topic.objects.filter(owner=request.user).order_by('date_added') #加入一个过滤,注意这个表达方式,filter是夹在原来代码的当中的。
4、保护用户的主题
截止目前,我们还没有限制对显示单个主题页面的访问,如果是已登录用户只要直接输入相应的topic_id的URL还是能访问相应的主题。
为修复该问题,在视图函数topic()获取请求的条目前执行检查。
from django.http import HttpResponseRedirect, Http404 #确认请求的主题属于当前用户 if topic.owner != request.user: raise Http404 #raise是用来引发异常处理的响应的,具体的还要再研究。
5、保护页面edit_entry (这里对于修改条目要保护,其他的url就还有新增主题和新增条目,这两项不用保护,具体还是查看urls.py中的需求。)
跟上面一样的代码,写在edit_entry()函数下。
6、将新主题关联到当前用户
当前新增主题时,会因为缺失owner字段而报错。因此将新主题关联到当前用户中。
new_topic.owner = request.user
PS:本章学完了,但是不能进行删除啊,没有删除功能啊。找一下删除功能怎么体现。