Ticket #11113: comment-moderation-signals.diff

File comment-moderation-signals.diff, 15.8 KB (added by James Bennett, 15 years ago)
  • django/contrib/comments/moderation.py

     
    22A generic comment-moderation system which allows configuration of
    33moderation options on a per-model basis.
    44
    5 Originally part of django-comment-utils, by James Bennett.
    6 
    75To use, do two things:
    86
    971. Create or import a subclass of ``CommentModerator`` defining the
     
    4139
    4240    moderator.register(Entry, EntryModerator)
    4341
    44 This sample class would apply several moderation steps to each new
     42This sample class would apply two moderation steps to each new
    4543comment submitted on an Entry:
    4644
    4745* If the entry's ``enable_comments`` field is set to ``False``, the
     
    5452configurability, see the documentation for the ``CommentModerator``
    5553class.
    5654
    57 Several example subclasses of ``CommentModerator`` are provided in
    58 `django-comment-utils`_, both to provide common moderation options and to
    59 demonstrate some of the ways subclasses can customize moderation
    60 behavior.
    61 
    62 .. _`django-comment-utils`: http://code.google.com/p/django-comment-utils/
    6355"""
    6456
    6557import datetime
    6658
    6759from django.conf import settings
    6860from django.core.mail import send_mail
    69 from django.db.models import signals
     61from django.contrib.comments import signals
    7062from django.db.models.base import ModelBase
    7163from django.template import Context, loader
    7264from django.contrib import comments
     
    145137    Most common moderation needs can be covered by changing these
    146138    attributes, but further customization can be obtained by
    147139    subclassing and overriding the following methods. Each method will
    148     be called with two arguments: ``comment``, which is the comment
    149     being submitted, and ``content_object``, which is the object the
    150     comment will be attached to::
     140    be called with three arguments: ``comment``, which is the comment
     141    being submitted, ``content_object``, which is the object the
     142    comment will be attached to, and ``request``, which is the
     143    ``HttpRequest`` in which the comment is being submitted::
    151144
    152145    ``allow``
    153146        Should return ``True`` if the comment should be allowed to
     
    200193            raise ValueError("Cannot determine moderation rules because date field is set to a value in the future")
    201194        return now - then
    202195
    203     def allow(self, comment, content_object):
     196    def allow(self, comment, content_object, request):
    204197        """
    205198        Determine whether a given comment is allowed to be posted on
    206199        a given object.
     
    217210                return False
    218211        return True
    219212
    220     def moderate(self, comment, content_object):
     213    def moderate(self, comment, content_object, request):
    221214        """
    222215        Determine whether a given comment on a given object should be
    223216        allowed to show up immediately, or should be marked non-public
     
    232225                return True
    233226        return False
    234227
    235     def comments_open(self, obj):
     228    def email(self, comment, content_object, request):
    236229        """
    237         Return ``True`` if new comments are being accepted for
    238         ``obj``, ``False`` otherwise.
    239 
    240         The algorithm for determining this is as follows:
    241 
    242         1. If ``enable_field`` is set and the relevant field on
    243            ``obj`` contains a false value, comments are not open.
    244 
    245         2. If ``close_after`` is set and the relevant date field on
    246            ``obj`` is far enough in the past, comments are not open.
    247 
    248         3. If neither of the above checks determined that comments are
    249            not open, comments are open.
    250 
    251         """
    252         if self.enable_field:
    253             if not getattr(obj, self.enable_field):
    254                 return False
    255         if self.auto_close_field and self.close_after:
    256             if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_close_field)).days >= self.close_after:
    257                 return False
    258         return True
    259 
    260     def comments_moderated(self, obj):
    261         """
    262         Return ``True`` if new comments for ``obj`` are being
    263         automatically sent to moderation, ``False`` otherwise.
    264 
    265         The algorithm for determining this is as follows:
    266 
    267         1. If ``moderate_field`` is set and the relevant field on
    268            ``obj`` contains a true value, comments are moderated.
    269 
    270         2. If ``moderate_after`` is set and the relevant date field on
    271            ``obj`` is far enough in the past, comments are moderated.
    272 
    273         3. If neither of the above checks decided that comments are
    274            moderated, comments are not moderated.
    275 
    276         """
    277         if self.moderate_field:
    278             if getattr(obj, self.moderate_field):
    279                 return True
    280         if self.auto_moderate_field and self.moderate_after:
    281             if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_moderate_field)).days >= self.moderate_after:
    282                 return True
    283         return False
    284 
    285     def email(self, comment, content_object):
    286         """
    287230        Send email notification of a new comment to site staff when email
    288231        notifications have been requested.
    289232
     
    341284        from the comment models.
    342285
    343286        """
    344         signals.pre_save.connect(self.pre_save_moderation, sender=comments.get_model())
    345         signals.post_save.connect(self.post_save_moderation, sender=comments.get_model())
     287        signals.comment_will_be_posted.connect(self.pre_save_moderation, sender=comments.get_model())
     288        signals.comment_was_posted.connect(self.post_save_moderation, sender=comments.get_model())
    346289
    347290    def register(self, model_or_iterable, moderation_class):
    348291        """
     
    376319                raise NotModerated("The model '%s' is not currently being moderated" % model._meta.module_name)
    377320            del self._registry[model]
    378321
    379     def pre_save_moderation(self, sender, instance, **kwargs):
     322    def pre_save_moderation(self, sender, comment, request, **kwargs):
    380323        """
    381324        Apply any necessary pre-save moderation steps to new
    382325        comments.
    383326
    384327        """
    385         model = instance.content_type.model_class()
    386         if instance.id or (model not in self._registry):
     328        model = comment.content_type.model_class()
     329        if model not in self._registry:
    387330            return
    388         content_object = instance.content_object
     331        content_object = comment.content_object
    389332        moderation_class = self._registry[model]
    390         if not moderation_class.allow(instance, content_object): # Comment will get deleted in post-save hook.
    391             instance.moderation_disallowed = True
    392             return
    393         if moderation_class.moderate(instance, content_object):
    394             instance.is_public = False
     333        if not moderation_class.allow(comment, content_object, request): # Comment will be disallowed outright (HTTP 403 response)
     334            return False
     335        if moderation_class.moderate(comment, content_object, request):
     336            comment.is_public = False
    395337
    396     def post_save_moderation(self, sender, instance, **kwargs):
     338    def post_save_moderation(self, sender, comment, request, **kwargs):
    397339        """
    398340        Apply any necessary post-save moderation steps to new
    399341        comments.
    400342
    401343        """
    402         model = instance.content_type.model_class()
     344        model = comment.content_type.model_class()
    403345        if model not in self._registry:
    404346            return
    405         if hasattr(instance, 'moderation_disallowed'):
    406             instance.delete()
    407             return
    408         self._registry[model].email(instance, instance.content_object)
     347        self._registry[model].email(comment, comment.content_object, request)
    409348
    410     def comments_open(self, obj):
    411         """
    412         Return ``True`` if new comments are being accepted for
    413         ``obj``, ``False`` otherwise.
    414349
    415         If no moderation rules have been registered for the model of
    416         which ``obj`` is an instance, comments are assumed to be open
    417         for that object.
    418 
    419         """
    420         model = obj.__class__
    421         if model not in self._registry:
    422             return True
    423         return self._registry[model].comments_open(obj)
    424 
    425     def comments_moderated(self, obj):
    426         """
    427         Return ``True`` if new comments for ``obj`` are being
    428         automatically sent to moderation, ``False`` otherwise.
    429 
    430         If no moderation rules have been registered for the model of
    431         which ``obj`` is an instance, comments for that object are
    432         assumed not to be moderated.
    433 
    434         """
    435         model = obj.__class__
    436         if model not in self._registry:
    437             return False
    438         return self._registry[model].comments_moderated(obj)
    439 
    440350# Import this instance in your own code to use in registering
    441351# your models for moderation.
    442352moderator = Moderator()
  • tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py

     
    11from regressiontests.comment_tests.tests import CommentTestCase, CT, Site
     2from django.contrib.comments.forms import CommentForm
    23from django.contrib.comments.models import Comment
    34from django.contrib.comments.moderation import moderator, CommentModerator, AlreadyModerated
    45from regressiontests.comment_tests.models import Entry
     
    2223    fixtures = ["comment_utils.xml"]
    2324
    2425    def createSomeComments(self):
    25         c1 = Comment.objects.create(
    26             content_type = CT(Entry),
    27             object_pk = "1",
    28             user_name = "Joe Somebody",
    29             user_email = "jsomebody@example.com",
    30             user_url = "http://example.com/~joe/",
    31             comment = "First!",
    32             site = Site.objects.get_current(),
    33         )
    34         c2 = Comment.objects.create(
    35             content_type = CT(Entry),
    36             object_pk = "2",
    37             user_name = "Joe the Plumber",
    38             user_email = "joetheplumber@whitehouse.gov",
    39             user_url = "http://example.com/~joe/",
    40             comment = "Second!",
    41             site = Site.objects.get_current(),
    42         )
     26        # Tests for the moderation signals must actually post data
     27        # through the comment views, because only the comment views
     28        # emit the custom signals moderation listens for.
     29        e = Entry.objects.get(pk=1)
     30        data = self.getValidData(e)
     31        self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
     32        self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
     33
     34        # We explicitly do a try/except to get the comment we've just
     35        # posted because moderation may have disallowed it, in which
     36        # case we can just return it as None.
     37        try:
     38            c1 = Comment.objects.all()[0]
     39        except IndexError:
     40            c1 = None
     41
     42        try:
     43            c2 = Comment.objects.all()[0]
     44        except IndexError:
     45            c2 = None
    4346        return c1, c2
    4447
    4548    def tearDown(self):
     
    5154
    5255    def testEmailNotification(self):
    5356        moderator.register(Entry, EntryModerator1)
    54         c1, c2 = self.createSomeComments()
     57        self.createSomeComments()
    5558        self.assertEquals(len(mail.outbox), 2)
    5659
    5760    def testCommentsEnabled(self):
    5861        moderator.register(Entry, EntryModerator2)
    59         c1, c2 = self.createSomeComments()
     62        self.createSomeComments()
    6063        self.assertEquals(Comment.objects.all().count(), 1)
    6164
    6265    def testAutoCloseField(self):
    6366        moderator.register(Entry, EntryModerator3)
    64         c1, c2 = self.createSomeComments()
     67        self.createSomeComments()
    6568        self.assertEquals(Comment.objects.all().count(), 0)
    6669
    6770    def testAutoModerateField(self):
  • docs/ref/contrib/comments/moderation.txt

     
    1212essentially makes it necessary to have some sort of automatic
    1313moderation system in place for any application which makes use of
    1414comments. To make this easier to handle in a consistent fashion,
    15 ``django.contrib.comments.moderation`` (based on `comment_utils`_)
    16 provides a generic, extensible comment-moderation system which can
    17 be applied to any model or set of models which want to make use of
    18 Django's comment system.
     15``django.contrib.comments.moderation`` provides a generic, extensible
     16comment-moderation system which can be applied to any model or set of
     17models which want to make use of Django's comment system.
    1918
    20 .. _`comment_utils`: http://code.google.com/p/django-comment-utils/
    2119
    2220Overview
    2321========
     
    140138--------------------------------
    141139
    142140For situations where the built-in options listed above are not
    143 sufficient, subclasses of
    144 :class:`CommentModerator` can also
    145 override the methods which actually perform the moderation, and apply any
    146 logic they desire.
    147 :class:`CommentModerator` defines three
    148 methods which determine how moderation will take place; each method will be
    149 called by the moderation system and passed two arguments: ``comment``, which
    150 is the new comment being posted, and ``content_object``, which is the
    151 object the comment will be attached to:
     141sufficient, subclasses of :class:`CommentModerator` can also override
     142the methods which actually perform the moderation, and apply any logic
     143they desire.  :class:`CommentModerator` defines three methods which
     144determine how moderation will take place; each method will be called
     145by the moderation system and passed two arguments: ``comment``, which
     146is the new comment being posted, ``content_object``, which is the
     147object the comment will be attached to, and ``request``, which is the
     148``HttpRequest`` in which the comment is being submitted:
    152149
    153 .. method:: CommentModerator.allow(comment, content_object)
     150.. method:: CommentModerator.allow(comment, content_object, request)
    154151
    155152    Should return ``True`` if the comment should be allowed to
    156153    post on the content object, and ``False`` otherwise (in which
    157154    case the comment will be immediately deleted).
    158155
    159 .. method:: CommentModerator.email(comment, content_object)
     156.. method:: CommentModerator.email(comment, content_object, request)
    160157
    161158    If email notification of the new comment should be sent to
    162159    site staff or moderators, this method is responsible for
    163160    sending the email.
    164161
    165 .. method:: CommentModerator.moderate(comment, content_object)
     162.. method:: CommentModerator.moderate(comment, content_object, request)
    166163
    167164    Should return ``True`` if the comment should be moderated (in
    168165    which case its ``is_public`` field will be set to ``False``
     
    217214        Determines how moderation is set up globally. The base
    218215        implementation in
    219216        :class:`Moderator` does this by
    220         attaching listeners to the :data:`~django.db.models.signals.pre_save`
    221         and :data:`~django.db.models.signals.post_save` signals from the
     217        attaching listeners to the :data:`~django.contrib.comments.signals.comment_will_be_posted`
     218        and :data:`~django.contrib.comments.signals.comment_was_posted` signals from the
    222219        comment models.
    223220
    224     .. method:: pre_save_moderation(sender, instance, **kwargs)
     221    .. method:: pre_save_moderation(sender, comment, request, **kwargs)
    225222
    226223        In the base implementation, applies all pre-save moderation
    227224        steps (such as determining whether the comment needs to be
    228225        deleted, or whether it needs to be marked as non-public or
    229226        generate an email).
    230227
    231     .. method:: post_save_moderation(sender, instance, **kwargs)
     228    .. method:: post_save_moderation(sender, comment, request, **kwargs)
    232229
    233230        In the base implementation, applies all post-save moderation
    234231        steps (currently this consists entirely of deleting comments
Back to Top