django -- 博客评论功能


用我一生,换你十年天真无邪 -- 盗墓笔记


环境

  1. bootstrap 4
  2. django 2.0
  3. python 3.6

实现效果

评论框

采用 bootstrap 模态框实现

评论列表

除了首级评论,其他都缩进显示

效果展示

blog.pinsily.site


初始化操作

  1. 新建app(实现功能化)
$ python manage.py startapp comment
  1. 加入 settings.py 中

model 设计

from django.db import models
from django.conf import settings

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType


class Comment(models.Model):

    # 使用 django 的 ContentType 与 GenericForeignKey 关联评论类型(article)和评论id
    # ContentType: https://juejin.im/entry/581da04f128fe1005afdf618
    content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    # 评论内容
    text = models.TextField()

    user_name = models.CharField('评论者名字', max_length=100)
    user_email = models.EmailField(
        max_length=50, blank=True, null=True, verbose_name='邮箱地址', help_text='邮箱地址,用于发送回复')

    # 评论时间和修改时间自动添加
    created_time = models.DateTimeField('评论发表时间', auto_now_add=True)
    modified_time = models.DateTimeField('修改时间', auto_now=True)

    # 根评论,每个回复都只有一个
    # 根评论的根评论默认为空
    root = models.ForeignKey("self", related_name='root_comment',
                             null=True, on_delete=models.DO_NOTHING, verbose_name=u"根回复")

    # 父级评论,每个回复只有一个
    # 根评论的父级评论默认为空
    parent = models.ForeignKey('self', related_name="parent_comment",
                               null=True, on_delete=models.DO_NOTHING, verbose_name=u'父级评论')

    # 根评论没有回复谁,为空
    reply_name = models.CharField(
        max_length=100, null=True, blank=True, verbose_name=u'回复谁')

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

    class Meta:
        db_table = "comment"
        ordering = ['created_time']
  1. 数据库迁移操作
$ python manage.py makemigrations
$ python manage.py migrate

view 视图函数

from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.contrib.contenttypes.models import ContentType

# Create your views here.

from .models import Comment


def comment(request):
    if request.method == "POST":
        nick = request.POST['nick']
        email = request.POST['email']
        text = request.POST['comment_body']

        # 参数都由隐藏的 input 提供
        content_type = request.POST["content_type"]
        object_id = int(request.POST["object_id"])
        parent_id = int(request.POST['reply_comment_id'])

        # 新建评论
        comment = Comment()

        # 获取对应的文章
        model_class = ContentType.objects.get(model=content_type).model_class()
        model_obj = model_class.objects.get(pk=object_id)

        # 关联评论和父级评论
        parent = None
        if parent_id:
            parent = get_object_or_404(Comment, pk=parent_id)

        # 判断是否是回复评论
        if not parent is None:
            comment.root = parent.root if not parent.root is None else parent
            comment.parent = parent
            comment.reply_name = parent.user_name
        else:
            comment.root = None
            comment.parent = None

        comment.user_name = nick
        comment.user_email = email
        comment.text = text
        comment.content_object = model_obj

        comment.save()

        # 评论后回到原页面
        referer = request.META.get(
            'HTTP_REFERER', reverse("blog:index"))
        return redirect(referer)

    referer = request.META.get(
        'HTTP_REFERER', reverse("blog:index"))
    return redirect(referer)

url

  1. 主 url
urlpatterns = [
    # ....
    path('comment/', include('comment.urls')),
]
  1. comment url
from django.urls import path, include, re_path
from . import views

app_name = 'comment'

urlpatterns = [
    path('', views.comment, name='user_comment'),
]

页面代码

  1. comment.html:通过 {% include "blog/comment.html" %} 引入到文章详情页
{% load static %}
<!-- modal begin -->
<div class="modal fade" id="commentModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title" id="myModalLabel">Comment</h4>

        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      </div>
      <div class="modal-body">
        <form class="form-horizontal" role="form" action="{% url 'comment:user_comment'%}" method="post">
          {% csrf_token %}
            <div class="form-group">

                <div class="col-sm-12">
                    <input type="text" class="form-control" name="nick" id="nick" placeholder="nick">
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-12">
                    <input type="email" class="form-control" name="email" id="email" placeholder="email">
                </div>
            </div>

            <div class="form-group">

                <div class="col-sm-12">
                    <textarea type="text" class="form-control" name="comment_body" id="conent" placeholder="content" rows="4"></textarea>
                </div>
            </div>

            <div class="form-group">

                <div class="col-sm-12">
                    <input type="hidden" name="object_id" value="{{ article.pk }}">
                    <input type="hidden" name="content_type" value="article">
                    <input type="hidden" name="reply_comment_id" id="reply_comment_id" value="">
                </div>
            </div>


            <div class="form-group">
              <div class="col-sm-12">
              <button class="btn btn-primary btn-sm btn-block" id="comment_btn">Comment</button>
              </div>
            </div>
        </form>
      </div>
    </div>
  </div>
