Python + Django4 搭建个人博客(十六):文章评论功能实现

上篇我们实现了用户注册功能,至此用户管理模块已经全部完成。

接下来我们来实现博客的另一重要功能:文章评论。

有了文章管理的知识积累,实现评论管理功能就比较轻松了。

按照MTV的模型分别编写对应的模块。

创建App

通过如下代码新建一个用于评论管理的App:

python manage.py startapp comment

注册App

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'article',
    'userprofile',
    # 新增'comment'代码,激活app
    'comment',
]

为了显示发表评论的时间为中国时间,修改时区设置TIME_ZONE为上海的时区。

TIME_ZONE = 'Asia/Shanghai'

定义模型

from django.db import models
from django.contrib.auth.models import User
from article.models import Article

# 博文的评论
class Comment(models.Model):
    article = models.ForeignKey(Article,
        on_delete=models.CASCADE,
        related_name='comments'
    )
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='comments'
    )
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('created',)

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

在模型中我们定义了两个外键

如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键

  • article是被评论的文章
  • user是评论的发布者

对数据进行迁移

创建表单

新建一个评论表单

from django import forms
from .models import Comment

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['body']

评论表中,有两个字段属于外键,针对外键字段,Django的内部逻辑是可以自动和外部数据表关联生成。

我们在表单中实际只需要前台处理body字段就可以了。

创建RRL

django4blog/urls.py新增评论管理URL:

from django.contrib import admin
from django.urls import path, re_path
# 引入app视图
import article.views
import userprofile.views
import comment.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', article.views.hello),
    re_path(r'^$', article.views.article_list),
    path('list/', article.views.article_list, name='list'),  # 展示文章
    path('detail/<int:id>/', article.views.article_detail, name='detail'),  # 文章详情
    path('create/', article.views.article_create, name='create'),  # 写文章
    path('delete/<int:id>/', article.views.article_delete, name='delete'),# 删除文章
    path('update/<int:id>/', article.views.article_update, name='update'),    # 更新文章
    path('login/', userprofile.views.user_login, name='login' ),
    path('logout/', userprofile.views.user_logout, name='logout' ),
    path('register/', userprofile.views.user_register, name='register' ),
    # 增加评论管理
    path('post-comment/<int:article_id>/', comment.views.post_comment, name='post_comment' ),
]

创建视图

评论的视图函数如下:

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse

from article.models import Article
from .forms import CommentForm

# 文章评论
@login_required(login_url='/login/')
def post_comment(request, article_id):
    article = get_object_or_404(Article, id=article_id)
    # 处理 POST 请求
    if request.method == 'POST':
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():
            new_comment = comment_form.save(commit=False)
            new_comment.article = article
            new_comment.user = request.user
            new_comment.save()
            return redirect(article)
        else:
            return HttpResponse("表单内容有误,请重新填写。")
    # 处理错误请求
    else:
        return HttpResponse("发表评论仅接受POST请求。")

get_object_or_404():它和Model.objects.get()的功能基本是相同的。区别是在生产环境下,如果用户请求一个不存在的对象时,Model.objects.get()会返回Error 500(服务器内部错误),而get_object_or_404()会返回Error 404。相比之下,返回404错误更加的准确。

redirect():返回到一个适当的url中:即用户发送评论后,重新定向到文章详情页面。当其参数是一个Model对象时,会自动调用这个Model对象的get_absolute_url()方法。因此接下来马上修改article的模型。

修改文章模型

文章模型article/models.py中添加get_absolute_url()方法:

# 博客文章数据模型
class Article(models.Model):
    # 文章id,主键
    id = models.AutoField(primary_key=True)
    # 文章作者
    author = models.CharField(max_length=100)
    # 文章标题,models.CharField 为字符串字段,用于保存较短的字符串,比如标题
    title = models.CharField('标题',max_length=100)

    # 文章正文,保存大量文本使用 TextField
    body = models.TextField('文章正文')

    # 文章创建时间,参数 default=timezone.now 指定其在创建数据时将默认写入当前的时间
    created = models.DateTimeField(default=timezone.now)

    # 文章更新时间,参数 auto_now=True 指定每次数据更新时自动写入当前时间
    updated = models.DateTimeField(auto_now=True)
    # 获取文章地址
    def get_absolute_url(self):
        return reverse('detail', args=[self.id])

通过reverse()方法返回文章详情页面的url,实现了路由重定向。

修改文章详情视图

