使用高级特性来优化你的博客
在上一章中,你创建了一个基础的博客应用。现在你将要改造它成为一个功能更加齐全的博客,利用一些高级的特性例如添加评论,给帖子打上tag,检索出相似的帖子。在本章中,你将会学习以下几点:
- 通过models创建表单
- 构建复杂的QuerySets
现在我们准备为博客创建一个评论系统,这样用户可以在帖子上进行评论。你需要做到以下几点来创建一个评论系统:
- 创建一个modle保存评论
- 创建一表单用来提交平路你和验证输入的数据
- 添加一个view来处理表单和保存新的评论到数据库中
- 编辑帖子的详情template来展示评论列表以及用来添加新评论的表单
首先,让我们创建一个model来存储评论。打开你博客应用下的models.py文件添加如下代码:
class Comment(models.Model):
name = models.CharField(max_length=80)
post = models.ForeignKey(Post, related_name='comments')
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ('created',)
def __str__(self):
return 'Comment by {} on {}'.format(self.name, self.post)
以上就是我们的Comment model。它包含了一个外键用来关联一个单独的帖子。在Comment model中定义多对单的关系是因为每一条评论只能在一个帖子下生成,而每一个帖子又可能包含多个评论。related_name属性允许我们命名这个属性这样我们就可以使用这个关系从有关联的对象来读取这儿。定义好这个之后,我们可以从一条评论来取到对应的帖子通过使用 comment.post
和取回一个帖子所有的评论通过使用post.comments.all(
。如果你没有定义related_name属性,Django会使用这个model的命名加上_set(例如:comment_set)来命名关联对象读取这儿的manager。
你可以学习更多关于多对单的关系通过访问 多对单的关系。
我们有包含一个active布尔字段用来手动使不好的评论无效不展示。我们使用created字段使评论默认根据创建时间来进行排序。
你刚创建的这个新的Comment model
并没有同步到数据库中。运行以下命令通过新的model生成一个新的数据迁移,并创建数据库:
python manage.py makemigrations blog
python manage.py migrate
添加站点管理
现在,我们可以添加我们新的model到管理站点中通过简单的接口来管理评论。打开博客应用下的admin.py
文件,添加如下内容:
from .models import Post, Comment
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'email', 'body')
admin.site.register(Comment, CommentAdmin)
访问 http://127.0.0.1:8000/admin/ 会看多多出一个 Comments
;
通过models创建表单
我们仍然需要创建一个表单可以让我们的用户在博客帖子下进行评论。请记住,Django有两个用来创建表单的基础类:Form和ModelForm。你已经使用过前者可以让用户通过email来分享帖子。在下面的例子中,你将需要使用ModelForm因为你必须从你的Comment model中创建一个动态的表单。编辑博客应用下的forms.py,添加如下代码:
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
从model中创建表单,我们只需要在这个表单的Meta类中声明使用哪个model来构建表单。Django将会解析model并为我们动态的创建表单。每一种model字段类型都有对应的默认表单字段类型。默认的,Django创建的表单包含model中包含的每个字段。当然,你可以明确的告诉框架你想在你的表单中包含哪些字段通过使用fields列,或者定义哪些字段你不需要的通过使用exclude列。对于我们的CommentForm,我们在表单中只需要name,email,和body字段,因为我们只需要用到这3个字段让我们的用户来填写。
在views中操作ModelForms
我们会使用帖子的详情view来实例化表单,能更简单的处理它。编辑models.py文件,导入Comment modle和CommentForm表单,并且修改post_detail view如下所示:
from .forms import EmailPostForm, CommentForm
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
# List of active comments for this post
comments = post.comments.filter(active=True)
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post = post
new_comment.save()
else:
comment_form = CommentForm()
return render(request,'blog/post/detail.html',{'post': post, 'comments': comments, 'comment_form': comment_form})
让我们来回顾下我们刚才对对我的view添加了哪些操作。我们使用post_detail view来显示帖子和对应的评论。我们添加了一个QuerySet来显示这个帖子所有有效的评论:
comments = post.comments.filter(active=True)
我们从post对象开始构建这个QuerySet。使用在Comment model中通过related_name属性定义的关系啦返回所有有关系的对象给comments*。
我们还在这个view中让我们的用户添加一条新的评论。如果调用这个view通过GET请求,就我们创建了一个表单实例通过使用comment_fomr = commentForm()
。如果请求是一个已经完成验证的POST,我们实例化表单来使用提交的数据并且验证数据通过使用is_valid()方法。如果这个表单是无效的,我们会在template中渲染验证错误的信息。如果表单通过验证,我们会做以下的操作:
- 我们创建一个新的Comment对象通过调用这个表单的save()方法,如下所示:
new_comment = comment_form.save(commit=False)
saven()方法创建了一个model的实例用来保存表单的数据到数据库中。如果你调用这个方法通过设置comment=False
,你创建的model实例不会即时保存到数据中。这是非常方便的当你想在最终保存之前修改这个model对象,我们接下来将做这一步骤。save()方法是给ModelForm用的,但不是给表单实例们用的,因为它们没有关联上任何model。
- 我们分配一个帖子给我们刚才创建的评论:
new_comment.post = post
通过以上动作,我们指定新的评论是属于分配的帖子。
3.我们保存新的评论到数据库,如下所示:
new_comment.save()
我们的view已经准备好显示和处理新的评论了。
在帖子详情template中添加评论
我们为帖子创建了一个管理评论的功能。现在我们需要修改我们的post_detail.html template来适应这个功能,通过做到以下步骤:
- 显示这个帖子的评论总数
- 显示评论的列
- 显示一个表单给用户来添加新的评论
首先,我们来添加评论的总数。打开blog/detail.html template在content区块中添加如下代码:
{% with comments.count as total_comments %}
<h2>
{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
我们在template中使用Django ORM执行comments.count()
QuerySet。注意,在Django template语言中调用方法时不使用圆括号。{% with %}
tag允许我们分配一个值给新的变量,这个变量可以一直使用直到遇到{% endwith %}
。
{% whti %}
template tag是非常有用的可以避开直接使用数据库或花费大量的时间在处理复杂的方法。
我们使用pluralize template filter在单词comment的后面展示total_comments的复数形式。Template filters使输入的变量值经过应用然后返回计算后的值。我们将会更多的讨论tempalte filters在第三章 扩展你的博客应用中。
这pluralize template filter 会在值的末尾显示一个"s"如果值不为 1。在之前的文本将会渲染成类似: 0 comments, 1 comment 或者 N comments。Django内置大量的template tags 和 filters来帮助你通过各种方法展示各类信息。
现在,让我们加入评论列。在blog/detail.html中之前的代码后面加入以下内容:
{% for comment in comments %}
<div class="comment">
<p class="info">
Comment {{ forloop.counter }} by {{ comment.name }}
{{ comment.created }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>There are no comments yet.</p>
{% endfor %}
我们使用{% for %}
template tag来循环所有的评论。我们会显示一个默认的信息如果comments列为空,告诉我们的用户这篇帖子还没有任何评论。我们通过使用{{ forloop.counter }}
变量来枚举评论,该变量包含在每次迭代的循环计数中。之后我们显示发送评论的用户名,日期,和评论的内容。
最后,你需要渲染表单或者显示一条成功的信息来代替表单当表单提交成功后。在之前的代码后面添加如下内容:
{% if new_comment %}
<h2>Your comment has been added.</h2>
{% else %}
<h2>Add a new comment</h2>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Add comment"></p>
</form>
{% endif %}
这段代码非常简洁明了:如果存在new_comment对象,我们会展示一条成功信息因为成功创建了一条新评论。否则,我们通过一个段落<p>
元素渲染表单中每一个字段,并且表明这个POST请求包含有CSRF标记。在浏览器中打开 http://127.0.0.1:8000/blog/ 然后点击任意一篇帖子的标题进入它的详情页面。你会看到如下页面展示:
邮箱一定要是正确格式,不然提交没反应。