老版本的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 評論對象
有兩種辦法:
- 直接引用評論對象。假設你的模板里已經有了一個叫做entry的評論對象,那么可以使用下面的方法獲得該對象的評論次數:
{% get_comment_count for entry as comment_count %}
- 使用對象的類型和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沒有這個字段,你必須自定義。怎么做?分三步:
- 創建一個自定義的comment模型,添加一個title字段;
- 創建一個自定義的comment form模型,同樣地增加title字段;
- 自定義一個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()