美文网首页Django学习我django的学习程序员
Django 学习小组:博客开发实战第六周教程 —— 实现评论功

Django 学习小组:博客开发实战第六周教程 —— 实现评论功

作者: 追梦人物 | 来源:发表于2016-06-25 10:00 被阅读6046次

本教程内容已过时,更新版教程请访问: Django 博客开发入门教程

通过前四周的时间我们开发了一个简单的个人 Blog,相关教程:

第一周Django 学习小组:博客开发实战第一周教程 —— 编写博客的 Model 和首页面
第二周Django 学习小组:博客开发实战第二周教程 —— 博客详情页面和分类页面
第三周Django 学习小组:博客开发实战第三周教程——文章列表分页和代码语法高亮
第四周Django 学习小组:博客开发实战第四周——标签云与文章归档
第五周Django 学习小组:博客开发实战第五周——基于类的通用视图详解(一)

本周我们将实现 blog 文章的评论功能。

提示:在阅读教程的过程中,如有任何问题请访问我们项目的 GithHub 或评论留言以获取帮助,本教程的相关代码已全部上传在 GitHub 的 blog-tutorial 分支 上,请点击链接获取。如果你对我们的教程或者项目有任何改进建议,请您随时告知我们。更多交流请加入我们的邮件列表 django_study@groups.163.com 和关注我们在 GithHub 上的项目。

本文首发于编程派微信公众号:编程派(微信号:codingpy)是一个专注Python编程的公众号,每天更新有关Python的国外教程和优质书籍等精选干货,欢迎关注。

实现思路

首先需要为评论(Comment)设计一个数据库表,并编写相应的 Model,将评论与文章关联,再编写发表评论的视图,设置相应的 url 即可。

评论的 Model 设计

blog/models.py

class BlogComment(models.Model):
    user_name = models.CharField('评论者名字', max_length=100)
    user_email = models.EmailField('评论者邮箱', max_length=255)
    body = models.TextField('评论内容')
    created_time = models.DateTimeField('评论发表时间', auto_now_add=True)
    article = models.ForeignKey('Article', verbose_name='评论所属文章', on_delete=models.CASCADE)

    def __str__(self):
        return self.body[:20]

参照大部分博客评论的样式,我们的 BlogComment Model 包含这些字段:

user_name:用户在评论前先要填写他们想使用的昵称

user_email:用户在评论前先要填写他们想使用的邮箱

body:用户提交的评论内容

created_time:评论提交时间

article:评论关联的文章,因为一个评论只能关联某一篇文章,而一篇文章下可能有多个评论,因此是一对多的关系,使用 ForeignKey

评论的表单

表单用来给服务器后台提交用户填写的数据,例如平时我们看到的填写登录、注册信息的页面就是一个登录、注册表单,用户填写表单信息后,点击提交按钮,表单中填写的内容就会打包发送给服务器后台。我们需要为用户填写评论设置一个表单,django 的 form 模块为我们提供了自动生成表单的功能,如果对表单不熟悉请参阅:官方文档:表单概述 ,以了解基本的表单使用方法(如果你对表单感觉很陌生的话)。下面我们使用 Django 的 ModelForm ( django ModelForm 介绍 )类为我们自动生成表单。首先在 blog 目录下新建一个 forms.py (和 models.py 同一目录)文件用来存放 form 的代码:

blog/forms.py

from django import forms
from .models import Article, BlogComment


class BlogCommentForm(forms.ModelForm):
    class Meta:
        """指定一些 Meta 选项以改变 form 被渲染后的样式"""
        model = BlogComment # form 关联的 Model
        
        fields = ['user_name', 'user_email', 'body']
        # fields 表示需要渲染的字段,这里需要渲染user_name、user_email、body
        # 这样渲染后表单会有三个文本输入框,分别是输入user_name、user_email、body的输入框
        
        widgets = {
            # 为各个需要渲染的字段指定渲染成什么html组件,主要是为了添加css样式。
            # 例如 user_name 渲染后的html组件如下:
            # <input type="text" class="form-control" placeholder="Username" aria-describedby="sizing-addon1">
            
            'user_name': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': "请输入昵称",
                'aria-describedby': "sizing-addon1",
            }),
            'user_email': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': "请输入邮箱",
                'aria-describedby': "sizing-addon1",
            }),
            'body': forms.Textarea(attrs={'placeholder': '我来评两句~'}),
        }

视图函数

这里我们一如既往坚持使用基于类的通用视图,由于涉及到评论表单的提交处理,因此我们使用 FormView。这里对 FormView 的使用稍作讲解。

