Django 博客 - 9 评论

评论模型

由于可以回复评论,所以评论是一个层次架构的模型
所以在模型中,评论是自己的外键,可以通过一条评论获取回复的评论

blog/models.py中添加Comment模型

class Comment(models.Model):
    user = models.ForeignKey(User)
    content = models.TextField()
    #parent为该评论的父评论,所以第一个参数为'self',当为空时表示为第一层级的评论
    #指定related_name='children',这样可以父评论通过comment.children获取子评论,默认是通过comment.comment_set获取
    parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.SET_NULL)
    post = models.ForeignKey(Post)
    created_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        ordering = ['-created_time']

为了在后台能管理Comment模型,在blog/admin.py注册

from blog.models import Comment
admin.site.register(Comment)

然后就是迁移数据库了

python manage.py makemigrations
python manage.py migrate

评论视图

评论适合文章关联的,没有单独的视图,是显示在文章下面的,所以我们修改PostView

blog/views.pyPostView里修改get_object方法
post对象,添加一个comments属性

    def get_object(self, queryset=None):
        post = super(PostView, self).get_object(queryset)

        #格式化markdown文章等其他操作
        #...

        #由于post是comment的外键,所以可以通过post.comment_set.all()来获取该文章下的所以评论
        #同时过滤出parent=None的评论
        post.comments = post.comment_set.all().filter(parent=None).order_by('-created_time')
        return post

评论模板

修改blog/templates/blog/detail.html
在合适的文章结束的地方添加

<ul>
    {% for c in post.comments %}
    <li>
        {{c.content}}
        <ul>
            {% for cc in c.children.all %}
            <li>
                {{cc.content}}
            </li>
            {% endfor %}
        </ul>
    </li>
    {% endfor %}
</ul>

这就是最简单的模板了,这里只显示两个层级的评论
接下来美化一下评论,为了复用,将单条评论的模板提取成一个模板文件blog/comment.html
并且接受评论对象comment,层级level和楼数counter这三个参数
评论是按时间降序排序的,最新的评论在最前面,所以counter传入的是forloop.revcounter
forloopfor标签里计数器,而forloop.revcounter是翻转计数,从迭代对象的长度递减到0
只有层级level为1时才显示评论的楼数,level=2时不显示

通过include标签可以包含其他模板文件
这样我们的模板就变成下面这样了

<ul>
    {% for c in post.comments %}
    <li>
        {% include 'blog/comment.html' with comment=c level=1 counter=forloop.revcounter %}
        <ul>
            {% for cc in c.children.all|dictsort:'created_time' %}
            <li>
                {% include 'blog/comment.html' with comment=cc level=2 %}
            </li>
            {% endfor %}
        </ul>
    </li>
    {% endfor %}
</ul>

接下来编写comment.html

我使用的是UIKit3前端框架,所以一条评论的HTML大概是这样的

<article class="uk-comment">
    <header class="uk-comment-header">
        <img class="uk-comment-avatar" src="" alt="">
        <h4 class="uk-comment-title"></h4>
        <ul class="uk-comment-meta uk-subnav"></ul>
    </header>
    <div class="uk-comment-body"></div>
</article>

新建blog/templates/blog/comment.html,将相关变量填入,就成下面这样了

<article class="uk-comment uk-comment-primary uk-visible-toggle">
    <header class="uk-comment-header uk-position-relative">
        <div class="uk-grid-medium uk-flex-middle" uk-grid>
            <div class="uk-width-auto">
                <img class="uk-comment-avatar" src="https://getuikit.com/docs/images/avatar.jpg" width="80" height="80" alt="">
            </div>
            <div class="uk-width-expand">
                <h4 class="uk-comment-title uk-margin-remove">
                    <a class="uk-link-reset" href="#">{{ comment.user.username }}</a>
                </h4>
                <p class="uk-comment-meta uk-margin-remove-top">
                    <a class="uk-link-reset" href="#">{{ comment.created_time }}</a>
                </p>
            </div>
        </div>
        {% if level == 1 %}
            <div class="uk-position-bottom-right uk-position-small">
                <span class="uk-link-muted" href="#">#{{ counter }}</span>
            </div>
            <div class="uk-position-top-right uk-position-small uk-hidden-hover">
                <a class="uk-link-muted" href="#">Reply</a>
            </div>
        {% endif %}
    </header>
    <div class="uk-comment-body">
        <p>{{ comment.content }}</p>
    </div>
</article>

现在的页面还不能发表评论,所以只能在后台界面手动添加评论来查看效果了
其中有个回复按键Reply,下面来实现类似csdn的评论功能

发表评论

要实现评论功能,可以通过提交表单实现,这里使用了django的模型表单类ModelForm
新建blog/forms.py文件,用来放置自定义表单类
自定义一个CommentForm类,用来表示一个评论表单

from django.forms import ModelForm
from blog.models import Comment

class CommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ('content', 'parent', 'user', 'post')

