本章内容来自书籍,记录下来仅方便复习,如有侵权,请联系作者删除。
+++python学习笔记+++
在本章,你将创建一些表单,让用户能够添加主题和条目,编辑已有的条目。实现一个用户身份验证系统,创建一个注册页面,供用户创建账户。
一、 让用户能够输入数据
超级用户:添加新主题,新条目。编辑已有条目。
1. 添加新主题
定义一个URL,编写一个视图函数,编写一个模板。
需要导入包含表单的模块forms.py
- (1) 用于添加主题的表单
使用ModelForm创建表单。创建forms.py文件,存储到models.py所在目录(learning_log/learning_logs)中。并编写第一个表单
forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm): #code1
class Meta:
model = Topic #code2
fields = ['text'] #code3
labels = {'text':''} #code4
分析:
首先导入模块forms和需要使用的模型Topic
code1:继承forms.ModelForm,最简单的ModelForm版本是内嵌一个Meta类。
code2:根据模型Topic创建一个表单,该表单只包含text字段,见code3
code4:让Django不要为字段text生成标签
- (2) URL模式new_topic
用户要添加新主题时,切换到http://localhost:8000/new_topic/
将nes_topic添加到learning_logs/urls中。
urls.py
'''定义learning_logs的URL模式'''
from django.urls import path
from . import views
app_name='learning_logs'
urlpatterns = [
--snip--
# 用于添加新主题的网页
path('new_topic/',views.new_topic,name = 'new_topic'),
]
这个URL模式将请求交给视图函数new_topic()。
- (3) 视图函数new_topic()
函数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
--snip--
def new_topic(request):
"""添加新主题"""
if request.method != 'POST': #code1
#未提交数据:创建一个新表单
form = TopicForm() #code2
else:
# POST提交的数据,对数据进行处理
form = TopicForm(request.POST) #code3
if form.is_valid(): #code4
form.save() #code5
return HttpResponseRedirect(reverse('learning_logs:topics')) #code6
context = {'form':form} #code7
return render(request, 'learning_logs/new_topic.html', context)
导入HttpResponseRedirect类,用户提交主题后将使用这个类将用户重定向到网页topics。函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL。
- (4) get请求和post请求
get请求:用于只是从服务器读取数据的页面
post请求:用户需要通过表单提交信息时使用
函数new_topic()将请求对象作为参数。用户初次请求网页时,浏览器发送get请求;用户填写表单并提交时,浏览器发送post请求。
code1:测试确定请求的方法时GET还是POST。
code2:如果不是POST请求,返回一个空表单,我们创建一个TopicForm实例存储在变量form中,再通过上下文字典将该表单发送给模板。见code7
code3:如果是POST请求,用户输入的数据存储在request.POST中,创建一个TopicForm实例,对象form将包含用户提交的信息。
code4:检查提交的信息是否有效,is_valid()核实用户填写了所有必不可少的字段,且输入的数据与要求的字段类型保持一致。
code5:如果字段都有效,调用save(),将表单中的数据写入数据库。
保存数据后,使用reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect(),后者将用户的浏览器重定向到页面topics。可以在页面topics中看到刚刚添加的主题。
- (5) 模板new_topic
创建新模板new_topic.html,用于显示我们刚创建的表单。
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'> #code1
{% csrf_token %} #code2
{{ form.as_p }} #code3
<button name ="submit">add topic</button> #code4
</form>
{% endblock content %}
分析:
code1:定义了HTML表单,实参action告诉服务器将提交的表单数据发送到哪里,我们将它发回给视图函数new_topic()。实参method让浏览器以POST请求的方式提交数据。
code2:Django使用模板标签{% csrf_token %}来房子攻击者利用表单来获取服务器未经授权的访问。
code3:包含模板变量{{% form.as_p %}},Django显示表单,修饰符as_p,让Django以段落格式渲染所有表单元素。
code4:定义提交按钮
- (6) 链接到页面new_topic
接下来,我们再页面topics中添加一个到页面new_topic的链接
topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
--snip--
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new_topic:</a>
{% endblock content%}
2. 添加新条目
再次定义URL,编写视图函数和模板,链接到添加新条目的网页。
需要在forms.py中再添加一个类。
- (1) 用于添加新条目的表单
创建于模型Entry相关联的表单,制定程度比TopicForm要高:
forms.py
from django import forms
from .models import Topic,Entry
class TopicForm(forms.ModelForm):
--snip--
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text':''} #code1
widgets = {'text':forms.Textarea(attrs={'cols':80})} code2
分析:
类EntryForm也继承了forms.ModelForm,包含的Meta类指出表单基于的模型以及要在表单中包含哪些字段。
code1:给字段text定义一个空标签
code2:定义widgets属性。小部件(widget)是一个HTML表单元素。覆盖Django的默认小部件。Django使用forms.Textarea,指定字段text的输入小部件,设置文本框的宽度为80列。
- (2) URL模式new_entry
用于添加新条目的页面URL模式中,需要包含实参topic_id,条目必须与特定的主题想关联。
learning_logs/urls.py
'''定义learning_logs的URL模式'''
from django.urls import path
from . import views
app_name='learning_logs'
urlpatterns = [
--snip--
# 用于添加新条目的页面
path('new_entry/<topic_id>/',views.new_entry,name = 'new_entry'),
]
这个URL模式为http://localhost:8000/new_entry/id/
id是与主题ID匹配的数字,<topic_id>捕获一个数值,存储在变量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
# Create your views here.
--snip--
def new_entry(request, topic_id): #code1
"""在特定主题中添加新条目"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST': #code2
# 未提交数据,创建一个空表单
form = EntryForm()
else:
# POST提交的数据,对数据进行处理
form = EntryForm(data=request.POST) #code3
if form.is_valid():
new_entry = form.save(commit=False) #code4
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)#code5
code1: new_entry()的定义包含形参topic_id,用于存储从URL中获取的值。渲染页面和处理表单数据时,都需要知道针对的是哪一个主题,因此我们使用topic_id来获取正确的主题。
code2:检查请求时get或post,如果不是post请求,创建一个空的EntryForm实例。
code3:如果是post,创建一个EntryForm实例,使用request对象中的POST数据来填充它。再检查表单是否有效,如果有效,就设置条目对象的属性topic,然后保存到数据库。
code4:调用save()时,传递实参commit = False,让Django创建一个新的条目对象,并存储到new_entry中。
code5:将用户重定向到显示相关主题的页面。调用reserve()时,需要提供两个实参,URL模式的名称,列表args,包含在URL中的所有实参。调用HttpResponseRedirect(),将用户重定向到显示新 条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。
- (4) 模板new_entry
模板new_entry类似于模板new_topic
new_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id%}">{{ topic }}</a></p> #code1
<p>Add a new entry:</p>
<form action="{% url 'learning_logs:new_entry' topic.id%}" method = 'post'> #code2
{% csrf_token %}
{{ form.as_p }}
<button name='submit'>add entry</button>
</form>
{% endblock content %}
分析:
code1:显示主题,让用户知到是在哪个主题下添加条目,点击可返回。
code2:表单的实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题。
- (5) 链接到页面new_entry()
在显示特定的主题中添加new_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>
--snip--
</ul>
{% endblock content %}
3. 编辑条目
- (1) URL模式edit_entry
这个页面的URL需要传递条目的ID,修改learning_logs/urls.py
urls.py
'''定义learning_logs的URL模式'''
from django.urls import path
from . import views
app_name='learning_logs'
urlpatterns = [
--snip--
#用于编辑条目的页面
path('edit_entry/<entry_id>/',views.edit_entry,name = 'edit_entry')
]
在URL(http://127.0.0.1:8000/edit_entry/1/)中传递的ID存储在形参entry_id中。这个URL模式将预期匹配的请求发送给视图函数edit_entry()。
- (2) 视图函数edit_entry()
页面edit_entry收到get请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑。
收到post请求时,将修改后的文本保存到数据库中。
views.py
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
# Create your views here.
def index(request): --snip--
def topics(request): --snip--
def topic(request, topic_id): --snip--
def new_topic(request): --snip--
def new_entry(request, topic_id): --snip--
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) #code1
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)
code1:让Django根据已有条目对象创建一个表单实例,并根据request.POST中的数据,对条目进行修改。
- (3) 模板edit_entry
edit_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'> #code1
{% csrf_token %}
{{ form.as_p }}
<button name="submit">save changes</button>
</form>
{% endblock content %}
code1:实参action将表单发回给函数edit_entry()进行处理,在标签{% url %}中,将条目ID作为一个实参,让视图对象能够修改正确的条目对象。
- (4) 链接到页面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> #code1
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
code1:将编辑链接放在每个条目的日期和文本后面。使用模板标签{% url %}根据URL模式edit_entry和当前条目的ID属性(entry_id)来确定URL。
二、创建用户账户
建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。修改模型Topic,让每个主题都归属于特定用户。
1. 应用程序users
使用startapp命令来创建一个名为users的应用程序。
(ll_env)learning_log> python manage.py statapp users
创建一个名为users的目录,用dir查看其目录结构,应用程序learning_logs相同。
(ll_env) D:\django\learning_log>python manage.py startapp users
(1)将应用程序users添加到settings.py中
settings.py
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#我的应用程序
'learning_logs',
'users',
]
(2)包含应用程序users的URL
接下来,我们需要修改项目根目录中的urls.py,使其包含users定义的URL
learning_log/urls.py
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', include(admin.site.urls)),
path('users/', include('users.urls', namespace='users')), #code1
path('', include(('learning_logs.urls','learning_logs'), namespace='learning_logs')),
]
code1:包含应用程序users文件中的urls.py,这行代码与任何以单词users打头的URL都匹配。(如http://localhost:8000/users/login/),创建了命名空间users,以便将应用程序learning_logs的URL与应用程序users中的URL区分开来。
2. 登录页面
实现登录页面功能,使用Django提供的默认登录视图。
在learning_log/users/中,新建urls.py文件.
learning_log/users/urls.py
"""为应用程序users定义URL模式"""
from django.urls import path
from django.contrib.auth import views as auth_views #code1
from . import views
app_name='users'
urlpatterns = [
#登录页面
path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'), #code2
]
code1:导入views,并重命名为auth_views
code2:登录页面的URL模式与URLhttp://localhost:8000.users/login.html匹配。我们没有编写自己的视图函数,我们传递了一个实参template_name,告诉Django去哪里查找我们编写的模板。
(1) 模板login.html
用户请求登录页面时,Django使用默认视图login,我们依然要为这个页面提供模板。
在learning_log/users中,创建目录templates,在templates中创建目录users,在users中创建文件login.html
learning_log/users/templates/users/login.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %} <!--code1-->
<p>Your username and password didn't mach. Please try again. </p> <!--code2-->
{% endif %}
<form method = "post" action="{% url 'users:login' %}">
{% csrf_token %}
{{ form.as_p }} <!--code3-->
<button name="submit">log in</button> <!--code4-->
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/> <!--code5-->
</form>
{% endblock content %}
code1:如果error属性被设置,我们就显示一条错误信息,指出输入的用户名-密码与数据库中的用户名-密码不匹配。
code2:要让视图处理表单,将实参action设置为登录页面的URL。
code3:显示表单
code4:添加提交按钮
code5:包含一个隐藏的表单元素——“next”,实参value告诉Django在用户成功登录后将其重定向到什么地方。(这里是主页)
(2) 链接到登录页面
下面在base.html中添加到登录页面的链接,让所有页面都包含它。
添加判断,让登录页面嵌套在一个{% if %}
标签中,如果用户已经登录,则不显示这个链接。
base.html
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
<a href="{% url 'learning_logs:topics' %}">Topics</a>
{% if user.is_authenticated %} <!--code1-->
Hello,{{ user.username }}. <!--code2-->
{% else %}
<a href="{% url 'users:login' %}">log in<a> <!--code3-->
{% endif %}
</p>
{% block content %}{% endblock content %}
code1:每个模板都可使用变量user,其中的is_authenticated属性,判断了用户是否登录,并返回True/False
code2:我们向已登录的用户显示一条问候语,设置属性user.username。个性化问候语,让用户知道他已经登录。
code3:对于还未通过身份验证的用户,我们再显示一个到登录页面的链接。
(3) 使用登录页面
访问http://127.0.0.1:8000/admin/,注销用户。
访问http://localhost:8000/users/login,进入页面index。
未登录状态下显示log in
登录状态下显示Hello,ll_admin
3. 注销
提供一个让用户注销的途径。我们不创建用于注销的页面,让用户只需单击一个链接就能注销并返回到主页。
为注销定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销链接。
(1) 注销URL
下面为注销定义了URL模式,该模式与URLhttp://localhost:8000/users/logout/匹配
users/urls.py
--snip--
urlpatterns = [
# 登录页面
path('login/',auth_views.LoginView.as_view(template_name='users/login.html'),name='login'),
# 注销
path('logout/',views.logout_view, name='logout'),
]
这个URL模式将请求发送给函数logout_view()。
(2) 视图函数logout_view()
导入Django函数logout(),并调用它,再重定向到主页。
users/views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
#from django.core.urlresolvers import reverse
from django.urls import reverse
from django.contrib.auth import logout
# Create your views here.
def logout_view(request):
"""注销用户"""
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
导入函数logout()并调用,将request对象作为实参,然后重定向到主页。
(3) 链接到注销视图
在base.html中添加注销链接,放在标签{% if user.is_authenticated %}中,使得只有用户登录后才能看到它。
base.html
<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 %}
4. 注册页面
创建注册页面,使用Django提供的表单UserCreationForm。
(1)注册页面的URL模式
‘’‘定义learning_logs的URL模式’’’
users/urls.py
from django.urls import path
from . import views
app_name='learning_logs'
urlpatterns = [
# 主页
path('', views.index, name = 'index'),
# 显示所有的主题
path('topics/',views.topics,name='topics'),
# 特定主题的详细页面(?P<topic_id>\d+) 改为 /<topic_id>
path('topics/<topic_id>/',views.topic,name='topic'),
# 用于添加新主题的网页
path('new_topic/',views.new_topic,name = 'new_topic'),
# 用于添加新条目的页面
path('new_entry/<topic_id>/',views.new_entry,name = 'new_entry'),
#用于编辑条目的页面
path('edit_entry/<entry_id>/',views.edit_entry,name = 'edit_entry')
]
(2)视图函数register()
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
#from django.core.urlresolvers import reverse
from django.urls import reverse
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm
#from django.views.decorators.csrf import csrf_protect
# Create your views here.
def logout_view(request):
"""注销用户"""
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
#@csrf_protect
def register(request):
"""注册新用户"""
if request.method != 'POST':
#显示空的注册表单
form = UserCreationForm()
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)
(3)注册模板
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
<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 href="{% url 'users:login' %}">log in<a>
{% endif %}
</p>
{% block content %}{% endblock content %}
三、让用户拥有自己的数据
修改模型Topic,让每个主题都归属于特定用户。这会影响到条目,我们来限制对一些用户的访问。
1. 使用@login_required限制访问
Django提供装饰器@login_required,对于某些页面,只允许已登录的用户访问它们。装饰器(decorator)是放在函数定义前面的指令。
(1)限制主题对topics页面的访问
learning_logs/views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required #code1
from .models import Topic,Entry
from .forms import TopicForm,EntryForm
@login_required #code2
def topics(request):
"""显示所有的主题"""
topics = Topic.objects.order_by('date_added')
context = {'topics':topics}
return render(request, 'learning_logs/topics.html', context)
在末尾添加代码:
settings.py
# 我的设置
LOGIN_URL = '/users/login/'
如果未登录的用户请求装饰器@login_required的保护页面,Django将重定向至settings.py中LOGIN_URL指定的URL。
(2)全面限制对项目“学习笔记”的访问
learning_logs/views.py中,除了index()外的每个视图,都应用了装饰器@login_required。
views.py
@login_required
def topics(request):
--snip--
@login_required
def topics(request):
--snip--
@login_required
def topic(request, topic_id):
--snip--
@login_required
def new_topic(request):
--snip--
@login_required
def new_entry(request, topic_id):
--snip--
@login_required
def edit_entry(request,entry_id):
--snip--
2. 将数据关联到用户
(1) 修改模型Topic
model.py
from django.contrib.auth.models import User
class Topic(models.Model):
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User)
--snip--
(2) 确定当前有哪些用户
在shell会话中,查看用户数及其ID
(ll_env) D:\django\learning_log>python manage.py shell
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: ll_admin>, <User: aigeqin>, <User: qinaige>]>
>>> for user in User.objects.all():
... print(user.username, user.id)
...
ll_admin 1
aigeqin 2
qinaige 3
>>>
(3) 迁移数据库
通过用户ID迁移数据库,执行命令makemigrations,Django指出,我们试图给Topic添加一个必不可少(不可为空)的字段。
在(result1)处,Django给我们两个选择,给该字段赋默认值,或者退出并且在model.py中添加默认值。
code2:我们选择第一个选项
code3:Django让我们输入默认值,我们可以输入任何已经创建好的用户ID,在这里我们输入超级用户,所以输入了ID为1。
(ll_env) D:\django\learning_log>python manage.py makemigrations learning_logs #code1
You are trying to add a non-nullable field 'owner' to topic without a default; w
e can't do that (the database needs something to populate existing rows).
Please select a fix: #result1
1) Provide a one-off default now (will be set on all existing rows with a null
value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1 #code2
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g.
timezone.now
Type 'exit' to exit this prompt
>>> 1 #code3
Migrations for 'learning_logs':
learning_logs\migrations\0003_topic_owner.py
- Add field owner to topic
(ll_env) D:\django\learning_log>
接下Django使用ID为1,来迁移数据库,生成迁移文件0003_topic_owner.py,它在模型中添加字段owner。
(ll_env) D:\django\learning_log>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
Applying learning_logs.0003_topic_owner... OK
(ll_env) D:\django\learning_log>
在Django的shell会话中应用迁移,
(ll_env) D:\django\learning_log>python manage.py shell
>>> from learning_logs.models import Topic
>>> for topic in Topic.objects.all():
... print(topic,topic.owner)
...
Chess ll_admin
Rock Climbing ll_admin
sdf ll_admin
Add Form1 ll_admin
Add Form2 ll_admin
>>>
从learning_logs.modelsh中导入Topic,遍历所有主题,打印每个主题及其所属的用户。
3. 只允许用户访问自己的主题
在views.py中,对函数topics()做如下修改:
learning_logs/views.py
def topics(request):
"""显示所有的主题"""
#topics = Topic.objects.order_by('date_added')
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
context = {'topics':topics}
return render(request, 'learning_logs/topics.html', context)
用户登录后,request对象将有一个user属性,存储有关用户的信息。
修改后的代码,让Django只从数据库中获取owner属性为当前用户的Topic对象。
结果:
(1) 以既有主题关联到用户的身份登录,访问topics页面,可以看到所有主题。
(2)以另一个用户的身份登录,topics页面不会列出任何主题。但是还有漏洞,如下:
http://localhost:8000/topics/ 不能看到主题内容
http://localhost:8000/topics/1/ 可以看到主题内容
4. 保护用户的主题
修改learning_logs/views.py 下的topic函数
learning_logs/views.py
from django.http import HttpResponseRedirect, Http404
@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')
--snip--
首先导入异常Http404,在用户请求它不能查看的主题时,引发404异常。
收到主题请求后,在渲染网页前检查该主题是否属于当前登录的用户,如果不是,则引发Http404异常。
结果:
(2)以另一个用户的身份登录,topics页面不会列出任何主题。但是还有漏洞,如下:
http://localhost:8000/topics/ 不能看到主题内容
http://localhost:8000/topics/1/ 报404异常
5. 保护页面edit_entry
页面edit_entry的URL为http://localhost:8000/edit_entry/(entry_id)/,
其中entry_id是一个数字。下面来保护这个页面,进制用户通过熟人类似于前面的URL来访问其他用户的条目。
learning_logs/views.py
@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':
--snip--
我们获取指定的条目以及与之相关联的主题,然后检查主题的所有者是否是当前登录的用户,如果不是,就引发Http404异常。
6. 将新主题关联到当前用户
当前,http://localhost:8000/new_topic/添加新主题时,点击add_topic按钮,引发异常。错误信息为IntegrityError ,指出learning_logs_topic.user_id不能为NULL,Django想让我们创建新主题时,必须指出其owner字段的值。
IntegrityError at /new_topic/
NOT NULL constraint failed: learning_logs_topic.owner_id
Request Method: POST
Request URL: http://localhost:8000/new_topic/
Django Version: 2.2.6
Exception Type: IntegrityError
Exception Value:
NOT NULL constraint failed: learning_logs_topic.owner_id
修改代码,通过request对象获取当前用户,可将新主题关联到当前用户。
learning_logs/views.py
@login_required
def new_topic(request):
--snip--
else:
# POST提交的数据,对数据进行处理
form = TopicForm(request.POST)
if form.is_valid():
# form.save()
new_topic = form.save(commit=False) #code1
new_topic.owner = request.user #code2
new_topic.save() #code3
return HttpResponseRedirect(reverse('learning_logs:topics'))
--snip--
code1:首先调用form.save(),传递实参commit=False,先修改新主题并保存到数据库中。
code2:将新主题的owner()属性设置为当前用户
code3:用刚定义的主题实例调用save()。
现在主题包含所有必不可少的数据,将被成功的保存。每个用户只能访问自己的数据。
四、小结
学习使用表单来让用户添加新主题,新条目,编辑条目。
实现用户账户,登录,注销,使用UserCreationForm让用户能够创建新账户。
使用装饰器@login_required,禁止未登录的用户访问特定页面。
学习如何执行要求制定默认数据的数据迁移
学习修改视图函数,让用户只能看到属于他的数据。
使用方法fileter()来获取合适的数据
学习如何将请求的数据的所有者同当前登录的用户比较
五、遇到的问题:
(1)django2.x报错No module named ‘django.core.urlresolvers’
https://blog.csdn.net/weixin_35757704/article/details/78977753
原因就是:django2.0 把原来的 django.core.urlresolvers 包 更改为了 django.urls包,所以我们需要把导入的包都修改一下就可以了。
(2)Django报错 ‘learning_logs ’is not a registered namespace,
https://blog.csdn.net/mukvintt/article/details/80320027
(3)ImportError: cannot import name ‘login’ from ‘django.contrib.auth.views’
https://blog.csdn.net/steventian72/article/details/85225631
from django.contrib.auth.views import login
修改为:
from django.contrib.auth import login
(4)Django报错:login() got an unexpected keyword argument ‘template_name’
https://blog.csdn.net/steventian72/article/details/85225631
https://docs.djangoproject.com/en/2.2/topics/auth/default/
修改:
learning_log/users.py
"""为应用程序users定义URL模式"""
from django.urls import path
# from django.contrib.auth.views import login,修改
#from django.contrib.auth.login import login
#from django.contrib.auth.views import loginView
from django.contrib.auth import views as auth_views
from . import views
app_name='users'
urlpatterns = [
#登录页面
#path('login/',login,{'template_name':'users/login.html'},name='login'),
#path('login/',auth_views.loginView.as_view(template_name='users/login.html'),name='login'),
path('login/',auth_views.LoginView.as_view(template_name='users/login.html'),name='login'),
]
(5) 连接错误:ConnectionAbortedError: [WinError 10053] 您的主机中的软件中止了一个已建立的连接