在 Django 的基于函数的视图中,涉及表单的处理的视图其逻辑一般是这样的:

def post_comment(request):
    if request.method =='POST':
        form = BlogCommentForm(request.POST)
        if form.is_valid():
            ...
        else:
            ...
     else:
        ...

即,首先判断用户是否通过表单 POST 了数据过来,如果是,则根据 POST 过来的数据构建一个表单,如果数据验证合法(form.is_valid),则创建评论,否则返回表单提交页。如果没有 POST 数据,则做其他相应的事情。FormView 把这些逻辑做了整合,无需写那么多 if else 语句:

blog/views.py

from django.views.generic.edit import FormView

...

class CommentPostView(FormView):
    form_class = BlogCommentForm # 指定使用的是哪个form
    template_name = 'blog/detail.html' 
    # 指定评论提交成功后跳转渲染的模板文件。
    # 我们的评论表单放在detail.html中,评论成功后返回到原始提交页面。

    def form_valid(self, form):
        """提交的数据验证合法后的逻辑"""
        # 首先根据 url 传入的参数(在 self.kwargs 中)获取到被评论的文章
        target_article = get_object_or_404(Article, pk=self.kwargs['article_id'])
        
        # 调用ModelForm的save方法保存评论,设置commit=False则先不保存到数据库,
        # 而是返回生成的comment实例,直到真正调用save方法时才保存到数据库。
        comment = form.save(commit=False)
        
        # 把评论和文章关联
        comment.article = target_article
        comment.save()
        
        # 评论生成成功,重定向到被评论的文章页面,get_absolute_url 请看下面的讲解。
        self.success_url = target_article.get_absolute_url()
        return HttpResponseRedirect(self.success_url)

    def form_invalid(self, form):
        """提交的数据验证不合法后的逻辑"""
        target_article = get_object_or_404(Article, pk=self.kwargs['article_id'])
        
        # 不保存评论,回到原来提交评论的文章详情页面
        return render(self.request, 'blog/detail.html', {
            'form': form,
            'article': target_article,
            'comment_list': target_article.blogcomment_set.all(),
        })

为了方便地重定向回原来提交评论的文章详情页面,我们为文章(Article)的模型新增一个方法:get_absolute_url,调用该方法将得到该 Article 对应的 url,例如这是文章 1 的 url:http://localhost:8000/article/1,则调用后返回 /article/1,这样调用 HttpResponseRedirect 后将返回该 url 下的文章详情页。

blog/models.py

from django.core.urlresolvers import reverse

class Article(models.Model):
    STATUS_CHOICES = (
        ('d', 'Draft'),
        ('p', 'Published'),
    )

    ...
    
    class Meta:
        ordering = ['-last_modified_time']

    # 新增 get_absolute_url 方法
    def get_absolute_url(self):
        # 这里 reverse 解析 blog:detail 视图函数对应的 url
        return reverse('blog:detail', kwargs={'article_id': self.pk})

同时为了在详情页渲染一个评论表单,稍微修改一下 ArticleDetailView 的视图函数,把评论表单 form 插入模板上下文中:

blog/views.py

class ArticleDetailView(DetailView):
    model = Article
    template_name = "blog/detail.html"
    context_object_name = "article"
    pk_url_kwarg = 'article_id'

    def get_object(self, queryset=None):
        obj = super(ArticleDetailView, self).get_object()
        obj.body = markdown2.markdown(obj.body, extras=['fenced-code-blocks'], )
        return obj

    # 新增 form 到 context
    def get_context_data(self, **kwargs):
        kwargs['comment_list'] = self.object.blogcomment_set.all()
        kwargs['form'] = BlogCommentForm()
        return super(ArticleDetailView, self).get_context_data(**kwargs)

URL 设置

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    ...
    # 设置评论视图对应的 url
    url(r'^article/(?P<article_id>\d+)/comment/$', views.CommentPostView.as_view(), name='comment'),
]

设置模板文件

新增了一个 comment.html 文件以渲染评论表单和评论列表,并且修改了 detail.html 文件以在文章详情页显示评论表单和评论列表,修改了blog/tatic 下的 style.css 为评论添加样式,由于代码比较多,就不贴出来了,主要是 html 和 css 的前端相关代码,请到 GitHub 的 blog-tutorial 分支 更新相关的模板和静态资源文件。

至此,整个评论功能的框架做好了,显示效果如下:

评论效果演示图

