Django实战之增加评论

书接 https://blog.csdn.net/weixin_32759777/article/details/104883848
评论提交的方式
js异步提交数据
当页面提交
单独页面提交
后面两个其实都是一类
差别在于是不是有独立的url和view
第一种是比较流行的方式,基于前端交互完成评论的提交,这样可以不刷新页面的情况下提交数据并展示数据
其实对于后端来说只是三种方式的格式不同,
我们选择单独页面提交,简单一些

具体的逻辑有两种
第一种是将Comment中的target改为CharField里面存放内容和网址
但是存在缺陷就是权限问题,毕竟要区分是不是作者。
第二种方式是使用GenericForeignKey
这种方式值得一说
我们在前面知道Model中ForeignKey的作用——关联两个模型,通过名字可以猜测,GenericForeignKey意味着更加通用
通常来说外键只能针对一个表,但是有时我们有针对对个表的需求,比如现在有一个Comment模型,他能关联Post同时也能关联Links(这里的需求是假设的)
怎么做到关联多种模型,

再解释之前,我们来思考这个问题,在Django中通过外键关联Model是怎么关联上的,
答案是外键字段,比如Post模型中的category字段,这个是存储在Post上 存储的内容是Category,模型的主键这样在使用时,即可以通过这个主键找到当前,Post关联的category,
所以这是通过增加一个字段content_type来存储对应的模型类型,这里拿Comment来举例
在Comment中,我们定义了object_id来存储对应模型的主键值,定义了content_type来存储记录对应的是哪个表,这样就可动态存储数据了
且可以存放多种数据
在这里插入图片描述
如图所示我们实现了通用外键

但是这又新增了一个问题,那就是content_type里面存放的字符串,是由谁来定义并且,写入的,总不能每新增一条数据,都要自己写入link或者post这样的字符串,因此
在Django中提供了一个这样的Model-ContentType ,用它来实现,如果你注意过,setting/base.py
中INSTALLED_APPS里面的内容,就会发现存在一个这样的App,django.contrib.contenttypes,他的作用就会说维护Model和我们用到的content_type之间的关系
比如在ContentType表里 Post模型对应1,Link模型对应2,那么在Comment中如果要写入一条post_id 为1的记录,那就是content_type=1,object_id=1
简单来说就是实现了通用外键,需要多维护一个字段和一张表,既然Django为我们提供了GenericForeignKey这样的字段,那么肯定是把麻烦的操作都已经封装,不过在实际使用中,唯一的问题是,我们需要操作两个模型,这多少少会对性能有些影响。
因此我们会想办法自己来实现对应的逻辑,这其实也是基于通用性和特殊性之间的考虑
通用性能够得到更易用的逻辑但是性能上会有损耗,而特殊的逻辑会在性能上有一定的优势却降低了易用性
具体的实现比较清晰,因此往往实际的业务开发往往是有针对性的,比如像上面Comment可以关联Post,也可以关联Link,因此,我们不使用Django提供的方法
毕竟他要做的是更加的通用
会带来复杂度
我们只需要在代码中建立,Model和对应的content_type的映射即可,第二种方式说了这么多,主要是为了家是通用外键这个字段类型,理解它能够,帮助你更好的设计业务下的模型关系
上面说的Comment和Link其实是伪需求,因为只需对友联页面可以评论即可,
不需要对每一条都进行评论,因此我们可以使用第一种方法,如果确实需要处理评论部分的权限,我们可以在业务层来处理,简单来说就是同target中存储的path来处理来获取文章id然后判断用户

实现评论
我们来修改模型只需要修改,target的字段类型,
testblog/comment/models.py中的target做如下修改

# target=models.ForeignKey(Post,verbose_name="评论目标",on_delete=models.CASCADE)
    target=models.CharField(max_length=100,verbose_name="评论目标")

执行下下面代码

 python3 manage.py makemigrations
 python3 manage.py migrate

testblog/comment/forms.py在它中添加如下代码

