草庐IT

django树形结构之博客评论案例(带回复功能) - 基础篇

全栈运维 2023-09-28 原文
image.png

前言说明

这里先以博客的评论模板展开实战,基础班是最基本的实现了评论和回复评论功能,但是在UI展示方面,没有能实现树状的层级结构,而且是回复评论的记录都在对应的顶层评论之下进行缩进

先看效果图

django-comment-02.png

模型设计

这里因为是做Demo介绍,所以把文章评论放到了一个应用中去,常规情况下为了应用的复用,建议是拆分成两个独立的应用

1、模型代码

# comment/models.py
class Post(models.Model):
    """
    1、用于测试,不用给全所有属性
    2、常规建议文章是独立的APP,评论是独立的APP,这里为了测试放到了一起
    """
    title = models.CharField(max_length=128, verbose_name="文章标题")

    def __str__(self):
        return self.title 


class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.DO_NOTHING, verbose_name="评论的文章")
    comment_body = models.TextField(verbose_name="评论的内容")
    comment_time = models.DateTimeField(default=timezone.now, verbose_name="评论时间")
    comment_user = models.CharField(max_length=32, verbose_name="评论人")
    comment_email = models.EmailField(verbose_name="评论者邮箱")
    comment_url = models.URLField(blank=True, null=True, verbose_name="评论者URL")
    # 关联自身
    parent_comment = models.ForeignKey('self', null=True, on_delete=models.DO_NOTHING, verbose_name="回复的评论")

    def __str__(self):
        return f'{self.comment_user.username} 评论说: {{ self.comment_content[:20] }}'

视图设计

视图函数代码

# comment/views.py
def index(request): 
    posts = Post.objects.all()  
    context = { 'posts': posts }
    return render(request,'comment/index.html',context=context)


def detail(request, id):
    post = Post.objects.get(pk=id)
    comments = Comment.objects.filter(post_id=id)
    form = CommentForm()
    context = { 'post': post, "comments": comments, "form": form}
    return render(request,'comment/detail.html',context=context)


def comment(request):
    comment_body = request.POST.get('comment_body')
    post_id = request.POST.get('post_id')
    pid = request.POST.get('pid')
    username = request.POST.get('comment_user')
    email = request.POST.get('comment_email')
    url = request.POST.get('comment_url')
    post = Post.objects.get(id=post_id)
    new_comment = Comment()
    new_comment.comment_body = comment_body
    new_comment.comment_user = username
    new_comment.comment_email = email 
    new_comment.comment_url = url 
    new_comment.post = post 

    if pid:
        new_comment.parent_comment_id = pid
    new_comment.save()

    # 评论成功跳转回当前详情页,是为了刷新当前页看到刚评论的内容
    return HttpResponseRedirect(reverse("comment:detail", args=(post_id)))

引入Form表单

因为是在学习Django,所以能用Django自带功能实现的,就优先使用Django功能

在template表单页展示时,可以自己写纯HTML页面,也可以使用 Django Form 表单来快速创建

Form表单代码

from django.forms import ModelForm, Textarea
from comment.models import Comment

class CommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['comment_user', 'comment_email', 'comment_url', 'comment_body']

URL及Template模板

1、URL设计

# comment/urls.py
from django.urls import path
from comment import views

app_name = 'comment'
urlpatterns = [
    path('', views.index, name='index'),
    path('post/<int:id>/', views.detail, name='detail'),
    path('post/comment/', views.comment, name='comment'),
]

2、template模板,这里只有 index.html 和 detail.html

index.html是展示文章列表,为了友好的跳转到详情页去

detail.html 页面主要是演示 评论框、评论列表和回复功能, 因为文章只有title,所以重点在演示评论功能

<!-- comment/templates/comment/detail.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" >
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>

    <title>Document</title>
