Ticket #11113: comment-moderation-signals.diff
File comment-moderation-signals.diff, 15.8 KB (added by , 16 years ago) |
---|
-
django/contrib/comments/moderation.py
2 2 A generic comment-moderation system which allows configuration of 3 3 moderation options on a per-model basis. 4 4 5 Originally part of django-comment-utils, by James Bennett.6 7 5 To use, do two things: 8 6 9 7 1. Create or import a subclass of ``CommentModerator`` defining the … … 41 39 42 40 moderator.register(Entry, EntryModerator) 43 41 44 This sample class would apply severalmoderation steps to each new42 This sample class would apply two moderation steps to each new 45 43 comment submitted on an Entry: 46 44 47 45 * If the entry's ``enable_comments`` field is set to ``False``, the … … 54 52 configurability, see the documentation for the ``CommentModerator`` 55 53 class. 56 54 57 Several example subclasses of ``CommentModerator`` are provided in58 `django-comment-utils`_, both to provide common moderation options and to59 demonstrate some of the ways subclasses can customize moderation60 behavior.61 62 .. _`django-comment-utils`: http://code.google.com/p/django-comment-utils/63 55 """ 64 56 65 57 import datetime 66 58 67 59 from django.conf import settings 68 60 from django.core.mail import send_mail 69 from django. db.models import signals61 from django.contrib.comments import signals 70 62 from django.db.models.base import ModelBase 71 63 from django.template import Context, loader 72 64 from django.contrib import comments … … 145 137 Most common moderation needs can be covered by changing these 146 138 attributes, but further customization can be obtained by 147 139 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:: 151 144 152 145 ``allow`` 153 146 Should return ``True`` if the comment should be allowed to … … 200 193 raise ValueError("Cannot determine moderation rules because date field is set to a value in the future") 201 194 return now - then 202 195 203 def allow(self, comment, content_object ):196 def allow(self, comment, content_object, request): 204 197 """ 205 198 Determine whether a given comment is allowed to be posted on 206 199 a given object. … … 217 210 return False 218 211 return True 219 212 220 def moderate(self, comment, content_object ):213 def moderate(self, comment, content_object, request): 221 214 """ 222 215 Determine whether a given comment on a given object should be 223 216 allowed to show up immediately, or should be marked non-public … … 232 225 return True 233 226 return False 234 227 235 def comments_open(self, obj):228 def email(self, comment, content_object, request): 236 229 """ 237 Return ``True`` if new comments are being accepted for238 ``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 on243 ``obj`` contains a false value, comments are not open.244 245 2. If ``close_after`` is set and the relevant date field on246 ``obj`` is far enough in the past, comments are not open.247 248 3. If neither of the above checks determined that comments are249 not open, comments are open.250 251 """252 if self.enable_field:253 if not getattr(obj, self.enable_field):254 return False255 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 False258 return True259 260 def comments_moderated(self, obj):261 """262 Return ``True`` if new comments for ``obj`` are being263 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 on268 ``obj`` contains a true value, comments are moderated.269 270 2. If ``moderate_after`` is set and the relevant date field on271 ``obj`` is far enough in the past, comments are moderated.272 273 3. If neither of the above checks decided that comments are274 moderated, comments are not moderated.275 276 """277 if self.moderate_field:278 if getattr(obj, self.moderate_field):279 return True280 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 True283 return False284 285 def email(self, comment, content_object):286 """287 230 Send email notification of a new comment to site staff when email 288 231 notifications have been requested. 289 232 … … 341 284 from the comment models. 342 285 343 286 """ 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()) 346 289 347 290 def register(self, model_or_iterable, moderation_class): 348 291 """ … … 376 319 raise NotModerated("The model '%s' is not currently being moderated" % model._meta.module_name) 377 320 del self._registry[model] 378 321 379 def pre_save_moderation(self, sender, instance, **kwargs):322 def pre_save_moderation(self, sender, comment, request, **kwargs): 380 323 """ 381 324 Apply any necessary pre-save moderation steps to new 382 325 comments. 383 326 384 327 """ 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: 387 330 return 388 content_object = instance.content_object331 content_object = comment.content_object 389 332 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 395 337 396 def post_save_moderation(self, sender, instance, **kwargs):338 def post_save_moderation(self, sender, comment, request, **kwargs): 397 339 """ 398 340 Apply any necessary post-save moderation steps to new 399 341 comments. 400 342 401 343 """ 402 model = instance.content_type.model_class()344 model = comment.content_type.model_class() 403 345 if model not in self._registry: 404 346 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) 409 348 410 def comments_open(self, obj):411 """412 Return ``True`` if new comments are being accepted for413 ``obj``, ``False`` otherwise.414 349 415 If no moderation rules have been registered for the model of416 which ``obj`` is an instance, comments are assumed to be open417 for that object.418 419 """420 model = obj.__class__421 if model not in self._registry:422 return True423 return self._registry[model].comments_open(obj)424 425 def comments_moderated(self, obj):426 """427 Return ``True`` if new comments for ``obj`` are being428 automatically sent to moderation, ``False`` otherwise.429 430 If no moderation rules have been registered for the model of431 which ``obj`` is an instance, comments for that object are432 assumed not to be moderated.433 434 """435 model = obj.__class__436 if model not in self._registry:437 return False438 return self._registry[model].comments_moderated(obj)439 440 350 # Import this instance in your own code to use in registering 441 351 # your models for moderation. 442 352 moderator = Moderator() -
tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py
1 1 from regressiontests.comment_tests.tests import CommentTestCase, CT, Site 2 from django.contrib.comments.forms import CommentForm 2 3 from django.contrib.comments.models import Comment 3 4 from django.contrib.comments.moderation import moderator, CommentModerator, AlreadyModerated 4 5 from regressiontests.comment_tests.models import Entry … … 22 23 fixtures = ["comment_utils.xml"] 23 24 24 25 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 43 46 return c1, c2 44 47 45 48 def tearDown(self): … … 51 54 52 55 def testEmailNotification(self): 53 56 moderator.register(Entry, EntryModerator1) 54 c1, c2 =self.createSomeComments()57 self.createSomeComments() 55 58 self.assertEquals(len(mail.outbox), 2) 56 59 57 60 def testCommentsEnabled(self): 58 61 moderator.register(Entry, EntryModerator2) 59 c1, c2 =self.createSomeComments()62 self.createSomeComments() 60 63 self.assertEquals(Comment.objects.all().count(), 1) 61 64 62 65 def testAutoCloseField(self): 63 66 moderator.register(Entry, EntryModerator3) 64 c1, c2 =self.createSomeComments()67 self.createSomeComments() 65 68 self.assertEquals(Comment.objects.all().count(), 0) 66 69 67 70 def testAutoModerateField(self): -
docs/ref/contrib/comments/moderation.txt
12 12 essentially makes it necessary to have some sort of automatic 13 13 moderation system in place for any application which makes use of 14 14 comments. 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 16 comment-moderation system which can be applied to any model or set of 17 models which want to make use of Django's comment system. 19 18 20 .. _`comment_utils`: http://code.google.com/p/django-comment-utils/21 19 22 20 Overview 23 21 ======== … … 140 138 -------------------------------- 141 139 142 140 For 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: 141 sufficient, subclasses of :class:`CommentModerator` can also override 142 the methods which actually perform the moderation, and apply any logic 143 they desire. :class:`CommentModerator` defines three methods which 144 determine how moderation will take place; each method will be called 145 by the moderation system and passed two arguments: ``comment``, which 146 is the new comment being posted, ``content_object``, which is the 147 object the comment will be attached to, and ``request``, which is the 148 ``HttpRequest`` in which the comment is being submitted: 152 149 153 .. method:: CommentModerator.allow(comment, content_object )150 .. method:: CommentModerator.allow(comment, content_object, request) 154 151 155 152 Should return ``True`` if the comment should be allowed to 156 153 post on the content object, and ``False`` otherwise (in which 157 154 case the comment will be immediately deleted). 158 155 159 .. method:: CommentModerator.email(comment, content_object )156 .. method:: CommentModerator.email(comment, content_object, request) 160 157 161 158 If email notification of the new comment should be sent to 162 159 site staff or moderators, this method is responsible for 163 160 sending the email. 164 161 165 .. method:: CommentModerator.moderate(comment, content_object )162 .. method:: CommentModerator.moderate(comment, content_object, request) 166 163 167 164 Should return ``True`` if the comment should be moderated (in 168 165 which case its ``is_public`` field will be set to ``False`` … … 217 214 Determines how moderation is set up globally. The base 218 215 implementation in 219 216 :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 the217 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 222 219 comment models. 223 220 224 .. method:: pre_save_moderation(sender, instance, **kwargs)221 .. method:: pre_save_moderation(sender, comment, request, **kwargs) 225 222 226 223 In the base implementation, applies all pre-save moderation 227 224 steps (such as determining whether the comment needs to be 228 225 deleted, or whether it needs to be marked as non-public or 229 226 generate an email). 230 227 231 .. method:: post_save_moderation(sender, instance, **kwargs)228 .. method:: post_save_moderation(sender, comment, request, **kwargs) 232 229 233 230 In the base implementation, applies all post-save moderation 234 231 steps (currently this consists entirely of deleting comments