Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
@jacobian jacobian authored
View
154 django/contrib/comments/forms.py
@@ -13,15 +13,10 @@
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000)
-class CommentForm(forms.Form):
- name = forms.CharField(label=_("Name"), max_length=50)
- email = forms.EmailField(label=_("Email address"))
- url = forms.URLField(label=_("URL"), required=False)
- comment = forms.CharField(label=_('Comment'), widget=forms.Textarea,
- max_length=COMMENT_MAX_LENGTH)
- honeypot = forms.CharField(required=False,
- label=_('If you enter anything in this field '\
- 'your comment will be treated as spam'))
+class CommentSecurityForm(forms.Form):
+ """
+ Handles the security aspects (anti-spoofing) for comment forms.
+ """
content_type = forms.CharField(widget=forms.HiddenInput)
object_pk = forms.CharField(widget=forms.HiddenInput)
timestamp = forms.IntegerField(widget=forms.HiddenInput)
@@ -32,8 +27,75 @@ def __init__(self, target_object, data=None, initial=None):
if initial is None:
initial = {}
initial.update(self.generate_security_data())
- super(CommentForm, self).__init__(data=data, initial=initial)
+ super(CommentSecurityForm, self).__init__(data=data, initial=initial)
+ def security_errors(self):
+ """Return just those errors associated with security"""
+ errors = ErrorDict()
+ for f in ["honeypot", "timestamp", "security_hash"]:
+ if f in self.errors:
+ errors[f] = self.errors[f]
+ return errors
+
+ def clean_security_hash(self):
+ """Check the security hash."""
+ security_hash_dict = {
+ 'content_type' : self.data.get("content_type", ""),
+ 'object_pk' : self.data.get("object_pk", ""),
+ 'timestamp' : self.data.get("timestamp", ""),
+ }
+ expected_hash = self.generate_security_hash(**security_hash_dict)
+ actual_hash = self.cleaned_data["security_hash"]
+ if expected_hash != actual_hash:
+ raise forms.ValidationError("Security hash check failed.")
+ return actual_hash
+
+ def clean_timestamp(self):
+ """Make sure the timestamp isn't too far (> 2 hours) in the past."""
+ ts = self.cleaned_data["timestamp"]
+ if time.time() - ts > (2 * 60 * 60):
+ raise forms.ValidationError("Timestamp check failed")
+ return ts
+
+ def generate_security_data(self):
+ """Generate a dict of security data for "initial" data."""
+ timestamp = int(time.time())
+ security_dict = {
+ 'content_type' : str(self.target_object._meta),
+ 'object_pk' : str(self.target_object._get_pk_val()),
+ 'timestamp' : str(timestamp),
+ 'security_hash' : self.initial_security_hash(timestamp),
+ }
+ return security_dict
+
+ def initial_security_hash(self, timestamp):
+ """
+ Generate the initial security hash from self.content_object
+ and a (unix) timestamp.
+ """
+
+ initial_security_dict = {
+ 'content_type' : str(self.target_object._meta),
+ 'object_pk' : str(self.target_object._get_pk_val()),
+ 'timestamp' : str(timestamp),
+ }
+ return self.generate_security_hash(**initial_security_dict)
+
+ def generate_security_hash(self, content_type, object_pk, timestamp):
+ """Generate a (SHA1) security hash from the provided info."""
+ info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
+ return sha_constructor("".join(info)).hexdigest()
+
+class CommentDetailsForm(CommentSecurityForm):
+ """
+ Handles the specific details of the comment (name, comment, etc.).
+ """
+ name = forms.CharField(label=_("Name"), max_length=50)
+ email = forms.EmailField(label=_("Email address"))
+ url = forms.URLField(label=_("URL"), required=False)
+ comment = forms.CharField(label=_('Comment'), widget=forms.Textarea,
+ max_length=COMMENT_MAX_LENGTH)
+
def get_comment_object(self):
"""
Return a new (unsaved) comment object based on the information in this
@@ -97,41 +159,6 @@ def check_for_duplicate_comment(self, new):
return new
- def security_errors(self):
- """Return just those errors associated with security"""
- errors = ErrorDict()
- for f in ["honeypot", "timestamp", "security_hash"]:
- if f in self.errors:
- errors[f] = self.errors[f]
- return errors
-
- def clean_honeypot(self):
- """Check that nothing's been entered into the honeypot."""
- value = self.cleaned_data["honeypot"]
- if value:
- raise forms.ValidationError(self.fields["honeypot"].label)
- return value
-
- def clean_security_hash(self):
- """Check the security hash."""
- security_hash_dict = {
- 'content_type' : self.data.get("content_type", ""),
- 'object_pk' : self.data.get("object_pk", ""),
- 'timestamp' : self.data.get("timestamp", ""),
- }
- expected_hash = self.generate_security_hash(**security_hash_dict)
- actual_hash = self.cleaned_data["security_hash"]
- if expected_hash != actual_hash:
- raise forms.ValidationError("Security hash check failed.")
- return actual_hash
-
- def clean_timestamp(self):
- """Make sure the timestamp isn't too far (> 2 hours) in the past."""
- ts = self.cleaned_data["timestamp"]
- if time.time() - ts > (2 * 60 * 60):
- raise forms.ValidationError("Timestamp check failed")
- return ts
-
def clean_comment(self):
"""
If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
@@ -148,31 +175,14 @@ def clean_comment(self):
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and'))
return comment
- def generate_security_data(self):
- """Generate a dict of security data for "initial" data."""
- timestamp = int(time.time())
- security_dict = {
- 'content_type' : str(self.target_object._meta),
- 'object_pk' : str(self.target_object._get_pk_val()),
- 'timestamp' : str(timestamp),
- 'security_hash' : self.initial_security_hash(timestamp),
- }
- return security_dict
-
- def initial_security_hash(self, timestamp):
- """
- Generate the initial security hash from self.content_object
- and a (unix) timestamp.
- """
-
- initial_security_dict = {
- 'content_type' : str(self.target_object._meta),
- 'object_pk' : str(self.target_object._get_pk_val()),
- 'timestamp' : str(timestamp),
- }
- return self.generate_security_hash(**initial_security_dict)
+class CommentForm(CommentDetailsForm):
+ honeypot = forms.CharField(required=False,
+ label=_('If you enter anything in this field '\
+ 'your comment will be treated as spam'))
- def generate_security_hash(self, content_type, object_pk, timestamp):
- """Generate a (SHA1) security hash from the provided info."""
- info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
- return sha_constructor("".join(info)).hexdigest()
+ def clean_honeypot(self):
+ """Check that nothing's been entered into the honeypot."""
+ value = self.cleaned_data["honeypot"]
+ if value:
+ raise forms.ValidationError(self.fields["honeypot"].label)
+ return value
View
15 docs/ref/contrib/comments/custom.txt
@@ -93,7 +93,12 @@ field::
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::
+Django provides a couple of "helper" classes to make writing certain types of
+custom comment forms easier; see :mod:`django.contrib.comments.forms` for
+more.
+
+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
@@ -104,14 +109,18 @@ Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to po
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.
+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.
+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()
View
48 docs/ref/contrib/comments/forms.txt
@@ -0,0 +1,48 @@
+.. _ref-contrib-comments-forms:
+
+====================
+Comment form classes
+====================
+
+.. module:: django.contrib.comments.forms
+ :synopsis: Forms for dealing with the built-in comment model.
+
+The ``django.contrib.comments.forms`` module contains a handful of forms
+you'll use when writing custom views dealing with comments, or when writing
+:ref:`custom comment apps <ref-contrib-comments-custom>`.
+
+.. class:: CommentForm
+
+ The main comment form representing the standard, built-in way of handling
+ submitted comments. This is the class used by all the views
+ :mod:`django.contrib.comments` to handle submitted comments.
+
+ If you want to build custom views that are similar to Django's built-in
+ comment handling views, you'll probably want to use this form.
+
+Abstract comment forms for custom comment apps
+----------------------------------------------
+
+If you're building a :ref:`custom comment app <ref-contrib-comments-custom>`,
+you might want to replace *some* of the form logic but still rely on parts of
+the existing form.
+
+:class:`CommentForm` is actually composed of a couple of abstract base class
+forms that you can subclass to reuse pieces of the form handling logic:
+
+.. class:: CommentSecurityForm
+
+ Handles the anti-spoofing protection aspects of the comment form handling.
+
+ This class contains the ``content_type`` and ``object_pk`` fields pointing
+ to the object the comment is attached to, along with a ``timestamp`` and a
+ ``security_hash`` of all the form data. Together, the timestamp and the
+ security hash ensure that spammers can't "replay" form submissions and
+ flood you with comments.
+
+.. class:: CommentDetailsForm
+
+ Handles the details of the comment itself.
+
+ This class contains the ``name``, ``email``, ``url``, and the ``comment``
+ field itself, along with the associated valdation logic.
View
1  docs/ref/contrib/comments/index.txt
@@ -215,3 +215,4 @@ More information
signals
upgrade
custom
+ forms

0 comments on commit 231a7e0

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