Ticket #9282: comment-utils.r10029.diff
File comment-utils.r10029.diff, 34.7 KB (added by , 16 years ago) |
---|
-
TabularUnified 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..d9e7d98
- + 1 """ 2 Copyright (c) 2007, James Bennett 3 All rights reserved. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are 7 met: 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 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 """ 31 32 """ 33 A generic comment-moderation system which allows configuration of 34 moderation options on a per-model basis. 35 36 To use, do two things: 37 38 1. Create or import a subclass of ``CommentModerator`` defining the 39 options you want. 40 41 2. 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 46 Example 47 ------- 48 49 First, we define a simple model class which might represent entries in 50 a 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 60 Then we create a ``CommentModerator`` subclass specifying some 61 moderation 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 69 And finally register it for moderation:: 70 71 moderator.register(Entry, EntryModerator) 72 73 This sample class would apply several moderation steps to each new 74 comment 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 82 For a full list of built-in moderation options and other 83 configurability, see the documentation for the ``CommentModerator`` 84 class. 85 86 Several example subclasses of ``CommentModerator`` are provided in 87 `django-comment-utils`_, both to provide common moderation options and to 88 demonstrate some of the ways subclasses can customize moderation 89 behavior. 90 91 .. _`django-comment-utils`: http://code.google.com/p/django-comment-utils/ 92 """ 93 94 95 import datetime 96 97 from django.conf import settings 98 from django.core.mail import send_mail 99 from django.db.models import signals 100 from django.db.models.base import ModelBase 101 from django.template import Context, loader 102 from django.contrib import comments 103 from django.contrib.sites.models import Site 104 105 106 class 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 115 class 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 124 class 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_field`` 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 336 class 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. 477 moderator = Moderator() -
TabularUnified docs/ref/contrib/comments/index.txt
diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index b78ac4f..ab5a7ec 100644
a b More information 215 215 signals 216 216 upgrade 217 217 custom 218 moderators -
TabularUnified 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..d66bb32
- + 1 .. _ref-contrib-comments-moderators: 2 3 ========================== 4 Generic comment moderation 5 ========================== 6 7 .. currentmodule:: django.contrib.comments.moderators 8 9 Django's bundled comments application is extremely useful on its own, 10 but the amount of comment spam circulating on the Web today 11 essentially makes it necessary to have some sort of automatic 12 moderation system in place for any application which makes use of 13 comments. To make this easier to handle in a consistent fashion, 14 ``django.contrib.comments.moderators`` (based on `comment_utils`_) 15 provides a generic, extensible comment-moderation system which can 16 be applied to any model or set of models which want to make use of 17 Django's comment system. 18 19 .. _`comment_utils`: http://code.google.com/p/django-comment-utils/ 20 21 22 Overview 23 ======== 24 25 The entire system is contained within ``django.contrib.comments.moderators``, 26 and uses a two-step process to enable moderation for any given model: 27 28 1. A subclass of :class:`django.contrib.comments.moderators.CommentModerator` 29 is defined which specifies the moderation options the model wants to 30 enable. 31 32 2. The model is registered with the moderation system, passing in the 33 model class and the class which specifies its moderation options. 34 35 A simple example is the best illustration of this. Suppose we have the 36 following model, which would represent entries in a weblog:: 37 38 from django.db import models 39 40 class Entry(models.Model): 41 title = models.CharField(maxlength=250) 42 body = models.TextField() 43 pub_date = models.DateTimeField() 44 enable_comments = models.BooleanField() 45 46 Now, suppose that we want the following steps to be applied whenever a 47 new comment is posted on an ``Entry``: 48 49 1. If the ``Entry``'s ``enable_comments`` field is ``False``, the 50 comment will simply be disallowed (i.e., immediately deleted). 51 52 2. If the ``enable_comments`` field is ``True``, the comment will be 53 allowed to save. 54 55 3. Once the comment is saved, an email should be sent to site staff 56 notifying them of the new comment. 57 58 Accomplishing this is fairly straightforward and requires very little 59 code:: 60 61 from django.contrib.comments.moderators import CommentModerator, moderator 62 63 class EntryModerator(CommentModerator): 64 email_notification = True 65 enable_field = 'enable_comments' 66 67 moderator.register(Entry, EntryModerator) 68 69 The :class:`django.contrib.comments.moderators.CommentModerator` class 70 pre-defines a number of useful moderation options which subclasses can enable 71 or disable as desired, and ``moderator`` knows how to work with them to 72 determine whether to allow a comment, whether to moderate a comment which 73 will be allowed to post, and whether to email notifications of new comments. 74 75 76 Built-in moderation options 77 --------------------------- 78 79 .. class:: CommentModerator 80 81 Most common comment-moderation needs can be handled by subclassing 82 :class:`~django.contrib.comments.moderators.CommentModerator` and 83 changing the values of pre-defined attributes; the full range of built-in 84 options is as follows. 85 86 .. attribute:: auto_close_field 87 88 If this is set to the name of a 89 :class:`~django.db.models.fields.DateField` or 90 :class:`~django.db.models.fields.DateTimeField` on the model for which 91 comments are being moderated, new comments for objects of that model 92 will be disallowed (immediately deleted) when a certain number of days 93 have passed after the date specified in that field. Must be 94 used in conjunction with :attr:`close_after`, which specifies the 95 number of days past which comments should be 96 disallowed. Default value is ``None``. 97 98 .. attribute:: auto_moderate_field 99 100 Like :attr:`auto_close_field`, but instead of outright deleting 101 new comments when the requisite number of days have elapsed, 102 it will simply set the ``is_public`` field of new comments to 103 ``False`` before saving them. Must be used in conjunction with 104 :attr:`moderate_after`, which specifies the number of days past 105 which comments should be moderated. Default value is ``None``. 106 107 .. attribute:: close_after 108 109 If :attr:`auto_close_field` is used, this must specify the number 110 of days past the value of the field specified by 111 :attr:`auto_close_field` after which new comments for an object 112 should be disallowed. Default value is ``None``. 113 114 .. attribute:: email_notification 115 116 If ``True``, any new comment on an object of this model which 117 survives moderation (i.e., is not deleted) will generate an 118 email to site staff. Default value is ``False``. 119 120 .. attribute:: enable_field 121 122 If this is set to the name of a 123 :class:`~django.db.models.fields.BooleanField` on the model 124 for which comments are being moderated, new comments on 125 objects of that model will be disallowed (immediately deleted) 126 whenever the value of that field is ``False`` on the object 127 the comment would be attached to. Default value is ``None``. 128 129 .. attribute:: moderate_after 130 131 If :attr:`auto_moderate_field` is used, this must specify the number 132 of days past the value of the field specified by 133 :attr:`auto_moderate_field` after which new comments for an object 134 should be marked non-public. Default value is ``None``. 135 136 Simply subclassing 137 :class:`~django.contrib.comments.moderators.CommentModerator` and changing 138 the values of these options will automatically enable the various moderation 139 methods for any models registered using the subclass. 140 141 Adding custom moderation methods 142 -------------------------------- 143 144 For situations where the built-in options listed above are not 145 sufficient, subclasses of 146 :class:`~django.contrib.comments.moderators.CommentModerator` can also 147 override the methods which actually perform the moderation, and apply any 148 logic they desire. 149 :class:`~django.contrib.comments.moderators.CommentModerator` defines three 150 methods which determine how moderation will take place; each method will be 151 called by the moderation system and passed two arguments: ``comment``, which 152 is the new comment being posted, and ``content_object``, which is the 153 object the comment will be attached to: 154 155 .. method:: CommentModerator.allow(comment, content_object) 156 157 Should return ``True`` if the comment should be allowed to 158 post on the content object, and ``False`` otherwise (in which 159 case the comment will be immediately deleted). 160 161 .. method:: CommentModerator.email(comment, content_object) 162 163 If email notification of the new comment should be sent to 164 site staff or moderators, this method is responsible for 165 sending the email. 166 167 .. method:: CommentModerator.moderate(comment, content_object) 168 169 Should return ``True`` if the comment should be moderated (in 170 which case its ``is_public`` field will be set to ``False`` 171 before saving), and ``False`` otherwise (in which case the 172 ``is_public`` field will not be changed). 173 174 175 Registering models for moderation 176 --------------------------------- 177 178 The moderation system, represented by 179 ``django.contrib.comments.moderators.moderator`` is an instance of the class 180 :class:`django.contrib.comments.moderators.Moderator`, which allows 181 registration and "unregistration" of models via two methods: 182 183 .. function:: moderator.register(model_or_iterable, moderation_class) 184 185 Takes two arguments: the first should be either a model class 186 or list of model classes, and the second should be a subclass 187 of ``CommentModerator``, and register the model or models to 188 be moderated using the options defined in the 189 ``CommentModerator`` subclass. If any of the models are 190 already registered for moderation, the exception 191 ``django.contrib.comments.moderators.AlreadyModerated`` will be raised. 192 193 .. function:: moderator.unregister(model_or_iterable) 194 195 Takes one argument: a model class or list of model classes, 196 and removes the model or models from the set of models which 197 are being moderated. If any of the models are not currently 198 being moderated, the exception 199 :exc:`django.contrib.comments.moderators.NotModerated` will be raised. 200 201 202 Customizing the moderation system 203 --------------------------------- 204 205 Most use cases will work easily with simple subclassing of 206 :class:`~django.contrib.comments.moderators.CommentModerator` and registration 207 with the provided :class:`~django.contrib.comments.moderators.moderator` 208 instance, but customization of global moderation behavior can be 209 achieved by subclassing :class:`~django.contrib.comments.moderators.Moderator` 210 and instead registering models with an instance of the subclass. 211 212 213 .. class:: Moderator 214 215 In addition to the 216 :func:`~django.contrib.comments.moderators.moderator.register` and 217 :func:`~django.contrib.comments.moderators.moderator.unregister` methods 218 detailed above, the following methods on 219 :class:`~django.contrib.comments.moderators.Moderator` can be overridden 220 to achieve customized behavior: 221 222 .. method:: connect 223 224 Determines how moderation is set up globally. The base 225 implementation in 226 :class:`~django.contrib.comments.moderators.Moderator` does this by 227 attaching listeners to the :data:`~django.db.models.signals.pre_save` 228 and :data:`~django.db.models.signals.post_save` signals from the 229 comment models. 230 231 .. method:: pre_save_moderation(sender, instance, **kwargs) 232 233 In the base implementation, applies all pre-save moderation 234 steps (such as determining whether the comment needs to be 235 deleted, or whether it needs to be marked as non-public or 236 generate an email). 237 238 .. method:: post_save_moderation(sender, instance, **kwargs) 239 240 In the base implementation, applies all post-save moderation 241 steps (currently this consists entirely of deleting comments 242 which were disallowed). -
TabularUnified 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> -
TabularUnified 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): 20 20 def __str__(self): 21 21 return self.headline 22 22 23 class 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 -
TabularUnified 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 * 86 86 from regressiontests.comment_tests.tests.templatetag_tests import * 87 87 from regressiontests.comment_tests.tests.comment_view_tests import * 88 88 from regressiontests.comment_tests.tests.moderation_view_tests import * 89 from regressiontests.comment_tests.tests.comment_utils_moderators_tests import * -
TabularUnified 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..1052636
- + 1 from regressiontests.comment_tests.tests import CommentTestCase, CT, Site 2 from django.contrib.comments.models import Comment 3 from django.contrib.comments.moderators import moderator, CommentModerator, AlreadyModerated 4 from regressiontests.comment_tests.models import Entry 5 from django.core import mail 6 7 class EntryModerator1(CommentModerator): 8 email_notification = True 9 10 class EntryModerator2(CommentModerator): 11 enable_field = 'enable_comments' 12 13 class EntryModerator3(CommentModerator): 14 auto_close_field = 'pub_date' 15 close_after = 7 16 17 class EntryModerator4(CommentModerator): 18 auto_moderate_field = 'pub_date' 19 moderate_after = 7 20 21 class 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) -
TabularUnified tests/runtests.py
diff --git a/tests/runtests.py b/tests/runtests.py index bd7f59b..f556246 100755
a b def django_tests(verbosity, interactive, test_labels): 110 110 'django.middleware.common.CommonMiddleware', 111 111 ) 112 112 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",) 113 117 114 118 # Load all the ALWAYS_INSTALLED_APPS. 115 119 # (This import statement is intentionally delayed until after we -
TabularUnified 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
- + 1 A comment has been posted on {{ content_object }}. 2 The comment reads as follows: 3 {{ comment }}