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)
|
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000)
|
||||||
|
|
||||||
class CommentForm(forms.Form):
|
class CommentSecurityForm(forms.Form):
|
||||||
name = forms.CharField(label=_("Name"), max_length=50)
|
"""
|
||||||
email = forms.EmailField(label=_("Email address"))
|
Handles the security aspects (anti-spoofing) for comment forms.
|
||||||
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'))
|
|
||||||
content_type = forms.CharField(widget=forms.HiddenInput)
|
content_type = forms.CharField(widget=forms.HiddenInput)
|
||||||
object_pk = forms.CharField(widget=forms.HiddenInput)
|
object_pk = forms.CharField(widget=forms.HiddenInput)
|
||||||
timestamp = forms.IntegerField(widget=forms.HiddenInput)
|
timestamp = forms.IntegerField(widget=forms.HiddenInput)
|
||||||
|
@ -32,8 +27,75 @@ class CommentForm(forms.Form):
|
||||||
if initial is None:
|
if initial is None:
|
||||||
initial = {}
|
initial = {}
|
||||||
initial.update(self.generate_security_data())
|
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):
|
def get_comment_object(self):
|
||||||
"""
|
"""
|
||||||
Return a new (unsaved) comment object based on the information in this
|
Return a new (unsaved) comment object based on the information in this
|
||||||
|
@ -97,41 +159,6 @@ class CommentForm(forms.Form):
|
||||||
|
|
||||||
return 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):
|
def clean_comment(self):
|
||||||
"""
|
"""
|
||||||
If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
|
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'))
|
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and'))
|
||||||
return comment
|
return comment
|
||||||
|
|
||||||
def generate_security_data(self):
|
class CommentForm(CommentDetailsForm):
|
||||||
"""Generate a dict of security data for "initial" data."""
|
honeypot = forms.CharField(required=False,
|
||||||
timestamp = int(time.time())
|
label=_('If you enter anything in this field '\
|
||||||
security_dict = {
|
'your comment will be treated as spam'))
|
||||||
'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):
|
def clean_honeypot(self):
|
||||||
"""
|
"""Check that nothing's been entered into the honeypot."""
|
||||||
Generate the initial security hash from self.content_object
|
value = self.cleaned_data["honeypot"]
|
||||||
and a (unix) timestamp.
|
if value:
|
||||||
"""
|
raise forms.ValidationError(self.fields["honeypot"].label)
|
||||||
|
return value
|
||||||
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()
|
|
||||||
|
|
|
@ -93,7 +93,12 @@ field::
|
||||||
data['title'] = self.cleaned_data['title']
|
data['title'] = self.cleaned_data['title']
|
||||||
return data
|
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.models import CommentWithTitle
|
||||||
from my_comments_app.forms import CommentFormWithTitle
|
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():
|
def get_form():
|
||||||
return CommentFormWithTitle
|
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:
|
||||||
|
|
||||||
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()
|
.. 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
|
signals
|
||||||
upgrade
|
upgrade
|
||||||
custom
|
custom
|
||||||
|
forms
|
||||||
|
|
Loading…
Reference in New Issue