Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #11113: fixed a couple of issues that slipped through the crack…

…s 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
  • Loading branch information...
commit d246401552ee37db485220adccd7b88693564f2a 1 parent 3da3716
@jacobian jacobian authored
View
138 django/contrib/comments/moderation.py
@@ -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 @@ class EntryModerator(CommentModerator):
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 @@ class EntryModerator(CommentModerator):
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 @@ def _get_delta(self, now, then):
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 @@ def allow(self, comment, content_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 @@ def moderate(self, comment, content_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 @@ def connect(self):
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 @@ def unregister(self, model_or_iterable):
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.
View
41 docs/ref/contrib/comments/moderation.txt
@@ -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:
-
-.. method:: CommentModerator.allow(comment, content_object)
+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, 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
View
45 tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py
@@ -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 @@ def testRegisterExistingModel(self):
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):
Please sign in to comment.
Something went wrong with that request. Please try again.