Fixed #11113: fixed a couple of issues that slipped through the cracks when comment moderation was added to `django.contrib.comments`.
The is a potentially backwards-incompatible change for users already relying on the internals of comment moderaration. To wit: * The moderation system now listens to the new `comment_will_be_posted`/`comment_was_posted` signals instead of `pre/post_save`. This means that import request-based information is available to moderation as it should be. * Some experimental code from `django.contrib.comments.moderation` has been removed. It was never intended to be merged into Django, and was completely untested and likely buggy. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10784 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
3da3716252
commit
d246401552
|
@ -2,8 +2,6 @@
|
|||
A generic comment-moderation system which allows configuration of
|
||||
moderation options on a per-model basis.
|
||||
|
||||
Originally part of django-comment-utils, by James Bennett.
|
||||
|
||||
To use, do two things:
|
||||
|
||||
1. Create or import a subclass of ``CommentModerator`` defining the
|
||||
|
@ -41,7 +39,7 @@ And finally register it for moderation::
|
|||
|
||||
moderator.register(Entry, EntryModerator)
|
||||
|
||||
This sample class would apply several moderation steps to each new
|
||||
This sample class would apply two moderation steps to each new
|
||||
comment submitted on an Entry:
|
||||
|
||||
* If the entry's ``enable_comments`` field is set to ``False``, the
|
||||
|
@ -54,19 +52,13 @@ For a full list of built-in moderation options and other
|
|||
configurability, see the documentation for the ``CommentModerator``
|
||||
class.
|
||||
|
||||
Several example subclasses of ``CommentModerator`` are provided in
|
||||
`django-comment-utils`_, both to provide common moderation options and to
|
||||
demonstrate some of the ways subclasses can customize moderation
|
||||
behavior.
|
||||
|
||||
.. _`django-comment-utils`: http://code.google.com/p/django-comment-utils/
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
from django.db.models import signals
|
||||
from django.contrib.comments import signals
|
||||
from django.db.models.base import ModelBase
|
||||
from django.template import Context, loader
|
||||
from django.contrib import comments
|
||||
|
@ -145,9 +137,10 @@ class CommentModerator(object):
|
|||
Most common moderation needs can be covered by changing these
|
||||
attributes, but further customization can be obtained by
|
||||
subclassing and overriding the following methods. Each method will
|
||||
be called with two arguments: ``comment``, which is the comment
|
||||
being submitted, and ``content_object``, which is the object the
|
||||
comment will be attached to::
|
||||
be called with three arguments: ``comment``, which is the comment
|
||||
being submitted, ``content_object``, which is the object the
|
||||
comment will be attached to, and ``request``, which is the
|
||||
``HttpRequest`` in which the comment is being submitted::
|
||||
|
||||
``allow``
|
||||
Should return ``True`` if the comment should be allowed to
|
||||
|
@ -200,7 +193,7 @@ class CommentModerator(object):
|
|||
raise ValueError("Cannot determine moderation rules because date field is set to a value in the future")
|
||||
return now - then
|
||||
|
||||
def allow(self, comment, content_object):
|
||||
def allow(self, comment, content_object, request):
|
||||
"""
|
||||
Determine whether a given comment is allowed to be posted on
|
||||
a given object.
|
||||
|
@ -217,7 +210,7 @@ class CommentModerator(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
def moderate(self, comment, content_object):
|
||||
def moderate(self, comment, content_object, request):
|
||||
"""
|
||||
Determine whether a given comment on a given object should be
|
||||
allowed to show up immediately, or should be marked non-public
|
||||
|
@ -232,57 +225,7 @@ class CommentModerator(object):
|
|||
return True
|
||||
return False
|
||||
|
||||
def comments_open(self, obj):
|
||||
"""
|
||||
Return ``True`` if new comments are being accepted for
|
||||
``obj``, ``False`` otherwise.
|
||||
|
||||
The algorithm for determining this is as follows:
|
||||
|
||||
1. If ``enable_field`` is set and the relevant field on
|
||||
``obj`` contains a false value, comments are not open.
|
||||
|
||||
2. If ``close_after`` is set and the relevant date field on
|
||||
``obj`` is far enough in the past, comments are not open.
|
||||
|
||||
3. If neither of the above checks determined that comments are
|
||||
not open, comments are open.
|
||||
|
||||
"""
|
||||
if self.enable_field:
|
||||
if not getattr(obj, self.enable_field):
|
||||
return False
|
||||
if self.auto_close_field and self.close_after:
|
||||
if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_close_field)).days >= self.close_after:
|
||||
return False
|
||||
return True
|
||||
|
||||
def comments_moderated(self, obj):
|
||||
"""
|
||||
Return ``True`` if new comments for ``obj`` are being
|
||||
automatically sent to moderation, ``False`` otherwise.
|
||||
|
||||
The algorithm for determining this is as follows:
|
||||
|
||||
1. If ``moderate_field`` is set and the relevant field on
|
||||
``obj`` contains a true value, comments are moderated.
|
||||
|
||||
2. If ``moderate_after`` is set and the relevant date field on
|
||||
``obj`` is far enough in the past, comments are moderated.
|
||||
|
||||
3. If neither of the above checks decided that comments are
|
||||
moderated, comments are not moderated.
|
||||
|
||||
"""
|
||||
if self.moderate_field:
|
||||
if getattr(obj, self.moderate_field):
|
||||
return True
|
||||
if self.auto_moderate_field and self.moderate_after:
|
||||
if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_moderate_field)).days >= self.moderate_after:
|
||||
return True
|
||||
return False
|
||||
|
||||
def email(self, comment, content_object):
|
||||
def email(self, comment, content_object, request):
|
||||
"""
|
||||
Send email notification of a new comment to site staff when email
|
||||
notifications have been requested.
|
||||
|
@ -341,8 +284,8 @@ class Moderator(object):
|
|||
from the comment models.
|
||||
|
||||
"""
|
||||
signals.pre_save.connect(self.pre_save_moderation, sender=comments.get_model())
|
||||
signals.post_save.connect(self.post_save_moderation, sender=comments.get_model())
|
||||
signals.comment_will_be_posted.connect(self.pre_save_moderation, sender=comments.get_model())
|
||||
signals.comment_was_posted.connect(self.post_save_moderation, sender=comments.get_model())
|
||||
|
||||
def register(self, model_or_iterable, moderation_class):
|
||||
"""
|
||||
|
@ -376,66 +319,35 @@ class Moderator(object):
|
|||
raise NotModerated("The model '%s' is not currently being moderated" % model._meta.module_name)
|
||||
del self._registry[model]
|
||||
|
||||
def pre_save_moderation(self, sender, instance, **kwargs):
|
||||
def pre_save_moderation(self, sender, comment, request, **kwargs):
|
||||
"""
|
||||
Apply any necessary pre-save moderation steps to new
|
||||
comments.
|
||||
|
||||
"""
|
||||
model = instance.content_type.model_class()
|
||||
if instance.id or (model not in self._registry):
|
||||
model = comment.content_type.model_class()
|
||||
if model not in self._registry:
|
||||
return
|
||||
content_object = instance.content_object
|
||||
content_object = comment.content_object
|
||||
moderation_class = self._registry[model]
|
||||
if not moderation_class.allow(instance, content_object): # Comment will get deleted in post-save hook.
|
||||
instance.moderation_disallowed = True
|
||||
return
|
||||
if moderation_class.moderate(instance, content_object):
|
||||
instance.is_public = False
|
||||
|
||||
def post_save_moderation(self, sender, instance, **kwargs):
|
||||
# Comment will be disallowed outright (HTTP 403 response)
|
||||
if not moderation_class.allow(comment, content_object, request):
|
||||
return False
|
||||
|
||||
if moderation_class.moderate(comment, content_object, request):
|
||||
comment.is_public = False
|
||||
|
||||
def post_save_moderation(self, sender, comment, request, **kwargs):
|
||||
"""
|
||||
Apply any necessary post-save moderation steps to new
|
||||
comments.
|
||||
|
||||
"""
|
||||
model = instance.content_type.model_class()
|
||||
model = comment.content_type.model_class()
|
||||
if model not in self._registry:
|
||||
return
|
||||
if hasattr(instance, 'moderation_disallowed'):
|
||||
instance.delete()
|
||||
return
|
||||
self._registry[model].email(instance, instance.content_object)
|
||||
|
||||
def comments_open(self, obj):
|
||||
"""
|
||||
Return ``True`` if new comments are being accepted for
|
||||
``obj``, ``False`` otherwise.
|
||||
|
||||
If no moderation rules have been registered for the model of
|
||||
which ``obj`` is an instance, comments are assumed to be open
|
||||
for that object.
|
||||
|
||||
"""
|
||||
model = obj.__class__
|
||||
if model not in self._registry:
|
||||
return True
|
||||
return self._registry[model].comments_open(obj)
|
||||
|
||||
def comments_moderated(self, obj):
|
||||
"""
|
||||
Return ``True`` if new comments for ``obj`` are being
|
||||
automatically sent to moderation, ``False`` otherwise.
|
||||
|
||||
If no moderation rules have been registered for the model of
|
||||
which ``obj`` is an instance, comments for that object are
|
||||
assumed not to be moderated.
|
||||
|
||||
"""
|
||||
model = obj.__class__
|
||||
if model not in self._registry:
|
||||
return False
|
||||
return self._registry[model].comments_moderated(obj)
|
||||
self._registry[model].email(comment, comment.content_object, request)
|
||||
|
||||
# Import this instance in your own code to use in registering
|
||||
# your models for moderation.
|
||||
|
|
|
@ -12,12 +12,10 @@ but the amount of comment spam circulating on the Web today
|
|||
essentially makes it necessary to have some sort of automatic
|
||||
moderation system in place for any application which makes use of
|
||||
comments. To make this easier to handle in a consistent fashion,
|
||||
``django.contrib.comments.moderation`` (based on `comment_utils`_)
|
||||
provides a generic, extensible comment-moderation system which can
|
||||
be applied to any model or set of models which want to make use of
|
||||
Django's comment system.
|
||||
``django.contrib.comments.moderation`` provides a generic, extensible
|
||||
comment-moderation system which can be applied to any model or set of
|
||||
models which want to make use of Django's comment system.
|
||||
|
||||
.. _`comment_utils`: http://code.google.com/p/django-comment-utils/
|
||||
|
||||
Overview
|
||||
========
|
||||
|
@ -140,29 +138,28 @@ Adding custom moderation methods
|
|||
--------------------------------
|
||||
|
||||
For situations where the built-in options listed above are not
|
||||
sufficient, subclasses of
|
||||
:class:`CommentModerator` can also
|
||||
override the methods which actually perform the moderation, and apply any
|
||||
logic they desire.
|
||||
:class:`CommentModerator` defines three
|
||||
methods which determine how moderation will take place; each method will be
|
||||
called by the moderation system and passed two arguments: ``comment``, which
|
||||
is the new comment being posted, and ``content_object``, which is the
|
||||
object the comment will be attached to:
|
||||
sufficient, subclasses of :class:`CommentModerator` can also override
|
||||
the methods which actually perform the moderation, and apply any logic
|
||||
they desire. :class:`CommentModerator` defines three methods which
|
||||
determine how moderation will take place; each method will be called
|
||||
by the moderation system and passed two arguments: ``comment``, which
|
||||
is the new comment being posted, ``content_object``, which is the
|
||||
object the comment will be attached to, and ``request``, which is the
|
||||
``HttpRequest`` in which the comment is being submitted:
|
||||
|
||||
.. method:: CommentModerator.allow(comment, content_object)
|
||||
.. method:: CommentModerator.allow(comment, content_object, request)
|
||||
|
||||
Should return ``True`` if the comment should be allowed to
|
||||
post on the content object, and ``False`` otherwise (in which
|
||||
case the comment will be immediately deleted).
|
||||
|
||||
.. method:: CommentModerator.email(comment, content_object)
|
||||
.. method:: CommentModerator.email(comment, content_object, request)
|
||||
|
||||
If email notification of the new comment should be sent to
|
||||
site staff or moderators, this method is responsible for
|
||||
sending the email.
|
||||
|
||||
.. method:: CommentModerator.moderate(comment, content_object)
|
||||
.. method:: CommentModerator.moderate(comment, content_object, request)
|
||||
|
||||
Should return ``True`` if the comment should be moderated (in
|
||||
which case its ``is_public`` field will be set to ``False``
|
||||
|
@ -217,18 +214,18 @@ models with an instance of the subclass.
|
|||
Determines how moderation is set up globally. The base
|
||||
implementation in
|
||||
:class:`Moderator` does this by
|
||||
attaching listeners to the :data:`~django.db.models.signals.pre_save`
|
||||
and :data:`~django.db.models.signals.post_save` signals from the
|
||||
attaching listeners to the :data:`~django.contrib.comments.signals.comment_will_be_posted`
|
||||
and :data:`~django.contrib.comments.signals.comment_was_posted` signals from the
|
||||
comment models.
|
||||
|
||||
.. method:: pre_save_moderation(sender, instance, **kwargs)
|
||||
.. method:: pre_save_moderation(sender, comment, request, **kwargs)
|
||||
|
||||
In the base implementation, applies all pre-save moderation
|
||||
steps (such as determining whether the comment needs to be
|
||||
deleted, or whether it needs to be marked as non-public or
|
||||
generate an email).
|
||||
|
||||
.. method:: post_save_moderation(sender, instance, **kwargs)
|
||||
.. method:: post_save_moderation(sender, comment, request, **kwargs)
|
||||
|
||||
In the base implementation, applies all post-save moderation
|
||||
steps (currently this consists entirely of deleting comments
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from regressiontests.comment_tests.tests import CommentTestCase, CT, Site
|
||||
from django.contrib.comments.forms import CommentForm
|
||||
from django.contrib.comments.models import Comment
|
||||
from django.contrib.comments.moderation import moderator, CommentModerator, AlreadyModerated
|
||||
from regressiontests.comment_tests.models import Entry
|
||||
|
@ -22,24 +23,26 @@ class CommentUtilsModeratorTests(CommentTestCase):
|
|||
fixtures = ["comment_utils.xml"]
|
||||
|
||||
def createSomeComments(self):
|
||||
c1 = Comment.objects.create(
|
||||
content_type = CT(Entry),
|
||||
object_pk = "1",
|
||||
user_name = "Joe Somebody",
|
||||
user_email = "jsomebody@example.com",
|
||||
user_url = "http://example.com/~joe/",
|
||||
comment = "First!",
|
||||
site = Site.objects.get_current(),
|
||||
)
|
||||
c2 = Comment.objects.create(
|
||||
content_type = CT(Entry),
|
||||
object_pk = "2",
|
||||
user_name = "Joe the Plumber",
|
||||
user_email = "joetheplumber@whitehouse.gov",
|
||||
user_url = "http://example.com/~joe/",
|
||||
comment = "Second!",
|
||||
site = Site.objects.get_current(),
|
||||
)
|
||||
# Tests for the moderation signals must actually post data
|
||||
# through the comment views, because only the comment views
|
||||
# emit the custom signals moderation listens for.
|
||||
e = Entry.objects.get(pk=1)
|
||||
data = self.getValidData(e)
|
||||
self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
|
||||
self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
|
||||
|
||||
# We explicitly do a try/except to get the comment we've just
|
||||
# posted because moderation may have disallowed it, in which
|
||||
# case we can just return it as None.
|
||||
try:
|
||||
c1 = Comment.objects.all()[0]
|
||||
except IndexError:
|
||||
c1 = None
|
||||
|
||||
try:
|
||||
c2 = Comment.objects.all()[0]
|
||||
except IndexError:
|
||||
c2 = None
|
||||
return c1, c2
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -51,17 +54,17 @@ class CommentUtilsModeratorTests(CommentTestCase):
|
|||
|
||||
def testEmailNotification(self):
|
||||
moderator.register(Entry, EntryModerator1)
|
||||
c1, c2 = self.createSomeComments()
|
||||
self.createSomeComments()
|
||||
self.assertEquals(len(mail.outbox), 2)
|
||||
|
||||
def testCommentsEnabled(self):
|
||||
moderator.register(Entry, EntryModerator2)
|
||||
c1, c2 = self.createSomeComments()
|
||||
self.createSomeComments()
|
||||
self.assertEquals(Comment.objects.all().count(), 1)
|
||||
|
||||
def testAutoCloseField(self):
|
||||
moderator.register(Entry, EntryModerator3)
|
||||
c1, c2 = self.createSomeComments()
|
||||
self.createSomeComments()
|
||||
self.assertEquals(Comment.objects.all().count(), 0)
|
||||
|
||||
def testAutoModerateField(self):
|
||||
|
|
Loading…
Reference in New Issue