Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #9958: split the `CommentForm` into a set of smaller forms. Thi…

…s for better encapsulation, but also so that it's easier for subclasses to get at the pieces they might need. Thanks to Thejaswi Puthraya.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10110 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 231a7e04194ad886b615122583ae7444d6a35443 1 parent e923b54
Jacob Kaplan-Moss authored
154  django/contrib/comments/forms.py
@@ -13,15 +13,10 @@
13 13
 
14 14
 COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000)
15 15
 
16  
-class CommentForm(forms.Form):
17  
-    name          = forms.CharField(label=_("Name"), max_length=50)
18  
-    email         = forms.EmailField(label=_("Email address"))
19  
-    url           = forms.URLField(label=_("URL"), required=False)
20  
-    comment       = forms.CharField(label=_('Comment'), widget=forms.Textarea,
21  
-                                    max_length=COMMENT_MAX_LENGTH)
22  
-    honeypot      = forms.CharField(required=False,
23  
-                                    label=_('If you enter anything in this field '\
24  
-                                            'your comment will be treated as spam'))
  16
+class CommentSecurityForm(forms.Form):
  17
+    """
  18
+    Handles the security aspects (anti-spoofing) for comment forms.
  19
+    """
25 20
     content_type  = forms.CharField(widget=forms.HiddenInput)
26 21
     object_pk     = forms.CharField(widget=forms.HiddenInput)
27 22
     timestamp     = forms.IntegerField(widget=forms.HiddenInput)
@@ -32,8 +27,75 @@ def __init__(self, target_object, data=None, initial=None):
32 27
         if initial is None:
33 28
             initial = {}
34 29
         initial.update(self.generate_security_data())
35  
-        super(CommentForm, self).__init__(data=data, initial=initial)
  30
+        super(CommentSecurityForm, self).__init__(data=data, initial=initial)
36 31
         
  32
+    def security_errors(self):
  33
+        """Return just those errors associated with security"""
  34
+        errors = ErrorDict()
  35
+        for f in ["honeypot", "timestamp", "security_hash"]:
  36
+            if f in self.errors:
  37
+                errors[f] = self.errors[f]
  38
+        return errors
  39
+
  40
+    def clean_security_hash(self):
  41
+        """Check the security hash."""
  42
+        security_hash_dict = {
  43
+            'content_type' : self.data.get("content_type", ""),
  44
+            'object_pk' : self.data.get("object_pk", ""),
  45
+            'timestamp' : self.data.get("timestamp", ""),
  46
+        }
  47
+        expected_hash = self.generate_security_hash(**security_hash_dict)
  48
+        actual_hash = self.cleaned_data["security_hash"]
  49
+        if expected_hash != actual_hash:
  50
+            raise forms.ValidationError("Security hash check failed.")
  51
+        return actual_hash
  52
+
  53
+    def clean_timestamp(self):
  54
+        """Make sure the timestamp isn't too far (> 2 hours) in the past."""
  55
+        ts = self.cleaned_data["timestamp"]
  56
+        if time.time() - ts > (2 * 60 * 60):
  57
+            raise forms.ValidationError("Timestamp check failed")
  58
+        return ts
  59
+
  60
+    def generate_security_data(self):
  61
+        """Generate a dict of security data for "initial" data."""
  62
+        timestamp = int(time.time())
  63
+        security_dict =   {
  64
+            'content_type'  : str(self.target_object._meta),
  65
+            'object_pk'     : str(self.target_object._get_pk_val()),
  66
+            'timestamp'     : str(timestamp),
  67
+            'security_hash' : self.initial_security_hash(timestamp),
  68
+        }
  69
+        return security_dict
  70
+
  71
+    def initial_security_hash(self, timestamp):
  72
+        """
  73
+        Generate the initial security hash from self.content_object
  74
+        and a (unix) timestamp.
  75
+        """
  76
+
  77
+        initial_security_dict = {
  78
+            'content_type' : str(self.target_object._meta),
  79
+            'object_pk' : str(self.target_object._get_pk_val()),
  80
+            'timestamp' : str(timestamp),
  81
+          }
  82
+        return self.generate_security_hash(**initial_security_dict)
  83
+
  84
