基于Django contrib Comments 评论模块(详解)


Posted in Python onDecember 08, 2017

老版本的Django中自带一个评论框架。但是从1.6版本后,该框架独立出去了,也就是本文的评论插件。

这个插件可给models附加评论,因此常被用于为博客文章、图片、书籍章节或其它任何东西添加评论。

一、快速入门

快速使用步骤:

安装包:pip install django-contrib-comments

在django的settings中的INSTALLED_APPS处添加'django.contrib.sites'进行app注册,并设置SITE_ID值。

在django的settings中的INSTALLED_APPS处添加'django_comments'.

运行manage.py migrate创建评论数据表。

在项目的根urls.py文件中添加URLs:url(r'^comments/', include('django_comments.urls')),

使用comment的模板标签,将评论嵌入到你的模板中。

1.1 comment模板标签

使用前请load标签:

{% load comments %}

1.1.1 评论对象

有两种办法:

1、直接引用评论对象。假设你的模板里已经有了一个叫做entry的评论对象,那么可以使用下面的方法获得该对象的评论次数:

{% get_comment_count for entry as comment_count %}

2、使用对象的类型和id进行引用。比如,你知道一个blog的entry的id为14,那么可以这么做:

{% get_comment_count for blog.entry 14 as comment_count %}

1.1.2 展示评论

使用render_comment_list或者get_comment_list 标签展示评论。

快速展示评论:

{% render_comment_list for [object] %}

这会使用插件里的comments/list.html模板来生成评论的html代码。

自定义展示评论:

{% get_comment_list for [object] as [varname] %}

实例:

{% get_comment_list for event as comment_list %}
{% for comment in comment_list %}
...
{% endfor %}

这种方式下,你可以自己控制comment的展示方式,例如添加css,js,结合bootstrap。

1.1.3 为评论添加超级链接

使用get_comment_permalink标签为评论添加永久的超级链接。用法:

{% get_comment_permalink comment_obj [format_string] %}

默认情况下,url中的命名锚以字母“c”加评论id组成。例如: ‘c82'。当然,也可以通过下面的方式自定义:

{% get_comment_permalink comment "#c%(id)s-by-%(user_name)s"%}

使用的是python标准格式化字符串的方式。

不管你是否自定义也好,你都必须在模板的合适位置提供一个匹配命名锚的机制。例如:

{% for comment in comment_list %}
  <a name="c{{ comment.id }}"></a>
  <a href="{% get_comment_permalink comment %}">
    permalink for comment #{{ forloop.counter }}
  </a>
  ...
{% endfor %}

这块内容在使用safari浏览器的时候可能有个bug。

1.1.4 评论数

获取评论数量:

{% get_comment_count for [object] as [varname] %}

例如:

{% get_comment_count for entry as comment_count %}
This entry has {{ comment_count }} comments.

1.1.5 评论表单

使用render_comment_form或者get_comment_form在页面上显示输入评论的表单。

快速显示表单:

{% render_comment_form for [object] %}

使用了默认的comments/form.html模板。简单说就是傻瓜式,最丑的界面。

自定义表单:

使用get_comment_form标签获取一个form对象,然后自己写逻辑控制它的展示方式。

{% get_comment_form for [object] as [varname] %}

展示例子(当然,这个也很丑!):

{% get_comment_form for event as form %}
<table>
 <form action="{% comment_form_target %}" method="post">
  {% csrf_token %}
  {{ form }}
  <tr>
   <td colspan="2">
    <input type="submit" name="submit" value="Post">
    <input type="submit" name="preview" value="Preview">
   </td>
  </tr>
 </form>
</table>

提交地址:

上面的例子通过一个comment_form_target标签为form表单指定了正确的评论内容提交地址,请务必使用该方法:

<form action="{% comment_form_target %}" method="post">

提交后的重定向地址:

如果想在用户评论后将页面重定向到另外一个地址,请在form中插入一个隐藏的input标签,并命名为next,如下所示:

<input type="hidden" name="next" value="{% url 'my_comment_was_posted' %}" />

为已认证用户提供不同的表单:

很多时候我们要为登录的认证用户提供一些不同于匿名用户的内容,比如姓名、邮箱、网址等等,这些可以从用户数据和信息表内获得。其实,现在大多数的网站也只允许认证用户进行评论。要做到这点,你只需要简单的展示用户信息,或修改form表单即可,例如:

