diff --git a/django/contrib/comments/moderation.py b/django/contrib/comments/moderation.py index 9dd6e54732..e8cd08f589 100644 --- a/django/contrib/comments/moderation.py +++ b/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 @@ 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. diff --git a/docs/ref/contrib/comments/moderation.txt b/docs/ref/contrib/comments/moderation.txt index 545279b6c4..78725be0bd 100644 --- a/docs/ref/contrib/comments/moderation.txt +++ b/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: +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 diff --git a/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py b/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py index 4fe8b8ae12..e018133bca 100644 --- a/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py +++ b/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 @@ 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):