前言
欢迎来到本系列教程的第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 <pk> \ d +)/ topics /(?P <topic_pk> \ 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 %} {% 块 面包屑 %}
<LI 类= “面包屑项目” > <a href= “{% URL'home'%}”>板</A> </ LI> <LI 类= “面包屑项目” > <a href= “{%URL'board_topics'topic.board.pk %}”>{{ topic.board.name }} </A> </ LI> <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 (用户名= '约翰' , 电子邮件= 'john@doe.com' , 密码= '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 %} {% 块 面包屑 %}
<LI 类= “面包屑项目” > <a href= “{% URL'home'%}”>板</A> </ LI> <LI 类= “面包屑项目” > <a href= “{%URL'board_topics'topic.board.pk %}”>{{ topic.board.name }} </A> </ LI> <LI 类= “面包屑项活性” > {{ 话题。subject }} </ li> {%endblock %} {% block content %} <DIV 类= “MB-4” >
<a href= "#" class= "btn btn-primary" role= "button">回复</A> </ DIV> {% for post in topic.posts.all %}
<div class = “card mb-2” >
<div class = “card-body p-3” >
<div class = “row” >
<div class = “col -2" >
<IMG SRC = “ {% 静态 'IMG / avatar.svg' %} ” ALT = “ {{ post.created_by.username }} ” 类= “W-100” >
<小>文章:{{ post.created_by.posts。count }} </ small>
</ div>
<div class = “col-10” >
<div class = “row mb-3” >
<div class = “col-6” >
<strong class = “text-muted” > {{ post.created_by.username }} </ strong>
</ div>
<div class = “col-6 text-right” >
<small class = “text-muted” > {{ post.created_at }} </ small>
</ div>
</ div>
{{ post.message }}
{% if post。created_by == user %}
<div 类= “MT-3” >
<a href= "#" class= "btn btn-primary btn-sm" role= "button">编辑</A> </ 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 %}
<TR>
<TD> <a href= “{% URL'topic_posts'board.pk topic.pk %}”>{{ topic.subject }} </A> </ td> <td> {{ topic.starter.username }} </ td> <td> 0 </ td> <td> 0 </ td> <td> {{ topic.last_updated }} </ td> </ tr> {%endfor %}
主题回复功能
现在让我们来实现回复帖子的功能,以便我们可以添加更多的数据和改进功能实现与单元测试。
添加新的URL路由:
myproject / urls.py(完整代码)
url(r'^ boards /(?P <pk> \ d +)/ topics /(?P <topic_pk> \ 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 ()
返回 redirect ('topic_posts' , pk = pk , topic_pk = topic_pk )
else :
form = PostForm ()
return render (request , 'reply_topic.html' , { 'topic' : topic , 'form': form })
现在我们再会到new_topic视图函数,更新重定向地址(标记为 #TODO 的地方)
@ login_required
DEF new_topic (请求, PK ):
板= get_object_or_404 (局, PK = PK )
如果 request.method == 'POST' :
形式= NewTopicForm ( request.POST )
如果 form.is_valid () :
主题= form.save (提交= False )
#code suppress ...
return redirect ('topic_posts' ,pk = pk , topic_pk = topic.pk ) #< - here
#code suppress ...
值得注意的是:在视图函数replay_topic中,我们使用topic_pk
,因为我们引用的是函数的关键字参数,而在new_topic视图中,我们使用的是topic.pk
,因为topic
的英文一个对象(主题模型的实例对象),.pk
是这个实例对象的一个属性,这两种细微的差别,其实区别很大,别搞混了。
回复页面模版的一个版本:
模板/ reply_topic.html
{% 延伸 'base.html文件' %} {% load static %} {% block title %}发表回复{% endblock %} {% 块 面包屑 %}
<LI 类= “面包屑项目” > <a href= “{% URL'home'%}”>板</A> </ LI> <LI 类= “面包屑项目” > <a href= “{%URL'board_topics'topic.board.pk %}”>{{ topic.board.name }} </A> </ LI> <LI 类= “面包屑项目” > <A HREF = “{%URL 'topic_posts' topic.board.pk topic.pk %} “ > {{ topic.subject }} </a> </ li>
<li class = ”breadcrumb-item active“ >发表回复</ li>
{% endblock %} {% block content %} <形式 方法= “POST” 类= “MB-4” >
{% csrf_token %}
{% 包括 '包括/ form.html' %}
<按钮 类型= “提交” 类= “BTN BTN-成功” >发布回复</ button>
</ form> {% for post in topic.posts.all %}
<div class = “card mb-2” >
<div class = “card-body p-3” >
<div class = “row mb-3” >
<div class = “col-6” >
<strong class = “text-muted” > {{ post.created_by.username }} </ strong>
</ div>
<div class = “col-6 text-right” >
<small class = “text-muted” > {{ post。created_at }} </ small>
</ div>
</ div>
{{ post.message }}
</ div>
</ div>
{% endfor %} {% endblock %}
提交回复之后,用户会跳回主题的回复列表:
我们可以改变第一条帖子的样式,使得它在页面上更突出:
templates / topic_posts.html(完整代码)
{% 为 交 在 topic.posts.all %}
<DIV 类= “卡MB-2 {% 如果 for循环。首先 %} 边界暗{% ENDIF %} ” >
{% 如果 for循环。首先 %}
<DIV 类= “card-header text-white bg-dark py-2 px-3” > {{ topic.subject }} </ div>
{% endif %}
<div class = “card-body p-3” >
<! - 代码被抑制 - >
</ div>
</ div>
{% endfor %}
现在对于测试,已经实现标准化流程了,就像我们迄今为止所做的一样。在板/测试目录中创建一个新文件 test_view_reply_topic.py:
boards / tests / test_view_reply_topic.py (完整代码)
来自 django.contrib.auth.models 导入 用户
来自 django.test 导入 来自django.urls的TestCase
导入反向来自..models 导入板,帖子,主题来自..views import reply_topic class ReplyTopicTestCase (TestCase ):
'''
在所有`reply_topic`视图测试
'''
def setUp (self ):
self中使用的基本测试用例。董事会 = 董事会。对象。create (name = 'Django' , description = 'Django board。' )
self 。用户名 = “约翰”
自我。口令 = '123'
的用户 = 用户。对象。create_user (用户名= 自我。用户名, 电子邮件= 'john@doe.com' , 密码= 自我。密码)
自我。topic = 主题。对象。创建(主题= “你好,世界” , 董事会= 自我。板, 首发= 用户)
发布。对象。创建(消息='Lorem ipsum dolor sit amet' , topic = self 。topic , created_by = user )
self 。URL = 反向('reply_topic' , kwargs = { 'pk'文件: 自我。板。PK , 'topic_pk' : 自我。主题。PK }) class LoginRequiredReplyTopicTests (ReplyTopicTestCase ):
#... class ReplyTopicTests (ReplyTopicTestCase ):
#... class SuccessfulReplyTopicTests (ReplyTopicTestCase ):
#... class InvalidReplyTopicTests (ReplyTopicTestCase ):
#...
这里的精髓在于自定义了测试用例基类ReplyTopicTestCase。然后所有四个类将继承这个测试用例。
首先,测试我们是否视图受@login_required
装饰器保护,然后检查HTML输入,状态码。最后,我们测试一个有效和无效的表单提交。
查询集(查询结果集)
现在我们花点时间来探索关于模型的API。首先,我们来改进主页:
有3个任务:
- 显示每个板块的总主题数
- 显示每个板块的总回复数
- 显示每个板块的最后发布者和日期
在实现这些功能前,我们先使用Python的终端
因为我们要在Python __str__
终端尝试,所以,把所有的模型定义一个 方法是个好主意
boards / models.py(完整代码)
来自 django.db的 导入 模型
来自 django.utils.text import Truncator 一流的 董事会(型号。型号):
#...
DEF __str__ (个体经营):
回归 自我。名称 类 主题(机型。型号):
#...
DEF __str__ (个体经营):
回归 自我。学科 班 后(型号。型号):
#...
DEF __str__ (个体经营):
TRUNCATED_MESSAGE = 舍(自我。消息)
返回 TRUNCATED_MESSAGE 。字符(30 )
在Post模型中,使用了 Truncator
工具类,这是将一个长字符串截取为任意长度字符的简便方法(这里我们使用30个字符)
现在打开Python shell
python 管理。来自boards.models 导入板的py shell #首先从数据库
板 = Board 获取一个板实例。对象。get (name = 'Django' )
这三个任务中最简单的一个就是获取当前版块的总主题数,因为Topic和Baoard是直接关联的。
board.topics.all()
<QuerySet [<主题:大家好!>,<主题:测试>,<主题:测试新帖子>,<主题:您好>]> board.topics.count()
4
就这样子。
现在统计一个版块下面的回复数量有点麻烦,因为回复并没有和Board直接关联
从 董事会。型号 进口 邮政 发布。对象。所有()
< 查询集 [ < 邮政: 这 是 我的 第一 主题.. :-) > , < 邮政: 测试。> , < 帖子: 嗨 大家!> ,
< Post : 这里的新 测试 !> ,< 帖子:测试的新回复功能!> , < Post : Lorem ipsum dolor sit amet ,... > ,
< Post : hi there> , < Post : test> , < Post : Testing .. > , < Post : some reply> , < Post : Random random 。>
] > 发布。对象。count ()
11
这里一共11个回复,但是它并不全部属于“Django”这个版块的。
我们可以这样来过滤
从 董事会。型号 进口 板, 邮政 董事会 = 董事会。对象。get (name ='Django' ) 发布。对象。过滤器(topic__board = 板)
< 查询集 [ < 邮政: 这 是 我的 第一 主题.. :-) > , < 邮政: 测试。> , < 帖子: 喜 有> ,
< 帖子: 嗨 大家!> , < Post : Lorem ipsum dolor 坐下 来,... > , < 帖子: 这里的新 测试 !> ,< 帖子:测试的新回复功能!> ] > 发布。对象。过滤器(topic__board = board )。count ()
7
双下划线的topic__board
用于通过模型关系来定位,在内部,Django在Board-Topic-Post之间构建了桥梁,构建SQL查询来获取属于指定版块下面的帖子回复。
最后一个任务是标识版块下面的最后一条回复
在`created_at`字段中排序,获取最新的第一个
帖子。对象。过滤器(topic__board = board )。order_by ('-created_at' )
< QuerySet [ < Post : testing> , < Post : new post> , < Post : hi there> , < Post : Lorem ipsum dolor sit amet ,... > ,
< Post : 测试 的 新 回复 功能!> , < Post : 这里的新 测试 !> ,< 帖子:嗨大家!> ,< Post :test 。> ,< 帖子:这是我的第一个话题.. :-) > ] > #我们可以使用`第()`方法只获取其结果是我们感兴趣的
帖子。对象。过滤器(topic__board = board )。order_by ('-created_at' )。first ()
< Post : testing >
太棒了,现在我们来实现它
boards / models.py (完整代码)
来自 django.db 导入 模型 一流的 董事会(型号。型号):
名称 = 机型。CharField (max_length = 30 , unique = True )
description = models 。CharField (max_length = 100 ) def __str __ (self ):
返回 自我。名称 def get_posts_count (self ):
return post 。对象。过滤器(topic__board = self )。count () def get_last_post (self ):
return post 。对象。过滤器(topic__board = self )。order_by ('-created_at' )。第一个()
注意,我们使用的是self
,因为这是Board的一个实例方法,所以我们就用这个Board实例来过滤这个QuerySet
现在我们可以改进主页的HTML模板来显示这些新的信息
模板/ home.html做为
{% 延伸 'base.html文件' %} {% block breadcrumb %}
<li class = “breadcrumb-item active” > Boards </ li>
{% endblock %} {% block content %}
<table class = “table” >
<thead class = “thead-inverse” >
<tr>
<th> Board </ th>
<th>帖子</ th>
<th>主题</ th >
<TH>最后发表</次>
</ TR>
</ THEAD>
<TBODY>
{% 为 板 在 板 %}
<TR>
<TD>
<A HREF = “ {% URL 'board_topics' 板。pk %} “ > {{ board.name }} </A>
<小 类= “文本静音d区” > {{ board.description }} </小>
</ TD>
<TD 类= “对齐中间人” >
{{ board.get_posts_count } }
</ td>
<td class = “align-middle” >
{{ board.topics.count }}
</ td>
<td class = “align-middle” >
{% with post = board.get_last_post %}
<small >
<a href = “{% URL 'topic_posts' board.pk post.topic.pk %} “ >
通过{{ post.created_by.username }}在{{ post.created_at }}
</A>
</小>
{% ENDWITH %}
</ TD>
</ TR>
{% endfor %}
</ tbody>
</ table>
{% endblock %}
现在是这样的效果
运行测试:
python manage.py测试
为别名'default'创建测试数据库...
系统检查发现没有问题(0静音)。
.................................................. ..... EEE ......................
================================================== ====================
错误:test_home_url_resolves_home_view(boards.tests.test_view_home.HomeTests)
-------------------------------------------------- --------------------
django.urls.exceptions.NoReverseMatch:反向'topic_posts',找不到参数'(1,'')'。尝试了1种模式:['boards /(?P <pk> \\ d +)/ topics /(?P <topic_pk> \\ d +)/ $'] ================================================== ====================
错误:test_home_view_contains_link_to_topics_page(boards.tests.test_view_home.HomeTests)
-------------------------------------------------- --------------------
django.urls.exceptions.NoReverseMatch:反向'topic_posts',找不到参数'(1,'')'。尝试了1种模式:['boards /(?P <pk> \\ d +)/ topics /(?P <topic_pk> \\ d +)/ $'] ================================================== ====================
错误:test_home_view_status_code(boards.tests.test_view_home.HomeTests)
-------------------------------------------------- --------------------
django.urls.exceptions.NoReverseMatch:反向'topic_posts',找不到参数'(1,'')'。尝试了1种模式:['boards /(?P <pk> \\ d +)/ topics /(?P <topic_pk> \\ d +)/ $'] -------------------------------------------------- --------------------
在5.663s中进行了80次测试 失败(错误= 3)
销毁别名'default'的测试数据库...
看起来好像有问题,如果没有回复的时候程序会崩溃
模板/ home.html做为
{% 与 柱= board.get_last_post %}
{% 如果 交 %}
<小>
<a href= “{% URL'topic_posts'board.pk post.topic.pk %}”>
通过{{ post.created_by.username }}在{{ post.created_at }} </A> </小> {%其他%} <小类= “文本静音” > <EM>没有发布信息。</ em> </ small> 结束 %}
再次运行测试:
python manage.py测试
为别名'default'创建测试数据库...
系统检查发现没有问题(0静音)。
.................................................. ..............................
-------------------------------------------------- --------------------
在5.630s中进行80次测试 好
销毁别名'default'的测试数据库...
我添加一个没有任何消息的版块,用于检查这个 “空消息”
现在是时候来改进回复列表页面了。
现在,我将告诉你另外一种方法来统计回复的数量,用一种更高效的方式
和之前一样,首先在Python shell中尝试
python manage.py shell
来自于 django.db.models 从board.models 导入板导入 计数 董事会 = 董事会。对象。get (name = 'Django' ) 主题 = 董事会。主题。order_by ('-last_updated' )。注释(回复= 计数('帖子' )) 为 主题 的 主题:
打印(主题。回复) 2
4
2
1
这里我们使用annotate
,查询集将即时生成一个新的列,这个新的列,将被翻译成一个属性,通过柯林斯 topic.replies
来访问,它包含了指定主题下的回复数。
我们来做一个小小的修复,因为回复里面不应该包括发起者的帖子
topics = board.topics.order_by(' - last_updated')。annotate(replylies = Count('posts') - 1) 主题中的主题:
打印(topic.replies) 1
3
1
0
很酷,对不对?
boards / views.py (完整代码)
来自 django.db.models 从django.shortcuts 导入 计数
导入get_object_or_404 ,从.models 导入板渲染 def board_topics (request , pk ):
board = get_object_or_404 (Board , pk = pk )
topics = board 。主题。order_by ('-last_updated' )。注释(回复= 计数('信息' ) - 1 )
返回 渲染(请求, 'topics.html' , { '板' : 板, '主题' : 主题})
templates / topics.html(完整代码)
{% 为 主题 在 主题 %}
<TR>
<TD> <a href= “{% URL'topic_posts'board.pk topic.pk %}”>{{ topic.subject }} </A> </ TD> <td> {{ topic.starter.username }} </ td> <td> {{ topic.replies }} </ td> <td> 0 </ td> <td> {{ topic.last_updated }} </ td> </ tr> {%endfor %}
下一步是修复主题的查看次数,但是,现在我们需要添加一个新的字段。
迁移
迁移(迁移)是Django的做网站开发的基本组成部分,它使得我们在演进应用的机型时,它能使得模型文件与数据库保持同步
当我们第一次运行命令 python manage.py migrate
的时候,Django会抓取所有迁移文件然后生成数据库schema。
当Django应用了迁移之后,有一个特殊的表叫做django_migrations,在这个表中,Django注册了所有已经的迁移记录。
所以,如果我们重新运行命令:
python manage.py migrate
执行的操作:
应用所有迁移:admin,auth,boards,contenttypes,sessions
正在运行迁移:
无需迁移。
Django知道没什么事可做了。
现在我们添加在Topic模型中添加一个新的字段:
boards / models.py(完整代码)
类 主题(机型。型号):
主题 = 模型。CharField (max_length = 255 )
last_updated = models 。DateTimeField (auto_now_add = True )
board = models 。ForeignKey (Board , related_name = 'topics' )
starter = models 。ForeignKey的(用户, related_name= '主题' )
views = models 。PositiveIntegerField (默认值= 0 ) # < - 这里 def __str __ (self ):
返回 自我。学科
我们添加了一个PositiveIntegerField
,因为这个字段将要存储的是页面的浏览量,不可能是一个负数
在我们可以使用这个新字段前,我们必须更新数据库架构,执行命令 makemigrations
python manage.py makemigrations “董事会”的迁移:
板/迁移/ 0003_topic_views.py
- 为主题添加字段视图
makemigrations
自动会生成0003_topic_views.py文件,将用于修改数据库(添加一个视图字段)
运行现在命令 migrate
来应用迁移
python manage.py migrate 执行的操作:
应用所有迁移:admin,auth,boards,contenttypes,sessions
正在运行迁移:
应用boards.0003_topic_views ...好的
现在我们可以用它来追踪指定主题被阅读了多少次
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 )
主题。views + = 1个
主题。save ()
返回 渲染(request , 'topic_posts.html' , { 'topic' : topic })
templates / topics.html(完整代码)
{% 为 主题 在 主题 %}
<TR>
<TD> <a href= “{% URL'topic_posts'board.pk topic.pk %}”>{{ topic.subject }} </A> </ TD> <td> {{ topic.starter.username }} </ td> <td> {{ topic.replies }} </ td> <td> {{ topic.views }} </ td> <! - here - - > <td> {{ topic.last_updated }} </ td> </ tr>{%endfor %}
现在打开一个主题,刷新页面几次,然后你会看到有页面阅读次数统计了。
总结
在这节课中,我们在留言板的基础功能上取得了一些进步,还剩下一些东西等待去实现,比如:编辑帖子,我的账户(更改个人信息)等等之后我们将提供降价语法和列表的分页功能。
下一节主要使用基于类的视图来解决这些问题,在之后,我们将学习到如何部署应用程序到网络服务器中去。
这部分的完整代码可以访问:https://github.com/sibtc/django-beginners-guide/tree/v0.5-lw