前言
欢迎来到本系列教程的第5部分,在这节课,我们将学习如何保护视图防止未登录的用户访问,以及在视图和表单中访问已经登录的用户,我们还将实现主题列表和回复列表视图,最后,将探索Django ORM的一些特性和数据迁移的简单介绍。
保护视图
我们必须保护视图防止那些未认证(登录)的用户访问,下面是发起一个新话题的页面
在上图中,用户还没有登录,尽管他们可以看到页面和表单.Django有一个内置的 视图装饰器 来避免它被未登录的用户访问:
boards / views.py(完整代码)
来自 django.contrib.auth.decorators 导入 login_required
@login_required
def new_topic (request , pk ): #...
现在如果用户没有登录,将被重定向到登录页面:
注意查询字符串 ?next = / boards / 1 / new / ,我们可以改进登录模板以便利用 next 变量来改进我们的用户体验,(译注:实际上这步操作不加也没问题)
配置登录后的重定向地址
templates / login.html (查看完整内容)
<形式 方法= “POST” 的novalidate >
{% csrf_token %} <输入 类型= “隐藏” 名称= “下一个” 的值= “ { { 下次 }} ” > {% 包括 '包括/ form.html' %} <按钮 type = “submit” class = “btn btn-primary btn-block” >登录 button> form>
现在尝试登录,登录成功后,应用程序会跳转到原来所在的位置。
下一个 参数是内置功能的一部分(译注:详情请参考Django的官方文档)
测试
添加现在一个测试用例确保主题发布视图被 @login_required
装饰器保护了,不过,我们还是先来重构一下 板/测试/ test_views.py 文件。
把test_views.py拆分3分类中翻译个文件:
- test_view_home.py 包含HomeTests类(完整代码)
- test_view_board_topics.py 包含BoardTopicsTests类(完整代码)
- test_view_new_topic.py 包含NewTopicTests类(完整代码)
我的项目/ | - myproject / | | - 帐户/ | | - 板/ | | | - 迁移/ | | | - templatetags / | | | - 测试/ | | | | - __init__.py | | | | - test_templatetags.py | | | | - test_view_home.py < - 这里 | | | | - test_view_board_topics.py < - 这里 | | | + - test_view_new_topic.py < - 在这里 | | | - __init__.py | | | - admin.py | | | - apps.py | | | - models.py | | + - views.py | | - myproject / | | - 静态/ | | - templates / | | - db.sqlite3 | + - manage.py + - venv /
重新运行测试,确保一切正常。
在现在 test_view_new_topic.py 中添加一个新测试用例,检查用来试图是否被@login_required
保护:
boards / tests / test_view_new_topic.py (完整代码)
来自 django.test的 导入 来自django.urls的TestCase
从..models 导入板导入反向 class LoginRequiredNewTopicTests (TestCase ): def setUp (self ): Board 。对象。create (name = 'Django' , description = 'Django board。' ) self 。url = reverse ('new_topic' , kwargs = { 'pk' : 1 }) self 。响应 = 自我。客户。得到(自我。url ) def test_redirection (self ): login_url = reverse ('login' ) self 。assertRedirects (自我。响应, '{} LOGIN_URL?下一= {URL}' 。格式(LOGIN_URL = LOGIN_URL , URL = 自我。URL ))
在测试用例中,我们尝试在没有登录的情况下发送请求给 新主题 视图,期待的结果是请求重定向到登录页面。
访问已登录用户
现在我么可以改进 new_topic 视图,将发布主题的用户设置当前登录的用户,取代之前直接从数据库查询出来的第一个用户,之前这份代码是临时的,因为那时候还没有方法去获取登录用户,但是现在可以了:
boards / views.py (完整代码)
来自 django.contrib.auth.decorators 从django.shortcuts 导入 login_required
导入get_object_or_404 ,重定向,渲染 from .forms 从.models import Board ,Post 导入 NewTopicForm @login_required def new_topic (request , pk ): board = get_object_or_404 (Board , pk = pk ) if request 。方法 == 'POST' : 形式 = NewTopicForm (请求。POST ) 如果 形式。is_valid (): topic = form 。save (commit = False ) 主题。板 = 董事会 主题。starter = 请求。用户 #< - 这里的 主题。保存() 发布。对象。创建( 消息= 形式。cleaned_data 。获得('信息' ), 话题= 主题, CREATED_BY = 请求。用户 #< -这里 ) 返回 重定向('board_topics' , PK = 板。PK ) #TODO:重定向到所创建的主题页面 否则: 形式 = NewTopicForm () 返回 渲染(请求, 'new_topic.html' , { '板' : 板, '形式' : 形式})
我们可以添加一个新的主题快速验证一下:
主题回复列表
现在我们花点时间来实现主题的回复列表页面,先来看一下下面的线框图:
首先我们需要写URL路由:
myproject / urls.py(完成代码)
url(r'^ boards /(?P\ d +)/ topics /(?P \ d +)/ $',views.topic_posts,name ='topic_posts'),
有两个关键字参数,pk
用于唯一标识版块(局),topic_pk
用于唯一标识该回复来自哪个主题。
boards / views.py(完整代码)
从 django.shortcuts 进口 get_object_or_404 , 使 从 .models 导入 主题 def topic_posts (request , pk , topic_pk ): topic = get_object_or_404 (主题, board__pk = pk , pk = topic_pk ) return render (request , 'topic_posts.html' , { 'topic' : topic })
注意我们正在间接地获取当前的版块,记住,主题(主题)模型是关联版块(局)模型的,所以我们可以访问当前的版块,你将在下一个代码段中看到:
templates / topic_posts.html(完整代码)
{% 延伸 'base.html文件' %}
{% block title %} { { topic.subject }} {% endblock %} {% 块 面包屑 %} 类= “面包屑项目” > href= “{% URL'home'%}”>板 LI> 类= “面包屑项目” > href= “{%URL'board_topics'topic.board.pk %}”>{ { topic.board.name }} LI> 类= “面包屑项活性” > { { 话题。subject }} li> {%endblock %} {% block content %} {% endblock %}
现在你会看到我们在模板中 board.name
被替换掉了,在导航条,是使用的主题的属性:topic.board.name
。
现在我们给topic_posts添加一个新的测试文件:
板/测试/ test_view_topic_posts.py
从 django.contrib.auth.models 进口 用户
从 django.test 进口 的TestCase 从 django.urls 进口 的决心, 相反 来自 ..models 导入 Board , Post , Topic from ..views import topic_posts class TopicPostsTests (TestCase ): def setUp (self ): board = Board 。对象。create (name = 'Django' , description = 'Django board。' ) user = User 。对象。create_user (用户名= '约翰' , 电子邮件= '[email protected]' , 密码= '123' ) 主题 = 主题。对象。create (subject = 'Hello,world' , board = board , starter = user ) 发布。对象。创建(消息= 'Lorem存有悲坐阿梅特' , 主题= 主题, CREATED_BY = 用户) 的url = 反向('topic_posts' , kwargs = { 'pk'文件: 板。PK , 'topic_pk': 主题。pk }) 自我。响应 = 自我。客户。得到(网址) def test_status_code (self ): self 。的assertEquals (自我。响应。STATUS_CODE , 200 ) def test_view_function (self ): view = resolve ('/ boards / 1 / topics / 1 /' ) self 。的assertEquals (视图。FUNC , topic_posts )
注意到,设置函数变得越来越复杂,我们可以创建一个minxin或者抽象类来重用这些代码,我们也可以使用第三方库来初始化设置一些测试数据,来减少这些样板代码。
同时,我们已经有了大量的测试用例,运行速度开始逐渐变得慢起来,我们可以通过用测试套件的方式测试指定的应用程序。
python manage.py测试板
为别名'default'创建测试数据库... 系统检查发现没有问题(0静音)。 ....................... -------------------------------------------------- -------------------- 在1.246s中进行23次测试 好 销毁别名'default'的测试数据库...
我们还可以只运行指定的测试文件
python manage.py test boards.tests.test_view_topic_posts
为别名'default'创建测试数据库... 系统检查发现没有问题(0静音)。 .. -------------------------------------------------- -------------------- 在0.129s中进行2次测试 好 销毁别名'default'的测试数据库...
抑或是指定单个测试用例
python manage.py test boards.tests.test_view_topic_posts.TopicPostsTests.test_status_code
为别名'default'创建测试数据库... 系统检查发现没有问题(0静音)。 。 -------------------------------------------------- -------------------- 在0.100s内进行1次测试 好 销毁别名'default'的测试数据库...
很酷,是不是?
继续前行!
在topic_posts.html页面中,我们可以创建一个用于循环迭代主题下的回复
模板/ topic_posts.html
{% 延伸 'base.html文件' %}
{% load static %} {% block title %} { { topic.subject }} {% endblock %} {% 块 面包屑 %} 类= “面包屑项目” > href= “{% URL'home'%}”>板 LI> 类= “面包屑项目” > href= “{%URL'board_topics'topic.board.pk %}”>{ { topic.board.name }} LI> 类= “面包屑项活性” > { { 话题。subject }} li> {%endblock %} {% block content %} 类= “MB-4” > href= "#" class= "btn btn-primary" role= "button">回复 DIV> {% for post in topic.posts.all %} class = “card mb-2” > class = “card-body p-3” > class = “row” > class = “col -2" > SRC = “ {% 静态 'IMG / avatar.svg' %} ” ALT = “ { { post.created_by.username }} ” 类= “W-100” > <小>文章:{ { post.created_by.posts。count }} small> div> class = “col-10” > class = “row mb-3” > class = “col-6” > class = “text-muted” > { { post.created_by.username }} strong> div> class = “col-6 text-right” > class = “text-muted” > { { post.created_at }} small> div> div> { { post.message }} {% if post。created_by == user %} 类= “MT-3” > href= "#" class= "btn btn-primary btn-sm" role= "button">编辑 DIV> {%ENDIF %} DIV> div> div> div> {%endfor %} {% endblock %}
因为我们现在还没有任何方法去上传用户图片,所以先放一张空的图片,我从 IconFinder下载了一张免费图片,然后保存在项目的static / img目录。
我们还没有真正探索过的Django的ORM,代码但{ { post.created_by.posts.count }}
在数据库中会执行一个select count
查询。尽管结果是正确的,但不是一个好方法。因为它在数据库中造成了多次不必要的查询。不过现在不用担心,先专注于如何与应用程序进行交互稍后,我们将改进此代码,以及如何改进那些复杂笨重的查询(译注:过早优化是万恶之源)。
另一个有意思的地方是我们正在测试当前帖子是否属于当前登录用户:{% if post.created_by == user %}
,我们只给帖子的拥有者显示编辑按钮。
因为我们现在要在主题页面添加一个URL路由到主题的帖子列表,更新topic.html模版,加上一个链接:
templates / topics.html (完整代码)
{% 为 主题 在 board.topics.all %} href= “{% URL'topic_posts'board.pk topic.pk %}”>{ { topic.subject }} td> { { topic.starter.username }} td> 0 td> 0 td> { { topic.last_updated }} td> tr> {%endfor %}
主题回复功能
现在让我们来实现回复帖子的功能,以便我们可以添加更多的数据和改进功能实现与单元测试。
添加新的URL路由:
myproject / urls.py(完整代码)
url(r'^ boards /(?P \ d +)/ topics /(?P \ d +)/ reply / $',views.reply_topic,name ='reply_topic'),
给回帖创建一个新的表单:
boards / forms.py (完整代码)
从 Django的 进口 形式
从 .models 导入 后 类 PostForm (形式。的ModelForm ): 类 元: 模型 = 邮政 字段 = [ '消息' , ]
新一个受的@login_required
保护的视图,以及简单的表单处理逻辑
boards / views.py(完整代码)
从 django.contrib.auth.decorators 导入已 login_required
从 django.shortcuts 进口 get_object_or_404 , 重定向, 使 从 者,恕不 导入 PostForm 从 .models 导入 主题 @login_required def reply_topic (request , pk , topic_pk ): topic = get_object_or_404 (主题, board__pk = pk , pk = topic_pk ) 如果 请求。方法 == 'POST' : 形式 = PostForm (请求。POST ) 如果 形式。is_valid (): post = form 。保存(提交= 假) 帖子。topic = 主题 帖子。created_by = request 。用户 发帖。save ()