from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
    nickname = forms.CharField(
        label="昵称",
        max_length=50,
        widget=forms.widgets.Input(attrs={"class": "form_control", "style": "width:60%;"})

    )
    email = forms.CharField(label="Email", max_length=50,
                            wedget=forms.widgets.EmailInput(attrs={"class": 'form_control', "style": "width:60%;"}))
    website=forms.CharField(label="网站",
                            max_length=100,
                            widget=forms.widgets.URLInput(attrs={"class": "form_control", "style": "width:60%;"})
                            )
    content=forms.CharField(label="内容",
                            max_length=500,
                            widget=forms.widgets.Textarea(attrs={"rows": "6", "cols": "60", "class": "form_control"})
                            )
    def clean_content(self):
        content=self.cleaned_data.get("content")
        if len(content)<10:
            raise forms.ValidationError("内容长度太短了")
        return content
    class Meta:
        model=Comment
        fields=["nickname","email","website","content"]

如果不考虑样式
只需要配置model和fields就行,但是为了样式,我们还要重新定义字段的组件,自定义内容不难理解都是样式方面的,另外我们在代码中使用clean_content方法来控制评论的长度,如果内容太少就直接抛出异常
Form定义完成之后,我们需要在Model层提供接口,用来返回某篇文章下的所有有效评论,
下面在Comment类中增加方法

class Comment(models.Model):
	#省略其他代码
	@classmethod
	def get_by_target(cls,target):
		return cls.objects.filter(target=target,status=status.STATUS_NORMAL)
		

这些都完成之后,素材就准备好了,接下来view层CommentForm和评论的数据传到模板层
我们需要,在PostDetail中,重写get_content_data方法的