+    def generate_security_hash(self, content_type, object_pk, timestamp):
  85
+        """Generate a (SHA1) security hash from the provided info."""
  86
+        info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
  87
+        return sha_constructor("".join(info)).hexdigest()
  88
+
  89
+class CommentDetailsForm(CommentSecurityForm):
  90
+    """
  91
+    Handles the specific details of the comment (name, comment, etc.).
  92
+    """
  93
+    name          = forms.CharField(label=_("Name"), max_length=50)
  94
+    email         = forms.EmailField(label=_("Email address"))
  95
+    url           = forms.URLField(label=_("URL"), required=False)
  96
+    comment       = forms.CharField(label=_('Comment'), widget=forms.Textarea,
  97
+                                    max_length=COMMENT_MAX_LENGTH)
  98
+
37 99
     def get_comment_object(self):
38 100
         """
39 101
         Return a new (unsaved) comment object based on the information in this
@@ -97,41 +159,6 @@ def check_for_duplicate_comment(self, new):
97 159
                 
98 160
         return new
99 161
 
100  
-    def security_errors(self):
101  
-        """Return just those errors associated with security"""
102  
-        errors = ErrorDict()
103  
-        for f in ["honeypot", "timestamp", "security_hash"]:
104  
-            if f in self.errors:
105  
-                errors[f] = self.errors[f]
106  
-        return errors
107  
-
108  
-    def clean_honeypot(self):
109  
-        """Check that nothing's been entered into the honeypot."""
110  
-        value = self.cleaned_data["honeypot"]
111  
-        if value:
112  
-            raise forms.ValidationError(self.fields["honeypot"].label)
113  
-        return value
114  
-
115  
-    def clean_security_hash(self):
116  
-        """Check the security hash."""
117  
-        security_hash_dict = {
118  
-            'content_type' : self.data.get("content_type", ""),
119  
-            'object_pk' : self.data.get("object_pk", ""),
120  
-            'timestamp' : self.data.get("timestamp", ""),
121  
-        }
122  
-        expected_hash = self.generate_security_hash(**security_hash_dict)
123  
-        actual_hash = self.cleaned_data["security_hash"]
124  
-        if expected_hash != actual_hash:
125  
-            raise forms.ValidationError("Security hash check failed.")
126  
-        return actual_hash
127  
-
128  
-    def clean_timestamp(self):
129  
-        """Make sure the timestamp isn't too far (> 2 hours) in the past."""
130  
-        ts = self.cleaned_data["timestamp"]
131  
-        if time.time() - ts > (2 * 60 * 60):
132  
-            raise forms.ValidationError("Timestamp check failed")
133  
-        return ts
134  
-
135 162
     def clean_comment(self):
136 163
         """
137 164
         If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
@@ -148,31 +175,14 @@ def clean_comment(self):
148 175
                     get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and'))
149 176
         return comment
150 177
 
151  
-    def generate_security_data(self):
152  
-        """Generate a dict of security data for "initial" data."""
153  
-        timestamp = int(time.time())
154  
-        security_dict =   {
155  
-            'content_type'  : str(self.target_object._meta),
156  
-            'object_pk'     : str(self.target_object._get_pk_val()),
157  
-            'timestamp'     : str(timestamp),
158  
-            'security_hash' : self.initial_security_hash(timestamp),
159  
-        }
160  
-        return security_dict
161  
-
162  
-    def initial_security_hash(self, timestamp):
163  
-        """
164  
-        Generate the initial security hash from self.content_object
165  
-        and a (unix) timestamp.
166  
-        """
167  
-
168  
-        initial_security_dict = {
169  
-            'content_type' : str(self.target_object._meta),
170  
-            'object_pk' : str(self.target_object._get_pk_val()),
171  
-            'timestamp' : str(timestamp),
172  
-          }
173  
-        return self.generate_security_hash(**initial_security_dict)
  178
+class CommentForm(CommentDetailsForm):
  179
+    honeypot      = forms.CharField(required=False,
  180
+                                    label=_('If you enter anything in this field '\
  181
+                                            'your comment will be treated as spam'))
174 182
 
175  
-    def generate_security_hash(self, content_type, object_pk, timestamp):
176  
-        """Generate a (SHA1) security hash from the provided info."""
177  
-        info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
178  
-        return sha_constructor("".join(info)).hexdigest()
  183
+    def clean_honeypot(self):
  184
+        """Check that nothing's been entered into the honeypot."""
  185