评论模块需要在文章详情页面展示,所以必须把评论模块的上下文也传递到模板中。

因此修改article/views.py中的article_detail()

# 文章详情
def article_detail(request,id):
    # 取出相应的文章
    article = Article.objects.get(id=id)
    # 取出文章评论
    comments = Comment.objects.filter(article=id)
    # 需要传递给模板的对象
    context = {'article': article, 'comments': comments}
    # 载入模板,并返回context对象
    return render(request, 'article/detail.html', context)

修改文章详情模板

<!-- extends表明此页面继承自 base.html 文件 -->
{% extends "base.html" %}
{% load static %}

<!-- 写入 base.html 中定义的 title -->
{% block title %}
    文章详情
{% endblock title %}

<!-- 写入 base.html 中定义的 content -->
{% block content %}

    <!-- 文章详情 -->
    <div class="container">
        <!--    <div class="row">-->
        <!-- 标题及作者 -->
        <h1 class="col-12 mt-4 mb-4">{
   
   { article.title }}</h1>
        <div class="col-12 alert alert-primary">
            <div class="col-12">
                <a>作者:{
   
   { article.author }}</a>
                &nbsp
                <a>{
   
   { article.created|date:'Y-m-d H:i:s' }}</a>
                &nbsp
                <a href="#" data-bs-toggle="modal" data-bs-target="#myModal">删除文章</a>
                <!-- 新增一个隐藏的表单 -->
                <form
                        style="display:none;"
                        id="safe_delete"
                        action="{% url "delete" article.id %}"
                        method="POST"
                >
                    {% csrf_token %}
                    <button type="submit">发送</button>
                </form>
                &nbsp
                <a href="{% url "update" article.id %}">编辑文章</a>
            </div>
        </div>
        <!-- 文章正文 -->
        <div class="col-12">
            <p>{
   
   { article.body }}</p>
        </div>
        <!--    </div>-->
    <div class="container">
    <div class="col-12">
        <!-- 发表评论 -->
        <hr>
        {% if user.is_authenticated %}
            <div>
                <form
                        action="{% url "post_comment" article.id %}"
                        method="POST"
                >
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="body">
                            <strong>
                                我也要发言:
                            </strong>
                        </label>
                        <textarea
                                type="text"
                                class="form-control"
                                id="body"
                                name="body"
                                rows="2"></textarea>
                    </div>
                    <br>
                    <!-- 提交按钮 -->
                    <button type="submit" class="btn btn-primary ">发送</button>
                </form>
            </div>
            <br>
        {% else %}
            <br>
            <h5 class="col-12 ">
                请<a href="{% url 'login' %}">登录</a>后回复
            </h5>
            </h5>
            <br>
        {% endif %}
    </div>
    </div>
        <!-- 显示评论 -->
        <h4>共有{
   
   { comments.count }}条评论</h4>
        <div>
            {% for comment in comments %}
                <hr>
                <p>
                    <strong style="color: pink">
                        {
   
   { comment.user }}
                    </strong> 于
                    <span style="color: green">
                    {
   
   { comment.created|date:"Y-m-d H:i:s" }}
                </span> 时说:
                </p>
                <pre style="font-family: inherit; font-size: 1em;">
                {
   
   { comment.body }}</pre>
            {% endfor %}
        </div>
    </div>
    </div>

{% endblock content %}
  • 表单组件中的action指定数据提交到哪个url中
  • 显示评论中的comments.count是模板对象中内置的方法,对包含的元素进行计数
  • |date:"Y-m-d H:i :s":管道符你已经很熟悉了,用于给对象“粘贴”某些属性或功能。这里用于格式化日期的显示方式。
  • <pre>定义预格式化的文本,在我们的项目中最关键的作用是保留空格和换行符。该标签会改变文字的字体、大小等,因此用style属性重新定义相关内容。尝试将<pre>替换为div,输入多行文本试试效果。

运行测试

运行之后,查看详情页,如果没登录会提示需要登录后回复评论。

登录后发布评论

结语

至此我们已经完成了评论管理的核心功能,发布和显示,其实部分网站还提供评论的修改和删除的功能。

实现过程和文章的修改和删除差不多,大家可以自行完成相关的开发。

我们博客网站的核心功能模块,文章,用户和评论部分基本已经完成了。

接下来我们将完成博客的一些比较实用的小功能点。

比如,分页,排序以及搜索等。

猜你喜欢

转载自blog.csdn.net/agelee/article/details/127211000