from comment.forms import CommentForm
from comment.models import Comment
class PostDetailView(CommentViewMinxin,DetaillView):
	queryset=Post.latest_posts()
	template_name="blog/detail.html"
	context_object_name="post"
	pk_url_kwarg="post_id"
	def get_context_data(self,**kwargs):
	context=super().get_context_data(**kwargs)
	context.update(
	{"comment_form":CommentForm,"comment_list":Comment.get_by_target(self.request.path))
	return context

这样就可在blog/detail.html模板中拿到comment_form和comment_list变量了,
我们需要做的就是把他们渲染出来
对于Form来说,渲染起来很简单,可以直接使用,列表的展示需要多写点代码
在{%endblock%}之前,添加如下代码

<hr/>
<div class="comment">
<form class="form-group" action="/comment/" method="POST">
{% csrf_token %}
<input name="target" type="hidden"  value="{{request.path}}"/>
{{comment_form}}
<input type="submit" value="写的多点"/>
</form>
<ul class="list-group">
{{% for comment in comment_list%}}
<li class="list-group-item">
<div class="nickname">
<a href="{{comment.website}}">{{nickname}}</a>
<span>{{comment.created_time}}</span>
</div>
<div class="comment-content">
{{comment.content}}
</div>

</li>
{{% end for %}}
</ul>
</div>

启动项目就可以看到评论功能了不过就是不能提交,上面form中定义action为/comment/这是一个新的url因此要在
comment/view.py中对应的创建一个新的View
代码如下

from django.shortcuts import redirect
from django.views.generic import TemplateView
from .forms import CommentsForm
class CommentView(TemplateView):
	http_methond_names =["post"]
	template_name="comment/result.html"
	def post(self,request,*args,**kwargs):
		comment_form=CommentForm(request.POST)
		target=request.POST.get("target")
		if comment_form.is_valid():
			instance =comment_form.save(commit=False)
			instance.target=target
			instance.save()
			succeed=True
			return redirect(target)
		else:
			succeed=False
		context={
		"succeed":succeed,
		"form":comment_form,
		"target":target}
		return self.render_to_response(context)

这里直接使用TemplateView来完成,这个View,只是提供了POST 方法,其逻辑是通过CommentForm来处理接收的数据然后验证并保存,最后渲染到评论结果页面,如果有检验失败的部分,也会展示到评论结果页面,
接下来增加一个result.html 在comment中
添加如下代码

<!doctype html>
<html>
<head>
<title>评论结果页</title>
<style>
body {TEXT-ALIGN:center;}
.result { text-align:center;
		width:40%;
		margin:auto;}
.errorlist {color:red;}
ul li {list-style-type:None;}
</style>
</head>
<body>
<div class="result">
{%if succeed%}
<a href="{{target}}">返回</a>
{%else%}
<ul class="errorlist">
{%for field ,message in form.errors.items %}
<li>{{message}}</li>
{%end for%}
</ul>
<a href="javascript:window.history.back();">返回</a>
{% end if %}


</div>
</body>
</html>

完成最后一步就是配置url

#省略其他代码
(r"^comment/$",CommentView.as_view(),name="comment"),

配置完后可以启动项目,添加一下评论,然后可以考虑改改其他代码,比如希望评论完成后并不是实时展示,而需要网络管理员同意后在展示,
抽象出评论模块组件和Mixin
上面的实现满足了基本功能,但是结构上不太合理,因为我们还需要在blog/views.py中来操作comment的数据,这就是意味着,如果在有链上增加评论,也得去修改View层代码,还记得之前说的,开闭原则,我们需要吧评论做成即插即用的组件,
要完成这个需求,就要用到Django的template tag(自定义标签)这部分接口了
可以先说下面我们期待的使用方式,在任何需要添加评论的地方,我们需要使用{% comment_block request.path%}即可,
之所以叫做comment_block,是因为comment是Django内置的tag
用来做大块代码的注释
在开始写代码之前,还是先来看一下,Django中的tag
在前面的模板中已经多次的使用了,比如说for循环和if判断等,这些都是会内置的我们需要自定义,tag
这里就直接使用需求来代替演示吧 ,因为使用起来并不复杂,
第一部
需要做的就是,commentApp下新建一个templatetags目录同时在该目录下新增__init__.py 和comment_block.py 这两个文件,
第二部
即使是在comment_block.py中写如下代码

from  django import template
from comment.forms import CommenForm
from comment.models import Comment
register=template.Library()
@register.inclusion_tag('comment/block.html')
def comment_block(target):
	return {"target":target,
	"comment_form":CommentForm(),
	"comment_list":Comment.get_by_target(target)}

其实现并不复杂,其他类型的方法使用也不复杂,
唯一需要注意的是目录结构,这个跟静态文件目录和模板一样,Django会自动查找,因此要放到正确的位置
上面的代码完成之后就可以把PostDetailView中新增的那个,get_context_data
去掉了同时去掉评论相关的引用了
接着编写模板,也就是上面用到的comment/block.html这个模板里面的代码直接从blog/detail.html中剪切黏贴过来即可唯一需要处理的是target部分,因为是自定义的标签默认没有request对象的 所以上面手动将target渲染到了页面中comment/block.html中的代码如下

<hr/>
<div class="comment">
<form class="form-group" action="/comment/" method="POST">
{% csrf_token %}
<input name="target" type="hidden"  value="{{target}}"/>
{{comment_form}}
<input type="submit" value="写的多点"/>
</form>
<ul class="list-group">
{{% for comment in comment_list%}}
<li class="list-group-item">
<div class="nickname">
<a href="{{comment.website}}">{{nickname}}</a>
<span>{{comment.created_time}}</span>
</div>
<div class="comment-content">
{{comment.content}}
</div>

</li>
{{% end for %}}
</ul>
</div>

编写完tag和模板之后,我们的工作就完成了,现在在文章中可增评论,因为是自定义的tag所以需要在模板的最上面(但是在extends下面)增加{%load comment_block%}用来加载自定义标签文件,
然后需要在展示评论的地方增加{%comment_block request.path%}即可
这里我们也可以在有链页面增加评论,使用的是同样的逻辑
修改最新评论模板
之前写的最新模板,是基于外键关联Post的方式,现在修改为通用的方法
针对某个url 我们需要修改config/blocks/sidebar_comments.html
代码如下


<ul class="list-group">
{{% for comment in comments%}}
<li class="list-group-item">
<a href="{{comment.target}}">{{comment.target.title}}</a>|

{{comment.nicknam}}:
{{comment.content}}
</li>
{{% end for %}}
</ul>

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方佑

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值