+        value = self.cleaned_data["honeypot"]
  186
+        if value:
  187
+            raise forms.ValidationError(self.fields["honeypot"].label)
  188
+        return value
15  docs/ref/contrib/comments/custom.txt
@@ -93,7 +93,12 @@ field::
93 93
             data['title'] = self.cleaned_data['title']
94 94
             return data
95 95
 
96  
-Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to point Django at these classes we've created::
  96
+Django provides a couple of "helper" classes to make writing certain types of
  97
+custom comment forms easier; see :mod:`django.contrib.comments.forms` for
  98
+more.
  99
+
  100
+Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to
  101
+point Django at these classes we've created::
97 102
 
98 103
     from my_comments_app.models import CommentWithTitle
99 104
     from my_comments_app.forms import CommentFormWithTitle
@@ -104,14 +109,18 @@ Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to po
104 109
     def get_form():
105 110
         return CommentFormWithTitle
106 111
 
107  
-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.
  112
+The above process should take care of most common situations. For more
  113
+advanced usage, there are additional methods you can define. Those are
  114
+explained in the next section.
108 115
 
109 116
 .. _custom-comment-app-api:
110 117
 
111 118
 Custom comment app API
112 119
 ======================
113 120
 
114  
-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.
  121
+The :mod:`django.contrib.comments` app defines the following methods; any
  122
+custom comment app must define at least one of them. All are optional,
  123
+however.
115 124
 
116 125
 .. function:: get_model()
117 126
 
48  docs/ref/contrib/comments/forms.txt
... ...
@@ -0,0 +1,48 @@
  1
+.. _ref-contrib-comments-forms:
  2
+
  3
+====================
  4
+Comment form classes
  5
+====================
  6
+
  7
+.. module:: django.contrib.comments.forms
  8
+   :synopsis: Forms for dealing with the built-in comment model.
  9
+
  10
+The ``django.contrib.comments.forms`` module contains a handful of forms
  11
+you'll use when writing custom views dealing with comments, or when writing
  12
+:ref:`custom comment apps <ref-contrib-comments-custom>`.
  13
+
  14
+.. class:: CommentForm
  15
+
  16
+   The main comment form representing the standard, built-in way of handling
  17
+   submitted comments. This is the class used by all the views
  18
+   :mod:`django.contrib.comments` to handle submitted comments.
  19
+
  20
+   If you want to build custom views that are similar to Django's built-in
  21
+   comment handling views, you'll probably want to use this form.
  22
+
  23
+Abstract comment forms for custom comment apps
  24
+----------------------------------------------
  25
+
  26
+If you're building a :ref:`custom comment app <ref-contrib-comments-custom>`,
  27
+you might want to replace *some* of the form logic but still rely on parts of
  28
+the existing form.
  29
+
  30
+:class:`CommentForm` is actually composed of a couple of abstract base class
  31
+forms that you can subclass to reuse pieces of the form handling logic:
  32
+
  33
+.. class:: CommentSecurityForm
  34
+
  35
+   Handles the anti-spoofing protection aspects of the comment form handling.
  36
+
  37
+   This class contains the ``content_type`` and ``object_pk`` fields pointing
  38
+   to the object the comment is attached to, along with a ``timestamp`` and a
  39
+   ``security_hash`` of all the form data. Together, the timestamp and the
  40
+   security hash ensure that spammers can't "replay" form submissions and
  41
+   flood you with comments.
  42
+
  43
+.. class:: CommentDetailsForm
  44
+
  45
+   Handles the details of the comment itself.
  46
+
  47
+   This class contains the ``name``, ``email``, ``url``, and the ``comment``
  48
+   field itself, along with the associated valdation logic.
1  docs/ref/contrib/comments/index.txt
@@ -215,3 +215,4 @@ More information
215 215
    signals
216 216
    upgrade
217 217
    custom
  218
+   forms

0 notes on commit 231a7e0

Please sign in to comment.
Something went wrong with that request. Please try again.