{% if user.is_authenticated %}
  {% get_comment_form for object as form %}
  <form action="{% comment_form_target %}" method="POST">
  {% csrf_token %}
  {{ form.comment }}
  {{ form.honeypot }}
  {{ form.content_type }}
  {{ form.object_pk }}
  {{ form.timestamp }}
  {{ form.security_hash }}
  <input type="hidden" name="next" value="{% url 'object_detail_view' object.id %}" />
  <input type="submit" value="提交评论" id="id_submit" />
  </form>
{% else %}
  <p>请先<a href="{% url 'auth_login' %}">登录</a>后方可评论.</p>
{% endif %}

上例中的honeypot(蜜罐,一种对攻击方进行欺骗的技术),能被用户看见,因此需要利用CSS将它隐藏起来。

#id_honeypot {
  display: none;
}

如果你想同时接受匿名评论,只需要将上面的else从句后面的代码修改为一个标准的评论表单就可以了。

1.1.6 评论表单注意事项

该插件的评论表单有一些重要的反垃圾机制,你需要特别注意:

form中包含了一些隐藏的域,例如评论对象的时间戳、信息等,还有一个用于验证信息的安全哈希。如果有不怀好意的人篡改这些数据,评论会被拒绝。如果你使用自定义的form,请确保这些字段原样的被引用。

时间戳用于确保“回复攻击”不会持续太久时间。那些在请求表单和提交表单时间差过长的用户,将被拒绝提交评论。(注:官档的意思是评论提交有时间限制要求?)

评论表单有一个honeypot域。这是一个陷阱,如果该域被填入任何数据,那么该评论会被拒绝提交。因为垃圾发送者往往自动的为表单的所有域填入一定数据,视图制造一个合法合格的提交数据单。

默认表单中上面的域都通过CSS进行了隐藏,并提供警告。如果你是自定义表单,请确保进行了同样的工作!

最后,本插件的防御机制,依赖django的csrf中间件,请确保它是开着的!否则,请使用csrf_protect装饰器对所有的使用评论表单的views进行装饰。

二、评论models

原型:class django_comments.models.Comment

它包含下面的字段:

content_object

评论的对象,例如一篇博客、图片、文章等等。这是一个GenericForeignKey外键。

content_type

一个指向ContentType的外键,用于保存评论对象的类型。要和上面的object区别开。

object_pk

对象的主键。一个TextField域。

site

评论提交的站点。外键。

user

指向评论的用户的外键。当匿名时,值为空。

user_name

用户名

user_email

用户邮箱

user_url

用户的网址。(很久以前的形式,现在基本都不要求填这个了。)

comment

评论的内容主体

submit_date

提交日期

ip_address

用户ip

is_public

True,则显示到页面上。

False,不显示到页面上。

is_removed

True,如果评论被移除了。用于跟踪那些被移除的评论,而不是简单的把他们直接删除。

(例如,有人言论不合适,管理员可以移除它,但是在原位置留下提示信息。)

源码:

from __future__ import unicode_literals

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
try:
  from django.urls import reverse
except ImportError:
  from django.core.urlresolvers import reverse # Django < 1.10

from .managers import CommentManager

COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)


class BaseCommentAbstractModel(models.Model):
  """
  An abstract base class that any custom comment models probably should
  subclass.
  """

  # Content-object field
  content_type = models.ForeignKey(ContentType,
                   verbose_name=_('content type'),
                   related_name="content_type_set_for_%(class)s",
                   on_delete=models.CASCADE)
  object_pk = models.TextField(_('object ID'))
  content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")

  # Metadata about the comment
  site = models.ForeignKey(Site, on_delete=models.CASCADE)

  class Meta:
    abstract = True

  def get_content_object_url(self):
    """
    Get a URL suitable for redirecting to the content object.
    """
    return reverse(
      "comments-url-redirect",
      args=(self.content_type_id, self.object_pk)
    )