</head>
<body>
    <div class="container">
        <!-- 文章部分忽略,详细代码见开源源码 -->

        <!-- comment form -->
         <h5>发表评论</h5>
         <div class="row" style="margin-bottom:15px;">
            <form action="{% url 'comment:comment' %}" method="post">
                {% csrf_token %}
                <input type="text" name="post_id" value="{{ post.id }}" hidden>
                <input type="text" name="pid" id="id_parent_comment" hidden>
                <div class="col-md-4">
                    <label for="form.comment_user.id_for_label">{{ form.comment_user.label }}: </label>
                    {{ form.comment_user }}
                    {{ form.comment_user.errors }}
                </div>
                <div class="col-md-4">
                    <label for="form.comment_email.id_for_label">{{ form.comment_email.label }}: </label>
                    {{ form.comment_email }}
                    {{ form.comment_email.errors }}
                </div>
                <div class="col-md-4">
                    <label for="form.comment_url.id_for_label">{{ form.comment_url.label }}: </label>
                    {{ form.comment_url }}
                    {{ form.comment_url.errors }}
                </div>
                <div class="col-md-12">
                    <label for="form.comment_body.id_for_label">{{ form.comment_body.label }}: </label> <br>
                    
                    {{ form.comment_body }}
                    {{ form.comment_body.errors }}
                    <input type="submit" value="提交">

                </div>
                
            </form>
        </div>

        <!-- comment list -->
        <div class="row">
            <h5>评论列表, 总 <span>{{  comment_count }}</span> 条评论</h5>
            <ul class="comment-list list-unstyled">
                {% for comment in comments %}
                <li class="comment-item" style="margin-top: 10px ; padding-bottom: 5px; border-bottom: 1px solid gray;"> 
                    <div class="">
                        <!-- <p>第{{ forloop.counter }}楼 -> By:<span>{{ comment.comment_user }}</span> -> 
                            <span>{{ comment.comment_time }}</span>
                        </p> -->
                        <p style="margin: 0px;">
                            <span>{{ comment.comment_user }} </span> 在 <span>{{ comment.comment_time |date:'Y-m-d'}}</span> 发表评论:
                        </p>
                        {% if comment.parent_comment_id %}
                            <div id="parent" style="color: orange; padding-left: 5px; margin-left: 10px; border-left: 2px solid orangered;">
                                  
                                  {{ comment.parent_comment.comment_body|linebreaks}}
                            </div>
                            
                        {% endif %}
                        <p><span>{{ comment.comment_body|linebreaks }}</span></p>
                        
                        <button class="reply" username="{{ comment.comment_user }}" pk="{{ comment.pk }}">Reply</button>
                        
                    </div>
                </li>
                {% empty %}
                <p>暂无评论</p>
                {% endfor %}
            </ul>
        </div>

    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script>
    
        $(".reply").click(function() {
                $("#id_comment_body").focus()
                var val = "@" + $(this).attr("username") + ' '
                $("#id_comment_body").val(val)
                var pid = $(this).attr("pk")
                console.log(pid)
                var var_pid = document.getElementById("id_parent_comment")
                var_pid.value = pid
                // 下面这两种方式设置都是存在问题的
                // $("#id_parent_comment").value = pid
                // $("#id_parent_comment").setAttribute("value", pid)
            })
    </script>
</body>
</html>

这里有一小段js代码,需要在文件head中引入 jquery ,目的有:
1、在点击Reply/回复 的时候聚焦到 评论框,同时这里还给回复的时候添加了默认的内容 @xxx

2、同时设置回复的内容的ID,也就是当前评论的父级评论的ID,为了是为了知道 回复的那条评论

知识点总结

1、引入了Django Form 表单,单独的Python文件定义,然后可以在template中快速创建响应的表单

2、一小段JS代码,实现回复时自动聚焦到评论框和设置父级评论的ID

3、在template中的模板表单 等post提交数据的地方,需要添加{% csrf_token %} 不然会报错,当然可以通过在post对应的待处理的视图函数上添加@csrf_exempt装饰器(忽略csrf校验) 对应的有个 csrf_protect 开启csrf校验

4、在comment视图函数中,注意需要判断父级评论id是否为空,来设定当前是回复的评论还是一般评论 ;同时注意评论成功之后进行跳转到当前详情页,查看当然评论的内容

问题和思考

从最开始的图片可以看到,虽然模型定义是符合树状结构 的,但是实际页面展示的时候却没有按照树状结构进行层级缩进 展示

因为在多层级的情况下,需要判断是不是叶子节点,而且还要用到递归处理。 写个递归很简单,但是又涉及到在template模板中进行数据展示,常规的函数无法使用,需要编写 标签或者过滤器才可以

那除了自己开发之外, 有没有更好的方案呢?

当然有,现在常用的是 django-mptt 第三包,另外一篇文章我们单独讲解 django-mptt

代码开源地址:https://gitee.com/colin5063/django_learning_v2/tree/django_blog_comment_v1/

有关django树形结构之博客评论案例(带回复功能) - 基础篇的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  3. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  6. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  7. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  8. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  9. ruby-on-rails - 一般建议和推荐的文件夹结构 - Sinatra - 2

    您将如何构建一个简单的Sinatra应用程序?我正在制作,我希望该应用具有以下功能:“应用程序”更像是一个包含所有信息的管理仪表板。然后另一个应用程序将通过REST访问信息。我还没有创建仪表板,只是从数据库中获取东西session和身份验证(尚未实现)您可以上传图片,其他应用可以显示这些图片我已经使用RSpec创建了一个测试文件通过Prawn生成报告目前的设置是这样的:app.rbtest_app.rb因为我实际上只有应用程序和测试文件。到目前为止,我已经将Datamapper用于ORM,将SQLite用于数据库。这是我的第一个Ruby/Sinatra项目,所以欢迎任何和所有建议-我应

  10. ruby - .gemrc 评论? - 2

    这是一个基本问题.gemrc文件中是否允许注释?如果是,你会怎么做?我这里查了没用docs.rubygems.org/read/chapter/11 最佳答案 文档说:Theconfigfileitselfisin’’’YAML’’’format.这意味着您可以拥有commentsstartingwith#,例如:#Ilikedocsrdoc:--inline-source--line-numbers 关于ruby-.gemrc评论?,我们在StackOverflow上找到一个类似的问题

随机推荐