Django实战之增加评论

评论提交的方式
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来存储记录对应的是哪个表,这样就可动态存储数据了
且可以存放多种数据
Django实战之增加评论
如图所示我们实现了通用外键

但是这又新增了一个问题,那就是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 formsfrom .models import Commentclass 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 CommentFormfrom comment.models import Commentclass 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 redirectfrom django.views.generic import TemplateViewfrom .forms import CommentsFormclass 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中
添加如下代码

<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 templatefrom comment.forms import CommenFormfrom 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>


上一篇:LightningChart已修复WinForm演示应用程序崩溃问题


下一篇:centos上安装Python并修复yum