@python_2_unicode_compatible
class CommentAbstractModel(BaseCommentAbstractModel):
  """
  A user comment about some object.
  """

  # Who posted this comment? If ``user`` is set then it was an authenticated
  # user; otherwise at least user_name should have been set and the comment
  # was posted by a non-authenticated user.
  user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
               blank=True, null=True, related_name="%(class)s_comments",
               on_delete=models.SET_NULL)
  user_name = models.CharField(_("user's name"), max_length=50, blank=True)
  # Explicit `max_length` to apply both to Django 1.7 and 1.8+.
  user_email = models.EmailField(_("user's email address"), max_length=254,
                  blank=True)
  user_url = models.URLField(_("user's URL"), blank=True)

  comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)

  # Metadata about the comment
  submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
  ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
  is_public = models.BooleanField(_('is public'), default=True,
                  help_text=_('Uncheck this box to make the comment effectively '
                        'disappear from the site.'))
  is_removed = models.BooleanField(_('is removed'), default=False,
                   help_text=_('Check this box if the comment is inappropriate. '
                         'A "This comment has been removed" message will '
                         'be displayed instead.'))

  # Manager
  objects = CommentManager()

  class Meta:
    abstract = True
    ordering = ('submit_date',)
    permissions = [("can_moderate", "Can moderate comments")]
    verbose_name = _('comment')
    verbose_name_plural = _('comments')

  def __str__(self):
    return "%s: %s..." % (self.name, self.comment[:50])

  def save(self, *args, **kwargs):
    if self.submit_date is None:
      self.submit_date = timezone.now()
    super(CommentAbstractModel, self).save(*args, **kwargs)

# 后面省略

三、自定义评论框架

很明显,这个插件还不够强大,功能还不够丰富,界面还不够美观。我们必须自定义整体框架!那么怎么办呢?

假如你自己在django-contrib-commests的基础上二次开发了一个叫做my_comment_app的评论框架。请这么设置它:

INSTALLED_APPS = [
  ...
  'my_comment_app',
  ...
]
COMMENTS_APP = 'my_comment_app'

在my_comment_app的__init__.py中定义新的模型级别的动作或行为。

简单的例子

例如有的网站希望用户在评论的时候,提供一个标题title。很显然现有的插件中的model没有这个字段,你必须自定义。怎么做?分三步:

1.创建一个自定义的comment模型,添加一个title字段;

2.创建一个自定义的comment form模型,同样地增加title字段;

3.自定义一个comment_app,定义一些新的方法,然后通知Django

如下创建包:

my_comment_app/
  __init__.py
  models.py
  forms.py

在models.py文件中编写一个CommentWithTitle模型类:

from django.db import models
from django_comments.abstracts import CommentAbstractModel

class CommentWithTitle(CommentAbstractModel):
  title = models.CharField(max_length=300)

然后在forms.py文件中编写新的form类,同时重写CommentForm.get_comment_create_data()方法,帮助我们增加title字段。

from django import forms
from django_comments.forms import CommentForm
from my_comment_app.models import CommentWithTitle

class CommentFormWithTitle(CommentForm):
  title = forms.CharField(max_length=300)

  def get_comment_create_data(self):
    # 使用父类的数据的同时增加title字段
    data = super(CommentFormWithTitle, self).get_comment_create_data()
    data['title'] = self.cleaned_data['title']
    return data

注:在django_comments.forms中提供了一些“helper”类,帮助我们更方便地进行自定义。

最后在my_comment_app/init.py中编写方法,通知Django我们所做的改动:

def get_model():
  from my_comment_app.models import CommentWithTitle
  return CommentWithTitle

def get_form():
  from my_comment_app.forms import CommentFormWithTitle
  return CommentFormWithTitle

注意:上面的import语句必须放在函数体内部,因为最新版本的django不允许在app的__init__.py的顶部import模块。

注意:不要循环导入模块,不要重复引入模块!

更多的自定义API

上面的例子是个通用的做法,如果还不能满足需求,那么可以使用下面的api,所有的自定义app都必须定义至少其中之一:

django_comments.get_model()

返回你要使用的自定义comment类。(请结合上面的例子进行理解。)

django_comments.get_form()

返回你要使用的自定义的comment form类。同上。

django_comments.get_form_target()

返回form在post时,提交的url地址。

django_comments.get_flag_url()

返回“flag this comment”视图的URL

默认情况下,它指的是django_comments.views.moderation.flag()

django_comments.get_delete_url()

返回“delete this comment” 视图的URL

默认情况下是django_comments.views.moderation.delete()

django_comments.get_approve_url()

返回“approve this comment from moderation” 视图的URL

默认情况下是django_comments.views.moderation.approve()

