Fixed #9958: split the `CommentForm` into a set of smaller forms. This 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
This commit is contained in:
parent
e923b545a4
commit
231a7e0419
|
@ -13,15 +13,10 @@ from django.utils.translation import ungettext, ugettext_lazy as _
|
|||
|
||||
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 @@ class CommentForm(forms.Form):
|
|||
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 @@ class CommentForm(forms.Form):
|
|||
|
||||
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 @@ class CommentForm(forms.Form):
|
|||
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
|
||||
class CommentForm(CommentDetailsForm):
|
||||
honeypot = forms.CharField(required=False,
|
||||
label=_('If you enter anything in this field '\
|
||||
'your comment will be treated as spam'))
|
||||
|
||||
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()
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -215,3 +215,4 @@ More information
|
|||
signals
|
||||
upgrade
|
||||
custom
|
||||
forms
|
||||
|
|
Loading…
Reference in New Issue