</div>

<!-- modal end -->

<!-- comment list begin -->
<div class="col-12 comment-list">

  <p>评论列表&nbsp;&nbsp;

    <a href="javascript:reply(0);">
      <button class="btn btn-danger btn-group-justified comment-btn">我来说一句</button>
    </a>

  </p>
  <hr style="margin: 5px 0 10px 0;">

  <!-- Single Comment -->
  <div class="media mb-4">
    <h6 class="mt-0">
      Comments( {{ comment_list | length }} )
    </h6>
  </div>
  {% for comment in comment_list %}
  <div class="media mb-4 comment-root">
    <img class="d-flex mr-3 rounded-circle" src="{% static 'blog/imgs/1.jpg' %}" alt="" width="25" height="25" style="border: 0.1px solid #afafaf;">
    <div class="media-body text-justify" style="font-size: 14px;">
      <span>{{ comment.user_name }}</span>&nbsp;&nbsp;<span"> {{ comment.created_time | date:'Y-m-d h:i' }} </span>&nbsp;&nbsp;<span>#</span>
      <a href="javascript:reply({{comment.pk}});">回复</a>
        <p class="comment-text">{{ comment.text  }}</p>

      {% for reply in comment.root_comment.all %}

        <span>
          <img class="rounded-circle" src="{% static 'blog/imgs/1.jpg' %}" alt="" width="25" height="25" style="border: 0.1px solid #afafaf; margin: 2px; margin-right: 15px; ">
          <span>{{ reply.user_name }}</span>&nbsp;&nbsp;
          <span> {{ reply.created_time | date:'Y-m-d h:i' }} </span>&nbsp;&nbsp;
          <span> 回复 </span>&nbsp;&nbsp;
          <span> {{ reply.reply_name }}: </span>&nbsp;&nbsp;
          <span> # </span>&nbsp;&nbsp;
          <a href="javascript:reply({{reply.pk}});">回复</a>
        </span>
          <p class="comment-reply-text">{{ reply.text }}</p>

      {% endfor %}

    </div>
  </div>
  {% empty %}

  <div class="media mb-4">
    <div class="media-body">
      <h6 class="mt-0">暂时没有评论!</h6>
    </div>
  </div>
  {% endfor %}

</div>

<!-- comment list end -->

<script type="text/javascript">

  function reply(reply_comment_id) {
      $("#reply_comment_id").val(reply_comment_id);
      $('#commentModal').modal({keyboard: false});
  }

</script>

部分 css

/*---------------------------------------------------------*/
/*comment*/
.comment-list {
    background-color: white;
    border: solid #bcbcbc 1px;
    border-top: solid 3px #1ba1e2;
    border-radius: 15px;
    margin: 50px 0 100px 0;
    padding-top: 20px;
}


.comment-btn {
    padding: 3px 3px 4px 3px; 
    font-size: 14px; 
    cursor:pointer;
}

.comment-root {
    border: 0.1px solid #afafaf;
    padding: 8px;
    margin: 10px; 
}

.comment-list span {
    font-size: 14px;
}

.comment-reply-text {
    font-size:14px; padding: 9px 0; margin:0; text-indent: 56px;
}

.comment-text {
    font-size:14px; padding: 9px 0; margin:0;
}

/*---------------------------------------------------------*/
/*----------------------*/
/*Model*/

.modal {
    color: black;
}

.modal-body {
    margin: 0 auto;
    text-align: center;
    width: 100%;
}

.modal-img {
    width: 100%;
    text-align: center;
    background-color: #888888;
}

.modal-img img {
    border: 1px #888888 solid;
    width: 100%;
    overflow: hidden;

    /*opacity: 0.5;*/
}