总结: Django Comment 评论插件原生的界面比较丑陋,但是通过自定制,可以改写出美观、适用的评论系统,比如博主个人主页的评论系统!

以上这篇基于Django contrib Comments 评论模块(详解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中__new__与__init__方法的区别详解
May 04 Python
Python中的条件判断语句与循环语句用法小结
Mar 21 Python
Python实现批量更换指定目录下文件扩展名的方法
Sep 19 Python
python控制windows剪贴板,向剪贴板中写入图片的实例
May 31 Python
在pandas多重索引multiIndex中选定指定索引的行方法
Nov 16 Python
python实现对任意大小图片均匀切割的示例
Dec 05 Python
Python中的 enum 模块源码详析
Jan 09 Python
flask框架jinja2模板与模板继承实例分析
Aug 01 Python
Python读取csv文件实例解析
Dec 30 Python
python实现同一局域网下传输图片
Mar 20 Python
python的数学算法函数及公式用法
Nov 18 Python
python 经纬度求两点距离、三点面积操作
Jun 03 Python
Python数据分析中Groupby用法之通过字典或Series进行分组的实例
Dec 08 #Python
python在ubuntu中的几种安装方法(小结)
Dec 08 #Python
Python编程之gui程序实现简单文件浏览器代码
Dec 08 #Python
Python中的pygal安装和绘制直方图代码分享
Dec 08 #Python
python的unittest测试类代码实例
Dec 07 #Python
Python numpy 常用函数总结
Dec 07 #Python
分享6个隐藏的python功能
Dec 07 #Python
You might like
一台收音机,让一家人都笑逐颜开!
2020/08/21 无线电
PHP脚本的10个技巧(4)
2006/10/09 PHP
php读取30天之内的根据算法排序的代码
2008/04/06 PHP
使ecshop模板中可引用常量的实现方法
2011/06/02 PHP
PHP批量上传图片的具体实现方法介绍.
2014/02/26 PHP
php图片添加文字水印实现代码
2016/03/15 PHP
PHP请求Socket接口测试实例
2016/08/12 PHP
PHP加密技术的简单实现
2016/09/04 PHP
PHP常用字符串函数用法实例总结
2020/06/04 PHP
搭建PhpStorm+PhpStudy开发环境的超详细教程
2020/09/17 PHP
图片自动缩小的js代码,用以防止图片撑破页面
2007/03/12 Javascript
IE6弹出“已终止操作”的解决办法
2010/11/27 Javascript
node.js中使用q.js实现api的promise化
2014/09/17 Javascript
JS实现网页上随滚动条滚动的层效果代码
2015/11/04 Javascript
AngularJS入门教程之链接与图片模板详解
2016/08/19 Javascript
ajax分页效果(bootstrap模态框)
2017/01/23 Javascript
常用的js方法合集
2017/03/10 Javascript
easyui-datagrid特殊字符不能显示的处理方法
2017/04/12 Javascript
Angular2使用Guard和Resolve进行验证和权限控制
2017/04/24 Javascript
NodeJS、NPM安装配置步骤(windows版本) 以及环境变量详解
2017/05/13 NodeJs
AngularJs用户登录问题处理(交互及验证、阻止FQ处理)
2017/10/26 Javascript
node thread.sleep实现示例
2018/06/20 Javascript
记一次Vue.js混入mixin的使用(分权限管理页面)
2019/04/17 Javascript
在Vue中使用icon 字体图标的方法
2019/06/14 Javascript
Vue formData实现图片上传
2019/08/20 Javascript
JavaScript如何把两个数组对象合并过程解析
2019/10/10 Javascript
nodeJs项目在阿里云的简单部署
2020/11/27 NodeJs
如何在 Vue 中使用 JSX
2021/02/14 Vue.js
Python内置函数reversed()用法分析
2018/03/20 Python
对python的bytes类型数据split分割切片方法
2018/12/04 Python
python生成13位或16位时间戳以及反向解析时间戳的实例
2020/03/03 Python
完美解决keras 读取多个hdf5文件进行训练的问题
2020/07/01 Python
招聘专员岗位职责
2014/03/07 职场文书
孝老爱亲模范事迹材料
2014/05/25 职场文书
简单聊一聊SQL注入及防止SQL注入
2022/03/23 MySQL
Apache自带的ab压力测试工具的实现
2022/07/23 Servers