Django contrib Comments 評論模塊詳解


老版本的Django中自帶一個評論框架。但是從1.6版本后,該框架獨立出去了,也就是本文的評論插件。

這個插件可給models附加評論,因此常被用於為博客文章、圖片、書籍章節或其它任何東西添加評論。

一、快速入門

快速使用步驟:

  1. 安裝包:pip install django-contrib-comments
  2. 在django的settings中的INSTALLED_APPS處添加'django.contrib.sites'進行app注冊,並設置SITE_ID值。
  3. 在django的settings中的INSTALLED_APPS處添加'django_comments'.
  4. 運行manage.py migrate創建評論數據表。
  5. 在項目的根urls.py文件中添加URLs:url(r'^comments/', include('django_comments.urls')),
  6. 使用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 評論插件原生的界面比較丑陋,但是通過自定制,可以改寫出美觀、適用的評論系統,比如博主個人主頁的評論系統!


歡迎大家訪問我的個人網站《劉江的博客和教程》www.liujiangblog.com

主要分享Python 及Django教程以及相關的博客!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM