Author: jacob
Date: 2009-02-23 16:16:26 -0600 (Mon, 23 Feb 2009)
New Revision: 9890

Added:
   django/trunk/docs/ref/contrib/comments/custom.txt
   django/trunk/tests/regressiontests/comment_tests/custom_comments/
   django/trunk/tests/regressiontests/comment_tests/custom_comments/__init__.py
   django/trunk/tests/regressiontests/comment_tests/custom_comments/forms.py
   django/trunk/tests/regressiontests/comment_tests/custom_comments/models.py
   django/trunk/tests/regressiontests/comment_tests/custom_comments/views.py
   django/trunk/tests/regressiontests/comment_tests/urls.py
Modified:
   django/trunk/AUTHORS
   django/trunk/django/contrib/comments/__init__.py
   django/trunk/django/contrib/comments/admin.py
   django/trunk/docs/ref/contrib/comments/index.txt
   django/trunk/docs/ref/contrib/comments/settings.txt
   django/trunk/tests/regressiontests/comment_tests/tests/app_api_tests.py
Log:
Fixed #8630: finished the custom comment app API that was left out of 1.0. This 
means it's now possible to override any of the models, forms, or views used by 
the comment app; see the new custom comment app docs for details and an 
example. Thanks to Thejaswi Puthraya for the original patch, and to carljm for 
docs and tests.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2009-02-23 22:16:00 UTC (rev 9889)
+++ django/trunk/AUTHORS        2009-02-23 22:16:26 UTC (rev 9890)
@@ -77,6 +77,7 @@
     Trevor Caira <tre...@caira.com>
     Ricardo Javier Cárdenes Medina <ricardo.carde...@gmail.com>
     Jeremy Carbaugh <jcarba...@gmail.com>
+    carljm <c...@dirtcircle.com>
     Graham Carlyle <graham.carl...@maplecroft.net>
     Antonio Cavedoni <http://cavedoni.com/>
     C8E

Modified: django/trunk/django/contrib/comments/__init__.py
===================================================================
--- django/trunk/django/contrib/comments/__init__.py    2009-02-23 22:16:00 UTC 
(rev 9889)
+++ django/trunk/django/contrib/comments/__init__.py    2009-02-23 22:16:26 UTC 
(rev 9890)
@@ -1,9 +1,10 @@
 from django.conf import settings
 from django.core import urlresolvers
 from django.core.exceptions import ImproperlyConfigured
+from django.contrib.comments.models import Comment
+from django.contrib.comments.forms import CommentForm
 
-# Attributes required in the top-level app for COMMENTS_APP
-REQUIRED_COMMENTS_APP_ATTRIBUTES = ["get_model", "get_form", "get_form_target"]
+DEFAULT_COMMENTS_APP = 'django.contrib.comments'
 
 def get_comment_app():
     """
@@ -22,13 +23,6 @@
         raise ImproperlyConfigured("The COMMENTS_APP setting refers to "\
                                    "a non-existing package.")
 
-    # Make sure some specific attributes exist inside that package.
-    for attribute in REQUIRED_COMMENTS_APP_ATTRIBUTES:
-        if not hasattr(package, attribute):
-            raise ImproperlyConfigured("The COMMENTS_APP package %r does not "\
-                                       "define the (required) %r function" % \
-                                            (package, attribute))
-
     return package
 
 def get_comment_app_name():
@@ -36,42 +30,61 @@
     Returns the name of the comment app (either the setting value, if it
     exists, or the default).
     """
-    return getattr(settings, 'COMMENTS_APP', 'django.contrib.comments')
+    return getattr(settings, 'COMMENTS_APP', DEFAULT_COMMENTS_APP)
 
 def get_model():
-    from django.contrib.comments.models import Comment
-    return Comment
+    """
+    Returns the comment model class.
+    """
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_model"):
+        return get_comment_app().get_model()
+    else:
+        return Comment
 
 def get_form():
-    from django.contrib.comments.forms import CommentForm
-    return CommentForm
+    """
+    Returns the comment ModelForm class.
+    """
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_form"):
+        return get_comment_app().get_form()
+    else:
+        return CommentForm
 
 def get_form_target():
-    return 
urlresolvers.reverse("django.contrib.comments.views.comments.post_comment")
+    """
+    Returns the target URL for the comment form submission view.
+    """
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_form_target"):
+        return get_comment_app().get_form_target()
+    else:
+        return 
urlresolvers.reverse("django.contrib.comments.views.comments.post_comment")
 
 def get_flag_url(comment):
     """
     Get the URL for the "flag this comment" view.
     """
-    if get_comment_app_name() != __name__ and hasattr(get_comment_app(), 
"get_flag_url"):
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_flag_url"):
         return get_comment_app().get_flag_url(comment)
     else:
-        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.flag", 
args=(comment.id,))
+        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.flag",
+                                    args=(comment.id,))
 
 def get_delete_url(comment):
     """
     Get the URL for the "delete this comment" view.
     """
-    if get_comment_app_name() != __name__ and hasattr(get_comment_app(), 
"get_delete_url"):
-        return get_comment_app().get_flag_url(get_delete_url)
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_delete_url"):
+        return get_comment_app().get_delete_url(comment)
     else:
-        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.delete", 
args=(comment.id,))
+        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.delete",
+                                    args=(comment.id,))
 
 def get_approve_url(comment):
     """
     Get the URL for the "approve this comment from moderation" view.
     """
-    if get_comment_app_name() != __name__ and hasattr(get_comment_app(), 
"get_approve_url"):
+    if get_comment_app_name() != DEFAULT_COMMENTS_APP and 
hasattr(get_comment_app(), "get_approve_url"):
         return get_comment_app().get_approve_url(comment)
     else:
-        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.approve", 
args=(comment.id,))
+        return 
urlresolvers.reverse("django.contrib.comments.views.moderation.approve",
+                                    args=(comment.id,))

Modified: django/trunk/django/contrib/comments/admin.py
===================================================================
--- django/trunk/django/contrib/comments/admin.py       2009-02-23 22:16:00 UTC 
(rev 9889)
+++ django/trunk/django/contrib/comments/admin.py       2009-02-23 22:16:26 UTC 
(rev 9890)
@@ -1,6 +1,7 @@
 from django.contrib import admin
 from django.contrib.comments.models import Comment
 from django.utils.translation import ugettext_lazy as _
+from django.contrib.comments import get_model
 
 class CommentsAdmin(admin.ModelAdmin):
     fieldsets = (
@@ -21,4 +22,7 @@
     ordering = ('-submit_date',)
     search_fields = ('comment', 'user__username', 'user_name', 'user_email', 
'user_url', 'ip_address')
 
-admin.site.register(Comment, CommentsAdmin)
+# Only register the default admin if the model is the built-in comment model
+# (this won't be true if there's a custom comment app).
+if get_model() is Comment:
+    admin.site.register(Comment, CommentsAdmin)

Added: django/trunk/docs/ref/contrib/comments/custom.txt
===================================================================
--- django/trunk/docs/ref/contrib/comments/custom.txt                           
(rev 0)
+++ django/trunk/docs/ref/contrib/comments/custom.txt   2009-02-23 22:16:26 UTC 
(rev 9890)
@@ -0,0 +1,180 @@
+.. _ref-contrib-comments-custom:
+
+==================================
+Customizing the comments framework
+==================================
+
+.. currentmodule:: django.contrib.comments
+
+If the built-in comment framework doesn't quite fit your needs, you can extend
+the comment app's behavior to add custom data and logic. The comments framework
+lets you extend the built-in comment model, the built-in comment form, and the
+various comment views.
+
+The :setting:`COMMENTS_APP` setting is where this customization begins. Set
+:setting:`COMMENTS_APP` to the name of the app you'd like to use to provide
+custom behavior. You'll use the same syntax as you'd use for
+:setting:`INSTALLED_APPS`, and the app given must also be in the
+:setting:`INSTALLED_APPS` list.
+
+For example, if you wanted to use an app named ``my_comment_app``, your
+settings file would contain::
+
+    INSTALLED_APPS = [
+        ...
+        'my_comment_app',
+        ...
+    ]
+    
+    COMMENTS_APP = 'my_comment_app'
+
+The app named in :setting:`COMMENTS_APP` provides its custom behavior by
+defining some module-level functions in the app's ``__init__.py``. The
+:ref:`complete list of these functions <custom-comment-app-api>` can be found
+below, but first let's look at a quick example.
+
+An example custom comments app
+==============================
+
+One of the most common types of customization is modifying the set of fields
+provided on the built-in comment model. For example, some sites that allow
+comments want the commentator to provide a title for their comment; the 
built-in
+comment model has no field for that title.
+
+To make this kind of customization, we'll need to do three things:
+
+    #. Create a custom comment :class:`~django.db.models.Model` that adds on 
the
+       "title" field.
+    
+    #. Create a custom comment :class:`~django.forms.Form` that also adds this
+       "title" field.
+    
+    #. Inform Django of these objects by defining a few functions in a
+       custom :setting:`COMMENTS_APP`.
+       
+So, carrying on the example above, we're dealing with a typical app structure 
in
+the ``my_custom_app`` directory::
+
+    my_custom_app/
+        __init__.py
+        models.py
+        forms.py
+
+In the ``models.py`` we'll define a ``CommentWithTitle`` model::
+
+    from django.db import models
+    from django.contrib.comments.models import BaseCommentAbstractModel
+
+    class CommentWithTitle(BaseCommentAbstractModel):
+        title = models.CharField(max_length=300)
+    
+All custom comment models must subclass :class:`BaseCommentAbstractModel`.
+
+Next, we'll define a custom comment form in ``forms.py``. This is a little more
+tricky: we have to both create a form and override
+:meth:`CommentForm.get_comment_model` and
+:meth:`CommentForm.get_comment_create_data` to return deal with our custom 
title
+field::
+
+    from django import forms
+    from django.contrib.comments.forms import CommentForm
+    from my_comment_app.models import CommentWithTitle
+
+    class CommentFormWithTitle(CommentForm):
+        title = forms.CharField(max_length=300)
+        
+        def get_comment_model(self):
+            # Use our custom comment model instead of the built-in one.
+            return CommentWithTitle
+        
+        def get_comment_create_data(self):
+            # Use the data of the superclass, and add in the title field
+            data = super(CommentFormWithTitle, self).get_comment_create_data()
+            data['title'] = self.cleaned_data['title']
+            return data
+
+Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to 
point Django at these classes we've created::
+
+    from my_comments_app.models import CommentWithTitle
+    from my_comments_app.forms import CommentFormWithTitle
+
+    def get_model():
+        return CommentWithTitle
+
+    def get_form():
+        return CommentFormWithTitle
+
+The above process should take care of most common situations. For more 
advanced usage, there are additional methods you can define. Those are 
explained in the next section.
+
+.. _custom-comment-app-api:
+
+Custom comment app API
+======================
+
+The :mod:`django.contrib.comments` app defines the following methods; any 
custom comment app must define at least one of them. All are optional, however.
+
+.. function:: get_model()
+
+    Return the :class:`~django.db.models.Model` class to use for comments. This
+    model should inherit from
+    :class:`django.contrib.comments.models.BaseCommentAbstractModel`, which
+    defines necessary core fields.
+
+    The default implementation returns
+    :class:`django.contrib.comments.models.Comment`.
+
+.. function:: get_form()
+
+    Return the :class:`~django.forms.Form` class you want to use for
+    creating, validating, and saving your comment model.  Your custom
+    comment form should accept an additional first argument,
+    ``target_object``, which is the object the comment will be
+    attached to.
+
+    The default implementation returns
+    :class:`django.contrib.comments.forms.CommentForm`.
+
+    .. note::
+
+        The default comment form also includes a number of unobtrusive
+        spam-prevention features (see
+        :ref:`notes-on-the-comment-form`).  If replacing it with your
+        own form, you may want to look at the source code for the
+        built-in form and consider incorporating similar features.
+
+.. function:: get_form_target()
+
+    Return the URL for POSTing comments. This will be the ``<form action>``
+    attribute when rendering your comment form.
+
+    The default implementation returns a reverse-resolved URL pointing
+    to the :func:`post_comment` view.
+
+    .. note::
+
+        If you provide a custom comment model and/or form, but you
+        want to use the default :func:`post_comment` view, you will
+        need to be aware that it requires the model and form to have
+        certain additional attributes and methods: see the
+        :func:`post_comment` view documentation for details.
+
+.. function:: get_flag_url()
+
+    Return the URL for the "flag this comment" view.
+
+    The default implementation returns a reverse-resolved URL pointing
+    to the :func:`django.contrib.comments.views.moderation.flag` view.
+
+.. function:: get_delete_url()
+
+    Return the URL for the "delete this comment" view.
+
+    The default implementation returns a reverse-resolved URL pointing
+    to the :func:`django.contrib.comments.views.moderation.delete` view.
+
+.. function:: get_approve_url()
+
+    Return the URL for the "approve this comment from moderation" view.
+
+    The default implementation returns a reverse-resolved URL pointing
+    to the :func:`django.contrib.comments.views.moderation.approve` view.
\ No newline at end of file

Modified: django/trunk/docs/ref/contrib/comments/index.txt
===================================================================
--- django/trunk/docs/ref/contrib/comments/index.txt    2009-02-23 22:16:00 UTC 
(rev 9889)
+++ django/trunk/docs/ref/contrib/comments/index.txt    2009-02-23 22:16:26 UTC 
(rev 9890)
@@ -42,7 +42,7 @@
     #. Use the `comment template tags`_ below to embed comments in your
        templates.
     
-You might also want to examine the :ref:`ref-contrib-comments-settings`
+You might also want to examine :ref:`ref-contrib-comments-settings`.
     
 Comment template tags
 =====================
@@ -161,7 +161,7 @@
     </form>
     
 Be sure to read the `notes on the comment form`_, below, for some special
-considerations you'll need to make if you're using this aproach.
+considerations you'll need to make if you're using this approach.
 
 .. templatetag:: comment_form_target
 
@@ -175,6 +175,8 @@
 
     <form action="{% comment_form_target %}" method="POST">
 
+.. _notes-on-the-comment-form:
+
 Notes on the comment form
 -------------------------
 
@@ -212,4 +214,4 @@
    settings
    signals
    upgrade
-
+   custom

Modified: django/trunk/docs/ref/contrib/comments/settings.txt
===================================================================
--- django/trunk/docs/ref/contrib/comments/settings.txt 2009-02-23 22:16:00 UTC 
(rev 9889)
+++ django/trunk/docs/ref/contrib/comments/settings.txt 2009-02-23 22:16:26 UTC 
(rev 9890)
@@ -29,6 +29,7 @@
 COMMENTS_APP
 ------------
 
-The app (i.e. entry in ``INSTALLED_APPS``) responsible for all "business 
logic."
-You can change this to provide custom comment models and forms, though this is
-currently undocumented.
+An app which provides :ref:`customization of the comments framework
+<ref-contrib-comments-custom>`.  Use the same dotted-string notation
+as in :setting:`INSTALLED_APPS`.  Your custom :setting:`COMMENTS_APP`
+must also be listed in :setting:`INSTALLED_APPS`.

Added: 
django/trunk/tests/regressiontests/comment_tests/custom_comments/__init__.py
===================================================================
--- 
django/trunk/tests/regressiontests/comment_tests/custom_comments/__init__.py    
                            (rev 0)
+++ 
django/trunk/tests/regressiontests/comment_tests/custom_comments/__init__.py    
    2009-02-23 22:16:26 UTC (rev 9890)
@@ -0,0 +1,32 @@
+from django.core import urlresolvers
+from regressiontests.comment_tests.custom_comments.models import CustomComment 
+from regressiontests.comment_tests.custom_comments.forms import 
CustomCommentForm
+
+def get_model():
+    return CustomComment 
+
+def get_form():
+    return CustomCommentForm
+
+def get_form_target():
+    return urlresolvers.reverse(
+        
"regressiontests.comment_tests.custom_comments.views.custom_submit_comment"
+    )
+
+def get_flag_url(c):
+    return urlresolvers.reverse(
+        
"regressiontests.comment_tests.custom_comments.views.custom_flag_comment", 
+        args=(c.id,)
+    )
+
+def get_delete_url(c):
+    return urlresolvers.reverse(
+        
"regressiontests.comment_tests.custom_comments.views.custom_delete_comment", 
+        args=(c.id,)
+    )
+
+def get_approve_url(c):
+    return urlresolvers.reverse(
+        
"regressiontests.comment_tests.custom_comments.views.custom_approve_comment", 
+        args=(c.id,)
+    )

Added: django/trunk/tests/regressiontests/comment_tests/custom_comments/forms.py
===================================================================
--- django/trunk/tests/regressiontests/comment_tests/custom_comments/forms.py   
                        (rev 0)
+++ django/trunk/tests/regressiontests/comment_tests/custom_comments/forms.py   
2009-02-23 22:16:26 UTC (rev 9890)
@@ -0,0 +1,4 @@
+from django import forms
+
+class CustomCommentForm(forms.Form):
+    pass

Added: 
django/trunk/tests/regressiontests/comment_tests/custom_comments/models.py
===================================================================
--- django/trunk/tests/regressiontests/comment_tests/custom_comments/models.py  
                        (rev 0)
+++ django/trunk/tests/regressiontests/comment_tests/custom_comments/models.py  
2009-02-23 22:16:26 UTC (rev 9890)
@@ -0,0 +1,4 @@
+from django.db import models
+
+class CustomComment(models.Model):
+    pass

Added: django/trunk/tests/regressiontests/comment_tests/custom_comments/views.py
===================================================================
--- django/trunk/tests/regressiontests/comment_tests/custom_comments/views.py   
                        (rev 0)
+++ django/trunk/tests/regressiontests/comment_tests/custom_comments/views.py   
2009-02-23 22:16:26 UTC (rev 9890)
@@ -0,0 +1,13 @@
+from django.http import HttpResponse
+
+def custom_submit_comment(request):
+    return HttpResponse("Hello from the custom submit comment view.")
+
+def custom_flag_comment(request, comment_id):
+    return HttpResponse("Hello from the custom flag view.")
+
+def custom_delete_comment(request, comment_id):
+    return HttpResponse("Hello from the custom delete view.")
+
+def custom_approve_comment(request, comment_id):
+    return HttpResponse("Hello from the custom approve view.")

Modified: 
django/trunk/tests/regressiontests/comment_tests/tests/app_api_tests.py
===================================================================
--- django/trunk/tests/regressiontests/comment_tests/tests/app_api_tests.py     
2009-02-23 22:16:00 UTC (rev 9889)
+++ django/trunk/tests/regressiontests/comment_tests/tests/app_api_tests.py     
2009-02-23 22:16:26 UTC (rev 9890)
@@ -28,3 +28,44 @@
         c = Comment(id=12345)
         self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
 
+
+class CustomCommentTest(CommentTestCase):
+    urls = 'regressiontests.comment_tests.urls'
+
+    def setUp(self):
+        self.old_comments_app = getattr(settings, 'COMMENTS_APP', None)
+        settings.COMMENTS_APP = 'regressiontests.comment_tests.custom_comments'
+        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + 
[settings.COMMENTS_APP,]
+
+    def tearDown(self):
+        del settings.INSTALLED_APPS[-1]
+        settings.COMMENTS_APP = self.old_comments_app
+        if settings.COMMENTS_APP is None:
+            delattr(settings._target, 'COMMENTS_APP')
+
+    def testGetCommentApp(self):
+        from regressiontests.comment_tests import custom_comments
+        self.assertEqual(comments.get_comment_app(), custom_comments)
+
+    def testGetModel(self):
+        from regressiontests.comment_tests.custom_comments.models import 
CustomComment
+        self.assertEqual(comments.get_model(), CustomComment)
+
+    def testGetForm(self):
+        from regressiontests.comment_tests.custom_comments.forms import 
CustomCommentForm
+        self.assertEqual(comments.get_form(), CustomCommentForm)
+
+    def testGetFormTarget(self):
+        self.assertEqual(comments.get_form_target(), "/post/")
+
+    def testGetFlagURL(self):
+        c = Comment(id=12345)
+        self.assertEqual(comments.get_flag_url(c), "/flag/12345/")
+
+    def getGetDeleteURL(self):
+        c = Comment(id=12345)
+        self.assertEqual(comments.get_delete_url(c), "/delete/12345/")
+
+    def getGetApproveURL(self):
+        c = Comment(id=12345)
+        self.assertEqual(comments.get_approve_url(c), "/approve/12345/")

Added: django/trunk/tests/regressiontests/comment_tests/urls.py
===================================================================
--- django/trunk/tests/regressiontests/comment_tests/urls.py                    
        (rev 0)
+++ django/trunk/tests/regressiontests/comment_tests/urls.py    2009-02-23 
22:16:26 UTC (rev 9890)
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('regressiontests.comment_tests.custom_comments.views',
+    url(r'^post/$',          'custom_submit_comment'),
+    url(r'^flag/(\d+)/$',    'custom_flag_comment'),
+    url(r'^delete/(\d+)/$',  'custom_delete_comment'),
+    url(r'^approve/(\d+)/$', 'custom_approve_comment'),
+)
+


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to