当然这只是一个评论的框架,很多细节有待处理和完善,但无论如何,用户可以为我们的文章发表评论意见了。

Django学习小组简介

django学习小组是一个促进 django 新手互相学习、互相帮助的组织。

小组在一边学习 django 的同时将一起完成几个项目,包括:

  • 一个简单的 django 博客,用于发布小组每周的学习和开发文档;
  • django中国社区,为国内的 django 开发者们提供一个长期维护的 django 社区;

上面所说的这个社区类似于 segmentfault 和 stackoverflow ,但更加专注(只专注于 django 开发的问题)。

更多的信息请关注我们的 github 组织,本教程项目的相关源代码也已上传到 GitHub 的 blog-tutorial 分支 上,请点击链接获取。

同时,你也可以加入我们的邮件列表 django_study@groups.163.com ,随时关注我们的动态。我们会将每周的详细开发文档和代码通过邮件列表发出。

如有任何建议,欢迎提 Issue,欢迎 fork,pr,当然也别忘了 star 哦!

相关文章

网友评论

  • 狤皇叔:多级评论要怎么写
  • 73dc4752da3c:有个bug,评论数一直是0
    追梦人物:自己改下模板咯。
  • 外向的孤独患者单身成狗:你好,我在GITHUB上面clone了你的代码,想请教一下,那个,你view下面的函数都没有进行render_to_response()的调用,你是怎样能把内容渲染成html返回的,处理的逻辑都大概明白了,就是这个问题困住了我
    追梦人物:@外向的孤独患者单身成狗 因为你在url使用了as view,把类转化成了函数。然后用户访问url这个函数会调用。
    外向的孤独患者单身成狗:@追梦人物 就是你不用调用?直接继承了ListView它自动会执行render? 一般继承类不是只会把函数继承而不运行吗?
    追梦人物:@外向的孤独患者单身成狗 我是用的是类视图,这个render 是继承的类视图帮我们做的。
  • ddaebc1e49cf:教程很棒~我有个疑问,如果博文需要加入图片有没有什么好的解决方案呢~
    追梦人物:@dannyw 是的,图片托管平台比如七牛云
    ddaebc1e49cf:@追梦人物 多谢回复哈~云数据平台指的是cdn嘛,然后在文章内容处使用markdown表示图片的语法即可是吧?
    追梦人物:@dannyw 先把图片上传到云数据平台,然后引用其外链。
  • Code_Rush:很棒!
  • 909779b6f0aa:给力!
  • 61aec1cc2832:Method Not Allowed (POST): /blog/article/2/comment/
    代码都是照抄你的,为什么出现方法错误?
    61aec1cc2832:@追梦人物 经过艰难的debug终于发现问题所在,
    url(r'^article/(?P<article_id>\d+)/$', views.ArticleDetailView.as_view(), name='detail'),
    之前这一句少了$,所以一直都是method not allow,不过还不清楚原因。顺便问一下点赞功能有什么思路?
    追梦人物:@序幕七章 代码上看不错问题。可能和你本地的服务器有关。尝试关掉一些代理或者翻墙服务什么的试一下。
    追梦人物:@序幕七章 错误堆栈贴出来看一下咯。
  • ce5010ac7d8c:请问在使用view的时候,比如listview,如何加入用户验证的模块?我想使用@login_required(login_url='login.html')这装饰器,可是一般的函数如何和view类关联?
    谢谢~~~
    追梦人物:@天尘子 在view的最前面继承 LoginRuiredMixin ,官方文档有介绍。
  • 326fd36d7a9b:您好,请问如何加入你们的邮件列表呢?我发邮件发送不出去。。。。很想加入你们,一起学习。
  • 4a35178ef971:感谢小组, 怒赞!
  • jiangbingo:支持~
  • jetson:谢谢分享,虽然教程暂停了 但是希望你们平时的代码 同步到git上去
    追梦人物:@jetson 嗯,谢谢支持
  • 16dda9e2f284:博客系列到此结束了吗?
    jetson:@追梦人物 看了一下 其实很清晰了,只要看了那个官方的demo的。伸手党太多了,恨不得手把手教,每一行代码加注释。
    追梦人物:@saturnisbig 嗯,系列文章可能暂停了。会出一些零碎的django文章。有人反应教程有些不清晰,等社区开发完毕后会出一份step by step 的教程,一步步带大家开发一个博客和论坛。

本文标题:Django 学习小组:博客开发实战第六周教程 —— 实现评论功

本文链接:https://www.haomeiwen.com/subject/focadttx.html