========================== Generic comment moderation ========================== Django's bundled comments application is extremely useful on its own, 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, ``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. Overview ======== The entire system is contained within ``comment_utils.moderation``, and uses a two-step process to enable moderation for any given model: 1. A subclass of ``comment_utils.moderation.CommentModerator`` is defined which specifies the moderation options the model wants to enable. 2. The model is registered with the moderation system, passing in the model class and the class which specifies its moderation options. A simple example is the best illustration of this. Suppose we have the following model, which would represent entries in a weblog:: from django.db import models class Entry(models.Model): title = models.CharField(maxlength=250) body = models.TextField() pub_date = models.DateTimeField() enable_comments = models.BooleanField() Now, suppose that we want the following steps to be applied whenever a new comment is posted on an ``Entry``: 1. If the ``Entry``'s ``enable_comments`` field is ``False``, the comment will simply be disallowed (i.e., immediately deleted). 2. If the ``enable_comments`` field is ``True``, the comment will be allowed to save, but will first be submitted to an Akismet spam check and have its ``is_public`` field set to ``False`` if Akismet thinks the comment is spam. 3. Once the comment is saved, an email should be sent to site staff notifying them of the new comment. Accomplishing this is fairly straightforward and requires very little code:: from comment_utils.moderation import CommentModerator, moderator class EntryModerator(CommentModerator): akismet = True email_notification = True enable_field = 'enable_comments' moderator.register(Entry, EntryModerator) The ``CommentModerator`` class pre-defines a number of useful moderation options which subclasses can enable or disable as desired, and ``moderator`` knows how to work with them to determine whether to allow a comment, whether to moderate a comment which will be allowed to post, and whether to email notifications of new comments. Built-in moderation options --------------------------- Most common comment-moderation needs can be handled by subclassing ``CommentModerator`` and changing the values of pre-defined attributes; the full range of built-in options is as follows:: ``akismet`` If ``True``, comments will be submitted to an Akismet spam check and, if Akismet thinks they're spam, will have their ``is_public`` field set to ``False`` before saving. If this is enabled, you will need to have the Python Akismet module installed, and you will need to add the setting ``AKISMET_API_KEY`` to your Django settings file; the value of this setting should be a valid Akismet API key. Default value is ``False``. ``auto_close_field`` If this is set to the name of a ``DateField`` or ``DateTimeField`` on the model for which comments are being moderated, new comments for objects of that model will be disallowed (immediately deleted) when a certain number of days have passed after the date specified in that field. Must be used in conjunction with ``close_after``, which specifies the number of days past which comments should be disallowed. Default value is ``None``. ``auto_moderate_field`` Like ``auto_close_field``, but instead of outright deleting new comments when the requisite number of days have elapsed, it will simply set the ``is_public`` field of new comments to ``False`` before saving them. Must be used in conjunction with ``moderate_after``, which specifies the number of days past which comments should be moderated. Default value is ``None``. ``close_after`` If ``auto_close_field`` is used, this must specify the number of days past the value of the field specified by ``auto_close_field`` after which new comments for an object should be disallowed. Default value is ``None``. ``email_notification`` If ``True``, any new comment on an object of this model which survives moderation (i.e., is not deleted) will generate an email to site staff. Default value is ``False``. ``enable_field`` If this is set to the name of a ``BooleanField`` on the model for which comments are being moderated, new comments on objects of that model will be disallowed (immediately deleted) whenever the value of that field is ``False`` on the object the comment would be attached to. Default value is ``None``. ``moderate_after`` If ``auto_moderate`` is used, this must specify the number of days past the value of the field specified by ``auto_moderate_field`` after which new comments for an object should be marked non-public. Default value is ``None``. Simply subclassing ``CommentModerator`` and changing the values of these options will automatically enable the various moderation methods for any models registered using the subclass. Adding custom moderation methods -------------------------------- For situations where the built-in options listed above are not sufficient, subclasses of ``CommentModerator`` can also override the methods which actually perform the moderation, and apply any logic they desire. ``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:: ``allow`` 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). ``email`` If email notification of the new comment should be sent to site staff or moderators, this method is responsible for sending the email. ``moderate`` Should return ``True`` if the comment should be moderated (in which case its ``is_public`` field will be set to ``False`` before saving), and ``False`` otherwise (in which case the ``is_public`` field will not be changed). Built-in subclasses of ``CommentModerator`` ------------------------------------------- In order to make common cases simpler, and to provide examples of how ``CommentModerator`` can be adapted to various types of moderation, three subclasses are included which set up different moderation options:: ``AkismetModerator`` Applies an Akismet spam check to all new comments for the model on which it is used. ``AlwaysModerate`` Forces all new comments for its model into moderation (by setting ``is_public=False``). ``ModerateFirstTimers`` Automatically moderates all comments from anyone who has not previously had a comment approved, while allowing all other comments to skip moderation. Registering models for moderation --------------------------------- The moderation system, represented by ``comment_utils.moderation.moderator`` is an instance of the class ``comment_utils.moderation.Moderator``, which allows registration and "unregistration" of models via two methods:: ``register`` Takes two arguments: the first should be either a model class or list of model classes, and the second should be a subclass of ``CommentModerator``, and register the model or models to be moderated using the options defined in the ``CommentModerator`` subclass. If any of the models are already registered for moderation, the exception ``comment_utils.moderation.AlreadyModerated`` will be raised. ``unregister`` Takes one argument: a model class or list of model classes, and removes the model or models from the set of models which are being moderated. If any of the models are not currently being moderated, the exception ``comment_utils.moderation.NotModerated`` will be raised. Customizing the moderation system --------------------------------- Most use cases will work easily with simple subclassing of ``CommentModerator`` and registration with the provided ``moderator`` instance, but customization of global moderation behavior can be achieved by subclassing ``Moderator`` and instead registering models with an instance of the subclass. In addition to the ``register`` and ``unregister`` methods detailed above, the following methods on ``Moderator`` can be overridden to achieve customized behavior:: ``connect`` Determines how moderation is set up globally. The base implementation in ``Moderator`` does this by attaching listeners to the ``pre_save`` and ``post_save`` signals from the comment models. ``pre_save_moderation`` 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). ``post_save_moderation`` In the base implementation, applies all post-save moderation steps (currently this consists entirely of deleting comments which were disallowed). Automatically deleting non-public comments ------------------------------------------ In order to save the hassle of manually deleting spam or otherwise-non-public comments on a regular basis, a script is included -- ``bin/delete_spam_comments.py`` -- which is intended to be used as a cron job, and which will automatically delete non-public comments based on criteria you provide to it. **Required arguments** ``-s``, ``--settings`` Specifies the Django settings module to use. **Optional arguments** ``-a`` ``--age`` An age cutoff, in days, beyond which non-public comments will be deleted. for example, when using ``--age=7`` any non-public comment older than 7 days will be deleted. Defaults to 14 if not specified. ``-d``, ``--dry-run`` Prints the number of comments which would have been deleted, but does not actually delete them. ``-t``, ``--type`` The type of comment to delete; ``--type=free`` will delete instance of ``FreeComment``, ``--type=registered`` will delete instances of ``Comment``. Defaults to "free" if not specified. ``-v``, ``--verbose`` If supplied, the script will run verbosely, printing a description of each comment as it is deleted. Regardless of the value of this argument, the script will print the total number of deleted comments once it is finished. So, for example, the script could be used like so:: ``python /path/to/comment_utils/bin/delete_spam_comments.py --settings=mysite.settings --age=7 --type=registered This would delete every non-public ``Comment`` in the ``mysite`` database older than 7 days.