4 from django
import forms
5 from django
.forms
.util
import ErrorDict
6 from django
.conf
import settings
7 from django
.contrib
.contenttypes
.models
import ContentType
8 from models
import Comment
9 from django
.utils
.encoding
import force_unicode
10 from django
.utils
.hashcompat
import sha_constructor
11 from django
.utils
.text
import get_text_list
12 from django
.utils
.translation
import ungettext
, ugettext_lazy
as _
14 COMMENT_MAX_LENGTH
= getattr(settings
,'COMMENT_MAX_LENGTH', 3000)
16 class CommentSecurityForm(forms
.Form
):
18 Handles the security aspects (anti-spoofing) for comment forms.
20 content_type
= forms
.CharField(widget
=forms
.HiddenInput
)
21 object_pk
= forms
.CharField(widget
=forms
.HiddenInput
)
22 timestamp
= forms
.IntegerField(widget
=forms
.HiddenInput
)
23 security_hash
= forms
.CharField(min_length
=40, max_length
=40, widget
=forms
.HiddenInput
)
25 def __init__(self
, target_object
, data
=None, initial
=None):
26 self
.target_object
= target_object
29 initial
.update(self
.generate_security_data())
30 super(CommentSecurityForm
, self
).__init
__(data
=data
, initial
=initial
)
32 def security_errors(self
):
33 """Return just those errors associated with security"""
35 for f
in ["honeypot", "timestamp", "security_hash"]:
37 errors
[f
] = self
.errors
[f
]
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", ""),
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.")
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")
60 def generate_security_data(self
):
61 """Generate a dict of security data for "initial" data."""
62 timestamp
= int(time
.time())
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
),
71 def initial_security_hash(self
, timestamp
):
73 Generate the initial security hash from self.content_object
74 and a (unix) timestamp.
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
),
82 return self
.generate_security_hash(**initial_security_dict
)
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()
89 class CommentDetailsForm(CommentSecurityForm
):
91 Handles the specific details of the comment (name, comment, etc.).
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
)
99 def get_comment_object(self
):
101 Return a new (unsaved) comment object based on the information in this
102 form. Assumes that the form is already validated and will throw a
105 Does not set any of the fields that would come from a Request object
106 (i.e. ``user`` or ``ip_address``).
108 if not self
.is_valid():
109 raise ValueError("get_comment_object may only be called on valid forms")
111 CommentModel
= self
.get_comment_model()
112 new
= CommentModel(**self
.get_comment_create_data())
113 new
= self
.check_for_duplicate_comment(new
)
117 def get_comment_model(self
):
119 Get the comment model to create with this form. Subclasses in custom
120 comment apps should override this, get_comment_create_data, and perhaps
121 check_for_duplicate_comment to provide custom comment models.
125 def get_comment_create_data(self
):
127 Returns the dict of data to be used to create a comment. Subclasses in
128 custom comment apps that override get_comment_model can override this
129 method to add extra fields onto a custom comment model.
132 content_type
= ContentType
.objects
.get_for_model(self
.target_object
),
133 object_pk
= force_unicode(self
.target_object
._get
_pk
_val
()),
134 user_name
= self
.cleaned_data
["name"],
135 user_email
= self
.cleaned_data
["email"],
136 user_url
= self
.cleaned_data
["url"],
137 comment
= self
.cleaned_data
["comment"],
138 submit_date
= datetime
.datetime
.now(),
139 site_id
= settings
.SITE_ID
,
144 def check_for_duplicate_comment(self
, new
):
146 Check that a submitted comment isn't a duplicate. This might be caused
147 by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
149 possible_duplicates
= self
.get_comment_model()._default
_manager
.using(
150 self
.target_object
._state
.db
152 content_type
= new
.content_type
,
153 object_pk
= new
.object_pk
,
154 user_name
= new
.user_name
,
155 user_email
= new
.user_email
,
156 user_url
= new
.user_url
,
158 for old
in possible_duplicates
:
159 if old
.submit_date
.date() == new
.submit_date
.date() and old
.comment
== new
.comment
:
164 def clean_comment(self
):
166 If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
167 contain anything in PROFANITIES_LIST.
169 comment
= self
.cleaned_data
["comment"]
170 if settings
.COMMENTS_ALLOW_PROFANITIES
== False:
171 bad_words
= [w
for w
in settings
.PROFANITIES_LIST
if w
in comment
.lower()]
173 plural
= len(bad_words
) > 1
174 raise forms
.ValidationError(ungettext(
175 "Watch your mouth! The word %s is not allowed here.",
176 "Watch your mouth! The words %s are not allowed here.", plural
) % \
177 get_text_list(['"%s%s%s"' % (i
[0], '-'*(len(i
)-2), i
[-1]) for i
in bad_words
], 'and'))
180 class CommentForm(CommentDetailsForm
):
181 honeypot
= forms
.CharField(required
=False,
182 label
=_('If you enter anything in this field '\
183 'your comment will be treated as spam'))
185 def clean_honeypot(self
):
186 """Check that nothing's been entered into the honeypot."""
187 value
= self
.cleaned_data
["honeypot"]
189 raise forms
.ValidationError(self
.fields
["honeypot"].label
)