Ticket #9282: 9282.2.diff

File 9282.2.diff, 33.9 KB (added by thejaswi_puthraya, 7 years ago)

git-patch against the latest checkout. Contains tests and docs. Thanks ubernostrum

  • new file django/contrib/comments/moderators.py

    diff --git a/django/contrib/comments/moderators.py b/django/contrib/comments/moderators.py
    new file mode 100644
    index 0000000..13c5064
    - +  
     1"""
     2Copyright (c) 2007, James Bennett
     3All rights reserved.
     4
     5Redistribution and use in source and binary forms, with or without
     6modification, are permitted provided that the following conditions are
     7met:
     8
     9    * Redistributions of source code must retain the above copyright
     10      notice, this list of conditions and the following disclaimer.
     11    * Redistributions in binary form must reproduce the above
     12      copyright notice, this list of conditions and the following
     13      disclaimer in the documentation and/or other materials provided
     14      with the distribution.
     15    * Neither the name of the author nor the names of other
     16      contributors may be used to endorse or promote products derived
     17      from this software without specific prior written permission.
     18
     19THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30"""
     31
     32"""
     33A generic comment-moderation system which allows configuration of
     34moderation options on a per-model basis.
     35
     36To use, do two things:
     37
     381. Create or import a subclass of ``CommentModerator`` defining the
     39   options you want.
     40
     412. Import ``moderator`` from this module and register one or more
     42   models, passing the models and the ``CommentModerator`` options
     43   class you want to use.
     44
     45
     46Example
     47-------
     48
     49First, we define a simple model class which might represent entries in
     50a weblog::
     51   
     52    from django.db import models
     53   
     54    class Entry(models.Model):
     55        title = models.CharField(maxlength=250)
     56        body = models.TextField()
     57        pub_date = models.DateField()
     58        enable_comments = models.BooleanField()
     59
     60Then we create a ``CommentModerator`` subclass specifying some
     61moderation options::
     62   
     63    from django.contrib.comments.moderators import CommentModerator, moderator
     64   
     65    class EntryModerator(CommentModerator):
     66        email_notification = True
     67        enable_field = 'enable_comments'
     68
     69And finally register it for moderation::
     70   
     71    moderator.register(Entry, EntryModerator)
     72
     73This sample class would apply several moderation steps to each new
     74comment submitted on an Entry:
     75
     76* If the entry's ``enable_comments`` field is set to ``False``, the
     77  comment will be rejected (immediately deleted).
     78
     79* If the comment is successfully posted, an email notification of the
     80  comment will be sent to site staff.
     81
     82For a full list of built-in moderation options and other
     83configurability, see the documentation for the ``CommentModerator``
     84class.
     85
     86Several example subclasses of ``CommentModerator`` are provided in
     87`django-comment-utils`_, both to provide common moderation options and to
     88demonstrate some of the ways subclasses can customize moderation
     89behavior.
     90
     91.. _`django-comment-utils`: http://code.google.com/p/django-comment-utils/
     92"""
     93
     94
     95import datetime
     96
     97from django.conf import settings
     98from django.core.mail import send_mail
     99from django.db.models import signals
     100from django.db.models.base import ModelBase
     101from django.template import Context, loader
     102from django.contrib import comments
     103from django.contrib.sites.models import Site
     104
     105
     106class AlreadyModerated(Exception):
     107    """
     108    Raised when a model which is already registered for moderation is
     109    attempting to be registered again.
     110   
     111    """
     112    pass
     113
     114
     115class NotModerated(Exception):
     116    """
     117    Raised when a model which is not registered for moderation is
     118    attempting to be unregistered.
     119   
     120    """
     121    pass
     122
     123
     124class CommentModerator(object):
     125    """
     126    Encapsulates comment-moderation options for a given model.
     127   
     128    This class is not designed to be used directly, since it doesn't
     129    enable any of the available moderation options. Instead, subclass
     130    it and override attributes to enable different options::
     131   
     132    ``auto_close_field``
     133        If this is set to the name of a ``DateField`` or
     134        ``DateTimeField`` on the model for which comments are
     135        being moderated, new comments for objects of that model
     136        will be disallowed (immediately deleted) when a certain
     137        number of days have passed after the date specified in
     138        that field. Must be used in conjunction with
     139        ``close_after``, which specifies the number of days past
     140        which comments should be disallowed. Default value is
     141        ``None``.
     142   
     143    ``auto_moderate_field``
     144        Like ``auto_close_field``, but instead of outright
     145        deleting new comments when the requisite number of days
     146        have elapsed, it will simply set the ``is_public`` field
     147        of new comments to ``False`` before saving them. Must be
     148        used in conjunction with ``moderate_after``, which
     149        specifies the number of days past which comments should be
     150        moderated. Default value is ``None``.
     151   
     152    ``close_after``
     153        If ``auto_close_field`` is used, this must specify the
     154        number of days past the value of the field specified by
     155        ``auto_close_field`` after which new comments for an
     156        object should be disallowed. Default value is ``None``.
     157   
     158    ``email_notification``
     159        If ``True``, any new comment on an object of this model
     160        which survives moderation will generate an email to site
     161        staff. Default value is ``False``.
     162   
     163    ``enable_field``
     164        If this is set to the name of a ``BooleanField`` on the
     165        model for which comments are being moderated, new comments
     166        on objects of that model will be disallowed (immediately
     167        deleted) whenever the value of that field is ``False`` on
     168        the object the comment would be attached to. Default value
     169        is ``None``.
     170   
     171    ``moderate_after``
     172        If ``auto_moderate`` is used, this must specify the number
     173        of days past the value of the field specified by
     174        ``auto_moderate_field`` after which new comments for an
     175        object should be marked non-public. Default value is
     176        ``None``.
     177   
     178    Most common moderation needs can be covered by changing these
     179    attributes, but further customization can be obtained by
     180    subclassing and overriding the following methods. Each method will
     181    be called with two arguments: ``comment``, which is the comment
     182    being submitted, and ``content_object``, which is the object the
     183    comment will be attached to::
     184   
     185    ``allow``
     186        Should return ``True`` if the comment should be allowed to
     187        post on the content object, and ``False`` otherwise (in
     188        which case the comment will be immediately deleted).
     189   
     190    ``email``
     191        If email notification of the new comment should be sent to
     192        site staff or moderators, this method is responsible for
     193        sending the email.
     194   
     195    ``moderate``
     196        Should return ``True`` if the comment should be moderated
     197        (in which case its ``is_public`` field will be set to
     198        ``False`` before saving), and ``False`` otherwise (in
     199        which case the ``is_public`` field will not be changed).
     200   
     201    Subclasses which want to introspect the model for which comments
     202    are being moderated can do so through the attribute ``_model``,
     203    which will be the model class.
     204   
     205    """
     206    auto_close_field = None
     207    auto_moderate_field = None
     208    close_after = None
     209    email_notification = False
     210    enable_field = None
     211    moderate_after = None
     212   
     213    def __init__(self, model):
     214        self._model = model
     215   
     216    def _get_delta(self, now, then):
     217        """
     218        Internal helper which will return a ``datetime.timedelta``
     219        representing the time between ``now`` and ``then``. Assumes
     220        ``now`` is a ``datetime.date`` or ``datetime.datetime`` later
     221        than ``then``.
     222       
     223        If ``now`` and ``then`` are not of the same type due to one of
     224        them being a ``datetime.date`` and the other being a
     225        ``datetime.datetime``, both will be coerced to
     226        ``datetime.date`` before calculating the delta.
     227       
     228        """
     229        if now.__class__ is not then.__class__:
     230            now = datetime.date(now.year, now.month, now.day)
     231            then = datetime.date(then.year, then.month, then.day)
     232        if now < then:
     233            raise ValueError("Cannot determine moderation rules because date field is set to a value in the future")
     234        return now - then
     235       
     236    def allow(self, comment, content_object):
     237        """
     238        Determine whether a given comment is allowed to be posted on
     239        a given object.
     240       
     241        Return ``True`` if the comment should be allowed, ``False
     242        otherwise.
     243       
     244        """
     245        if self.enable_field:
     246            if not getattr(content_object, self.enable_field):
     247                return False
     248        if self.auto_close_field and self.close_after:
     249            if self._get_delta(datetime.datetime.now(), getattr(content_object, self.auto_close_field)).days >= self.close_after:
     250                return False
     251        return True
     252   
     253    def moderate(self, comment, content_object):
     254        """
     255        Determine whether a given comment on a given object should be
     256        allowed to show up immediately, or should be marked non-public
     257        and await approval.
     258       
     259        Return ``True`` if the comment should be moderated (marked
     260        non-public), ``False`` otherwise.
     261       
     262        """
     263        if self.auto_moderate_field and self.moderate_after:
     264            if self._get_delta(datetime.datetime.now(), getattr(content_object, self.auto_moderate_field)).days >= self.moderate_after:
     265                return True
     266        return False
     267   
     268    def comments_open(self, obj):
     269        """
     270        Return ``True`` if new comments are being accepted for
     271        ``obj``, ``False`` otherwise.
     272       
     273        The algorithm for determining this is as follows:
     274       
     275        1. If ``enable_field`` is set and the relevant field on
     276           ``obj`` contains a false value, comments are not open.
     277       
     278        2. If ``close_after`` is set and the relevant date field on
     279           ``obj`` is far enough in the past, comments are not open.
     280       
     281        3. If neither of the above checks determined that comments are
     282           not open, comments are open.
     283       
     284        """
     285        if self.enable_field:
     286            if not getattr(obj, self.enable_field):
     287                return False
     288        if self.auto_close_field and self.close_after:
     289            if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_close_field)).days >= self.close_after:
     290                return False
     291        return True
     292   
     293    def comments_moderated(self, obj):
     294        """
     295        Return ``True`` if new comments for ``obj`` are being
     296        automatically sent to moderation, ``False`` otherwise.
     297       
     298        The algorithm for determining this is as follows:
     299       
     300        1. If ``moderate_field`` is set and the relevant field on
     301           ``obj`` contains a true value, comments are moderated.
     302       
     303        2. If ``moderate_after`` is set and the relevant date field on
     304           ``obj`` is far enough in the past, comments are moderated.
     305       
     306        3. If neither of the above checks decided that comments are
     307           moderated, comments are not moderated.
     308       
     309        """
     310        if self.moderate_field:
     311            if getattr(obj, self.moderate_field):
     312                return True
     313        if self.auto_moderate_field and self.moderate_after:
     314            if self._get_delta(datetime.datetime.now(), getattr(obj, self.auto_moderate_field)).days >= self.moderate_after:
     315                return True
     316        return False
     317   
     318    def email(self, comment, content_object):
     319        """
     320        Send email notification of a new comment to site staff when email
     321        notifications have been requested.
     322       
     323        """
     324        if not self.email_notification:
     325            return
     326        recipient_list = [manager_tuple[1] for manager_tuple in settings.MANAGERS]
     327        t = loader.get_template('comments/comment_notification_email.txt')
     328        c = Context({ 'comment': comment,
     329                      'content_object': content_object })
     330        subject = '[%s] New comment posted on "%s"' % (Site.objects.get_current().name,
     331                                                          content_object)
     332        message = t.render(c)
     333        send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list, fail_silently=True)
     334
     335
     336class Moderator(object):
     337    """
     338    Handles moderation of a set of models.
     339   
     340    An instance of this class will maintain a list of one or more
     341    models registered for comment moderation, and their associated
     342    moderation classes, and apply moderation to all incoming comments.
     343   
     344    To register a model, obtain an instance of ``CommentModerator``
     345    (this module exports one as ``moderator``), and call its
     346    ``register`` method, passing the model class and a moderation
     347    class (which should be a subclass of ``CommentModerator``). Note
     348    that both of these should be the actual classes, not instances of
     349    the classes.
     350   
     351    To cease moderation for a model, call the ``unregister`` method,
     352    passing the model class.
     353   
     354    For convenience, both ``register`` and ``unregister`` can also
     355    accept a list of model classes in place of a single model; this
     356    allows easier registration of multiple models with the same
     357    ``CommentModerator`` class.
     358   
     359    The actual moderation is applied in two phases: one prior to
     360    saving a new comment, and the other immediately after saving. The
     361    pre-save moderation may mark a comment as non-public or mark it to
     362    be removed; the post-save moderation may delete a comment which
     363    was disallowed (there is currently no way to prevent the comment
     364    being saved once before removal) and, if the comment is still
     365    around, will send any notification emails the comment generated.
     366   
     367    """
     368    def __init__(self):
     369        self._registry = {}
     370        self.connect()
     371   
     372    def connect(self):
     373        """
     374        Hook up the moderation methods to pre- and post-save signals
     375        from the comment models.
     376       
     377        """
     378        signals.pre_save.connect(self.pre_save_moderation, sender=comments.get_model())
     379        signals.post_save.connect(self.post_save_moderation, sender=comments.get_model())
     380   
     381    def register(self, model_or_iterable, moderation_class):
     382        """
     383        Register a model or a list of models for comment moderation,
     384        using a particular moderation class.
     385       
     386        Raise ``AlreadyModerated`` if any of the models are already
     387        registered.
     388       
     389        """
     390        if isinstance(model_or_iterable, ModelBase):
     391            model_or_iterable = [model_or_iterable]
     392        for model in model_or_iterable:
     393            if model in self._registry:
     394                raise AlreadyModerated("The model '%s' is already being moderated" % model._meta.module_name)
     395            self._registry[model] = moderation_class(model)
     396   
     397    def unregister(self, model_or_iterable):
     398        """
     399        Remove a model or a list of models from the list of models
     400        whose comments will be moderated.
     401       
     402        Raise ``NotModerated`` if any of the models are not currently
     403        registered for moderation.
     404       
     405        """
     406        if isinstance(model_or_iterable, ModelBase):
     407            model_or_iterable = [model_or_iterable]
     408        for model in model_or_iterable:
     409            if model not in self._registry:
     410                raise NotModerated("The model '%s' is not currently being moderated" % model._meta.module_name)
     411            del self._registry[model]
     412   
     413    def pre_save_moderation(self, sender, instance, **kwargs):
     414        """
     415        Apply any necessary pre-save moderation steps to new
     416        comments.
     417       
     418        """
     419        model = instance.content_type.model_class()
     420        if instance.id or (model not in self._registry):
     421            return
     422        content_object = instance.content_object
     423        moderation_class = self._registry[model]
     424        if not moderation_class.allow(instance, content_object): # Comment will get deleted in post-save hook.
     425            instance.moderation_disallowed = True
     426            return
     427        if moderation_class.moderate(instance, content_object):
     428            instance.is_public = False
     429   
     430    def post_save_moderation(self, sender, instance, **kwargs):
     431        """
     432        Apply any necessary post-save moderation steps to new
     433        comments.
     434       
     435        """
     436        model = instance.content_type.model_class()
     437        if model not in self._registry:
     438            return
     439        if hasattr(instance, 'moderation_disallowed'):
     440            instance.delete()
     441            return
     442        self._registry[model].email(instance, instance.content_object)
     443
     444    def comments_open(self, obj):
     445        """
     446        Return ``True`` if new comments are being accepted for
     447        ``obj``, ``False`` otherwise.
     448       
     449        If no moderation rules have been registered for the model of
     450        which ``obj`` is an instance, comments are assumed to be open
     451        for that object.
     452       
     453        """
     454        model = obj.__class__
     455        if model not in self._registry:
     456            return True
     457        return self._registry[model].comments_open(obj)
     458
     459    def comments_moderated(self, obj):
     460        """
     461        Return ``True`` if new comments for ``obj`` are being
     462        automatically sent to moderation, ``False`` otherwise.
     463       
     464        If no moderation rules have been registered for the model of
     465        which ``obj`` is an instance, comments for that object are
     466        assumed not to be moderated.
     467       
     468        """
     469        model = obj.__class__
     470        if model not in self._registry:
     471            return False
     472        return self._registry[model].comments_moderated(obj)
     473
     474
     475# Import this instance in your own code to use in registering
     476# your models for moderation.
     477moderator = Moderator()
  • docs/ref/contrib/comments/index.txt

    diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt
    index 5aeebe3..2bca94e 100644
    a b More information 
    212212   settings
    213213   signals
    214214   upgrade
     215   moderators
    215216
  • new file docs/ref/contrib/comments/moderators.txt

    diff --git a/docs/ref/contrib/comments/moderators.txt b/docs/ref/contrib/comments/moderators.txt
    new file mode 100644
    index 0000000..8c1713e
    - +  
     1==========================
     2Generic comment moderation
     3==========================
     4
     5Django's bundled comments application is extremely useful on its own,
     6but the amount of comment spam circulating on the Web today
     7essentially makes it necessary to have some sort of automatic
     8moderation system in place for any application which makes use of
     9comments. To make this easier to handle in a consistent fashion,
     10``django.contrib.comments.moderators`` (based on `comment_utils`_)
     11provides a generic, extensible comment-moderation system which can
     12be applied to any model or set of models which want to make use of
     13Django's comment system.
     14
     15.. _`comment_utils`: http://code.google.com/p/django-comment-utils/
     16
     17
     18Overview
     19========
     20
     21The entire system is contained within ``django.contrib.comments.moderators``,
     22and uses a two-step process to enable moderation for any given model:
     23
     241. A subclass of ``django.contrib.comments.moderators.CommentModerator`` is
     25   defined which specifies the moderation options the model wants to
     26   enable.
     27
     282. The model is registered with the moderation system, passing in the
     29   model class and the class which specifies its moderation options.
     30
     31A simple example is the best illustration of this. Suppose we have the
     32following model, which would represent entries in a weblog::
     33
     34    from django.db import models
     35   
     36    class Entry(models.Model):
     37        title = models.CharField(maxlength=250)
     38        body = models.TextField()
     39        pub_date = models.DateTimeField()
     40        enable_comments = models.BooleanField()
     41
     42Now, suppose that we want the following steps to be applied whenever a
     43new comment is posted on an ``Entry``:
     44
     451. If the ``Entry``'s ``enable_comments`` field is ``False``, the
     46   comment will simply be disallowed (i.e., immediately deleted).
     47
     482. If the ``enable_comments`` field is ``True``, the comment will be
     49   allowed to save.
     50
     513. Once the comment is saved, an email should be sent to site staff
     52   notifying them of the new comment.
     53
     54Accomplishing this is fairly straightforward and requires very little
     55code::
     56
     57    from django.contrib.comments.moderators import CommentModerator, moderator
     58   
     59    class EntryModerator(CommentModerator):
     60        email_notification = True
     61        enable_field = 'enable_comments'
     62   
     63    moderator.register(Entry, EntryModerator)
     64
     65The ``CommentModerator`` class pre-defines a number of useful
     66moderation options which subclasses can enable or disable as desired,
     67and ``moderator`` knows how to work with them to determine whether to
     68allow a comment, whether to moderate a comment which will be allowed
     69to post, and whether to email notifications of new comments.
     70
     71
     72Built-in moderation options
     73---------------------------
     74
     75Most common comment-moderation needs can be handled by subclassing
     76``CommentModerator`` and changing the values of pre-defined
     77attributes; the full range of built-in options is as follows:
     78
     79    ``auto_close_field``
     80        If this is set to the name of a ``DateField`` or
     81        ``DateTimeField`` on the model for which comments are being
     82        moderated, new comments for objects of that model will be
     83        disallowed (immediately deleted) when a certain number of days
     84        have passed after the date specified in that field. Must be
     85        used in conjunction with ``close_after``, which specifies the
     86        number of days past which comments should be
     87        disallowed. Default value is ``None``.
     88   
     89    ``auto_moderate_field``
     90        Like ``auto_close_field``, but instead of outright deleting
     91        new comments when the requisite number of days have elapsed,
     92        it will simply set the ``is_public`` field of new comments to
     93        ``False`` before saving them. Must be used in conjunction with
     94        ``moderate_after``, which specifies the number of days past
     95        which comments should be moderated. Default value is ``None``.
     96   
     97    ``close_after``
     98        If ``auto_close_field`` is used, this must specify the number
     99        of days past the value of the field specified by
     100        ``auto_close_field`` after which new comments for an object
     101        should be disallowed. Default value is ``None``.
     102   
     103    ``email_notification``
     104        If ``True``, any new comment on an object of this model which
     105        survives moderation (i.e., is not deleted) will generate an
     106        email to site staff. Default value is ``False``.
     107   
     108    ``enable_field``
     109        If this is set to the name of a ``BooleanField`` on the model
     110        for which comments are being moderated, new comments on
     111        objects of that model will be disallowed (immediately deleted)
     112        whenever the value of that field is ``False`` on the object
     113        the comment would be attached to. Default value is ``None``.
     114   
     115    ``moderate_after``
     116        If ``auto_moderate`` is used, this must specify the number of
     117        days past the value of the field specified by
     118        ``auto_moderate_field`` after which new comments for an object
     119        should be marked non-public. Default value is ``None``.
     120
     121Simply subclassing ``CommentModerator`` and changing the values of
     122these options will automatically enable the various moderation methods
     123for any models registered using the subclass.
     124
     125
     126Adding custom moderation methods
     127--------------------------------
     128
     129For situations where the built-in options listed above are not
     130sufficient, subclasses of ``CommentModerator`` can also override the
     131methods which actually perform the moderation, and apply any logic
     132they desire. ``CommentModerator`` defines three methods which
     133determine how moderation will take place; each method will be called
     134by the moderation system and passed two arguments: ``comment``, which
     135is the new comment being posted, and ``content_object``, which is the
     136object the comment will be attached to:
     137
     138    ``allow``
     139        Should return ``True`` if the comment should be allowed to
     140        post on the content object, and ``False`` otherwise (in which
     141        case the comment will be immediately deleted).
     142   
     143    ``email``
     144        If email notification of the new comment should be sent to
     145        site staff or moderators, this method is responsible for
     146        sending the email.
     147   
     148    ``moderate``
     149        Should return ``True`` if the comment should be moderated (in
     150        which case its ``is_public`` field will be set to ``False``
     151        before saving), and ``False`` otherwise (in which case the
     152        ``is_public`` field will not be changed).
     153
     154
     155Registering models for moderation
     156---------------------------------
     157
     158The moderation system, represented by
     159``django.contrib.comments.moderators.moderator`` is an instance of the class
     160``django.contrib.comments.moderators.Moderator``, which allows registration and
     161"unregistration" of models via two methods:
     162
     163    ``register``
     164        Takes two arguments: the first should be either a model class
     165        or list of model classes, and the second should be a subclass
     166        of ``CommentModerator``, and register the model or models to
     167        be moderated using the options defined in the
     168        ``CommentModerator`` subclass. If any of the models are
     169        already registered for moderation, the exception
     170        ``django.contrib.comments.moderators.AlreadyModerated`` will be raised.
     171
     172    ``unregister``
     173        Takes one argument: a model class or list of model classes,
     174        and removes the model or models from the set of models which
     175        are being moderated. If any of the models are not currently
     176        being moderated, the exception
     177        ``django.contrib.comments.moderators.NotModerated`` will be raised.
     178
     179
     180Customizing the moderation system
     181---------------------------------
     182
     183Most use cases will work easily with simple subclassing of
     184``CommentModerator`` and registration with the provided ``moderator``
     185instance, but customization of global moderation behavior can be
     186achieved by subclassing ``Moderator`` and instead registering models
     187with an instance of the subclass.
     188
     189In addition to the ``register`` and ``unregister`` methods detailed
     190above, the following methods on ``Moderator`` can be overridden to
     191achieve customized behavior:
     192
     193    ``connect``
     194        Determines how moderation is set up globally. The base
     195        implementation in ``Moderator`` does this by attaching
     196        listeners to the ``pre_save`` and ``post_save`` signals from
     197        the comment models.
     198
     199    ``pre_save_moderation``
     200        In the base implementation, applies all pre-save moderation
     201        steps (such as determining whether the comment needs to be
     202        deleted, or whether it needs to be marked as non-public or
     203        generate an email).
     204
     205    ``post_save_moderation``
     206        In the base implementation, applies all post-save moderation
     207        steps (currently this consists entirely of deleting comments
     208        which were disallowed).
  • new file tests/regressiontests/comment_tests/fixtures/comment_utils.xml

    diff --git a/tests/regressiontests/comment_tests/fixtures/comment_utils.xml b/tests/regressiontests/comment_tests/fixtures/comment_utils.xml
    new file mode 100644
    index 0000000..a39bbf6
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<django-objects version="1.0">
     3  <object pk="1" model="comment_tests.entry">
     4      <field type="CharField" name="title">ABC</field>
     5      <field type="TextField" name="body">This is the body</field>
     6      <field type="DateField" name="pub_date">2008-01-01</field>
     7      <field type="BooleanField" name="enable_comments">True</field>
     8  </object>
     9  <object pk="2" model="comment_tests.entry">
     10      <field type="CharField" name="title">XYZ</field>
     11      <field type="TextField" name="body">Text here</field>
     12      <field type="DateField" name="pub_date">2008-01-02</field>
     13      <field type="BooleanField" name="enable_comments">False</field>
     14  </object>
     15</django-objects>
  • tests/regressiontests/comment_tests/models.py

    diff --git a/tests/regressiontests/comment_tests/models.py b/tests/regressiontests/comment_tests/models.py
    index 28022e2..62f4168 100644
    a b class Article(models.Model): 
    2020    def __str__(self):
    2121        return self.headline
    2222
     23class Entry(models.Model):
     24    title = models.CharField(max_length=250)
     25    body = models.TextField()
     26    pub_date = models.DateField()
     27    enable_comments = models.BooleanField()
     28
     29    def __str__(self):
     30        return self.title
  • tests/regressiontests/comment_tests/tests/__init__.py

    diff --git a/tests/regressiontests/comment_tests/tests/__init__.py b/tests/regressiontests/comment_tests/tests/__init__.py
    index 09026aa..449fea4 100644
    a b from regressiontests.comment_tests.tests.comment_form_tests import * 
    8686from regressiontests.comment_tests.tests.templatetag_tests import *
    8787from regressiontests.comment_tests.tests.comment_view_tests import *
    8888from regressiontests.comment_tests.tests.moderation_view_tests import *
     89from regressiontests.comment_tests.tests.comment_utils_moderators_tests import *
  • new file tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py

    diff --git a/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py b/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py
    new file mode 100644
    index 0000000..4e6a03d
    - +  
     1from regressiontests.comment_tests.tests import CommentTestCase, CT, Site
     2from django.contrib.comments.models import Comment
     3from django.contrib.comments.moderators import moderator, CommentModerator, AlreadyModerated
     4from regressiontests.comment_tests.models import Entry
     5from django.core import mail
     6
     7class EntryModerator1(CommentModerator):
     8    email_notification = True
     9
     10class EntryModerator2(CommentModerator):
     11    enable_field = 'enable_comments'
     12
     13class EntryModerator3(CommentModerator):
     14    auto_close_field = 'pub_date'
     15    close_after = 7
     16
     17class EntryModerator4(CommentModerator):
     18    auto_moderate_field = 'pub_date'
     19    moderate_after = 7
     20
     21class CommentUtilsModeratorTests(CommentTestCase):
     22    fixtures = ["comment_utils.xml"]
     23
     24    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        )
     43        return c1, c2
     44
     45    def tearDown(self):
     46        moderator.unregister(Entry)
     47
     48    def testRegisterExistingModel(self):
     49        moderator.register(Entry, EntryModerator1)
     50        self.assertRaises(AlreadyModerated, moderator.register, Entry, EntryModerator1)
     51
     52    def testEmailNotification(self):
     53        moderator.register(Entry, EntryModerator1)
     54        c1, c2 = self.createSomeComments()
     55        self.assertEquals(len(mail.outbox), 2)
     56
     57    def testCommentsEnabled(self):
     58        moderator.register(Entry, EntryModerator2)
     59        c1, c2 = self.createSomeComments()
     60        self.assertEquals(Comment.objects.all().count(), 1)
     61
     62    def testAutoCloseField(self):
     63        moderator.register(Entry, EntryModerator3)
     64        c1, c2 = self.createSomeComments()
     65        self.assertEquals(Comment.objects.all().count(), 0)       
     66
     67    def testAutoModerateField(self):
     68        moderator.register(Entry, EntryModerator4)
     69        c1, c2 = self.createSomeComments()
     70        self.assertEquals(c2.is_public, False)       
  • tests/runtests.py

    diff --git a/tests/runtests.py b/tests/runtests.py
    index cc9594b..656d669 100755
    a b def django_tests(verbosity, interactive, test_labels): 
    110110        'django.middleware.common.CommonMiddleware',
    111111    )
    112112    settings.SITE_ID = 1
     113    # For testing comment-utils, we require the MANAGERS attribute
     114    # to be set, so that a test email is sent out which we catch
     115    # in our tests.
     116    settings.MANAGERS = ("admin@djangoproject.com",)
    113117
    114118    # Load all the ALWAYS_INSTALLED_APPS.
    115119    # (This import statement is intentionally delayed until after we
  • new file tests/templates/comments/comment_notification_email.txt

    diff --git a/tests/templates/comments/comment_notification_email.txt b/tests/templates/comments/comment_notification_email.txt
    new file mode 100644
    index 0000000..63f1493
    - +  
     1A comment has been posted on {{ content_object }}.
     2The comment reads as follows:
     3{{ comment }}
Back to Top