有了表单类,就需要想办法吧表单对象传递给模板
只需要在blog/views.pyPostView覆写get_context_data方法
这个方法在get方法里调用,用来生成上下文传递给模板
因此直接创建一个CommentForm,添加到上下文里

from blog.forms import CommentForm
...
def get_context_data(self, **kwargs):
    context = super(PostView, self).get_context_data(**kwargs)
    form = CommentForm()
    context.update({'form': form})
    return context

有了表单对象之后,就可以在模板里使用了,注意name属性要和CommentFormfields对应上
这样提交的时候,表单和表单对象才可以对应上
同时必须有{% csrf_token %}标签,防止csrf攻击

<form method="post">
    {% csrf_token %}
    <textarea name="{{ form.content.name }}" id="replay_comment_content" required></textarea>
    <input type="hidden" name="{{ form.parent.name }}" id="replay_comment_id">
    <input type="hidden" name="{{ form.user.name }}" value="{{ user.id }}">
    <input type="hidden" name="{{ form.post.name }}" value="{{ post.id }}">
    <button>submit</button>
</form>

form.content是评论内容,需要用户填写的
form.parent是被回复的评论的评论对象
form.user是当前登录的用户
form.post是当前的文章
后面三个是对用户不可见的,form.parent的值是当用户点击回复按键才会填充的,可以为空,而form.userform.post的值是一打开页面就确定了

观察csdn的回复评论的流程,可以发现,当随意点击其中一个回复按键,就会跳转到评论框,并且会在评论框填充[reply]被回复的用户名[/reply]
跳转到评论框可以通过设置锚点,而评论框填充内容就需要javascript来实现了

首先修改blog/templates/blog/comment.html,在回复按键上添加class="reply_button",方便在js获取所有回复按键
href="#reply",点击回复按键时会滚动到idreply的组件上
设置data-id为评论的iddata-username为评论的用户名,用于提交的时候获取被评论的id和用户名

{% if level == 1 %}
-      <div class="uk-position-top-right uk-position-small uk-hidden-hover"><a class="uk-link-muted" href="#">Reply</a></div>
+      <div class="uk-position-top-right uk-position-small uk-hidden-hover"><a class="uk-link-muted reply_button"
+                                                                                    href="#reply"
+                                                                                    data-id="{{ comment.id }}"
+                                                                                    data-username="{{ comment.user }}">Reply</a>
+     </div>
{% endif %}

再将表单的id设置为replyonsubmit设置为return onSubmitComment(),用于在提交的时候做一些处理

<form method="post" onsubmit="return onSubmitComment()" id='reply'>
    ...
</form>

接下来编写javascript代码,因为代码会使用到jquery,所以必须写在引入jquery文件的后面
blog/templates/blog/base.html里所以js文件导入后面添加一个block afterbody

 <script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.30/js/uikit.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.30/js/uikit-icons.min.js"></script>
+{% block afterbody %}{% endblock %}
 </body>
 </html>

blog/templates/blog/detail.html里添加如下代码

{% block afterbody %}
    <script>
        $('#reply_button').click(function () {
            $('#replay_comment_id').val($(this).data('id'));
            $('#replay_comment_content').val('[reply]'+$(this).data('username')+'[/reply]\n');
        });

        function onSubmitComment() {
            var content = $('#replay_comment_content').val();
            var newstr = content.replace(/^\[reply\].*?\[\/reply\]\n/, '');
            if (newstr) {
                if (newstr === content) {
                    $('#replay_comment_id').val('');
                }
                $('#replay_comment_content').val(newstr);
                return true;
            }
            return false;
        }
    </script>
{% endblock %}

通过$('#reply_button').click()为所有回复按键添加点击事件
当点击回复按键时将表单的parent组件(idreplay_comment_id)设置为$(this).data('id'),也就是对应刚刚设置的data-id属性
将表单的content组件(idreplay_comment_content)设置为'[reply]'+$(this).data('username')+'[/reply]\n',提示回复的是哪个用户

当点击提交按键时,调用onSubmitComment函数,这里会取出提交的评论内容,去掉我们添加的[reply]用户名[/reply]
如果没有我们添加的内容,说明是直接发表评论,也可能是用户自己删掉提示内容,这时候需要将parent字段置空
到这里前端的内容都搞完了,由于提交时一个POST请求,还需要后端处理

又回到PostView,添加一个def post(self, request, *args, **kwargs)方法,POST请求会调用post方法
request.POST构建一个CommentFormrequest.POST包含了所有表单参数
CommentForm有效时直接调用save方法保存到数据库里面,最后通过redirect重定向会我们的文章页面,避免重复提交表单

from django.shortcuts import render, redirect
...
    def post(self, request, *args, **kwargs):
        obj = self.get_object()
        form = CommentForm(request.POST)

        if form.is_valid():
            form.save()

        return redirect(obj)

当然,后端也应该验证传入的参数,判断用户权限等,这里就先省略了

猜你喜欢

转载自blog.csdn.net/abc_1234d/article/details/78357225
今日推荐