|
1 import re |
|
2 import time |
|
3 import datetime |
|
4 |
|
5 from django import forms |
|
6 from django.forms.util import ErrorDict |
|
7 from django.conf import settings |
|
8 from django.http import Http404 |
|
9 from django.contrib.contenttypes.models import ContentType |
|
10 from models import Comment |
|
11 from django.utils.encoding import force_unicode |
|
12 from django.utils.hashcompat import sha_constructor |
|
13 from django.utils.text import get_text_list |
|
14 from django.utils.translation import ungettext, ugettext_lazy as _ |
|
15 |
|
16 COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000) |
|
17 |
|
18 class CommentForm(forms.Form): |
|
19 name = forms.CharField(label=_("Name"), max_length=50) |
|
20 email = forms.EmailField(label=_("Email address")) |
|
21 url = forms.URLField(label=_("URL"), required=False) |
|
22 comment = forms.CharField(label=_('Comment'), widget=forms.Textarea, |
|
23 max_length=COMMENT_MAX_LENGTH) |
|
24 honeypot = forms.CharField(required=False, |
|
25 label=_('If you enter anything in this field '\ |
|
26 'your comment will be treated as spam')) |
|
27 content_type = forms.CharField(widget=forms.HiddenInput) |
|
28 object_pk = forms.CharField(widget=forms.HiddenInput) |
|
29 timestamp = forms.IntegerField(widget=forms.HiddenInput) |
|
30 security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput) |
|
31 |
|
32 def __init__(self, target_object, data=None, initial=None): |
|
33 self.target_object = target_object |
|
34 if initial is None: |
|
35 initial = {} |
|
36 initial.update(self.generate_security_data()) |
|
37 super(CommentForm, self).__init__(data=data, initial=initial) |
|
38 |
|
39 def get_comment_object(self): |
|
40 """ |
|
41 Return a new (unsaved) comment object based on the information in this |
|
42 form. Assumes that the form is already validated and will throw a |
|
43 ValueError if not. |
|
44 |
|
45 Does not set any of the fields that would come from a Request object |
|
46 (i.e. ``user`` or ``ip_address``). |
|
47 """ |
|
48 if not self.is_valid(): |
|
49 raise ValueError("get_comment_object may only be called on valid forms") |
|
50 |
|
51 new = Comment( |
|
52 content_type = ContentType.objects.get_for_model(self.target_object), |
|
53 object_pk = force_unicode(self.target_object._get_pk_val()), |
|
54 user_name = self.cleaned_data["name"], |
|
55 user_email = self.cleaned_data["email"], |
|
56 user_url = self.cleaned_data["url"], |
|
57 comment = self.cleaned_data["comment"], |
|
58 submit_date = datetime.datetime.now(), |
|
59 site_id = settings.SITE_ID, |
|
60 is_public = True, |
|
61 is_removed = False, |
|
62 ) |
|
63 |
|
64 # Check that this comment isn't duplicate. (Sometimes people post comments |
|
65 # twice by mistake.) If it is, fail silently by returning the old comment. |
|
66 possible_duplicates = Comment.objects.filter( |
|
67 content_type = new.content_type, |
|
68 object_pk = new.object_pk, |
|
69 user_name = new.user_name, |
|
70 user_email = new.user_email, |
|
71 user_url = new.user_url, |
|
72 ) |
|
73 for old in possible_duplicates: |
|
74 if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment: |
|
75 return old |
|
76 |
|
77 return new |
|
78 |
|
79 def security_errors(self): |
|
80 """Return just those errors associated with security""" |
|
81 errors = ErrorDict() |
|
82 for f in ["honeypot", "timestamp", "security_hash"]: |
|
83 if f in self.errors: |
|
84 errors[f] = self.errors[f] |
|
85 return errors |
|
86 |
|
87 def clean_honeypot(self): |
|
88 """Check that nothing's been entered into the honeypot.""" |
|
89 value = self.cleaned_data["honeypot"] |
|
90 if value: |
|
91 raise forms.ValidationError(self.fields["honeypot"].label) |
|
92 return value |
|
93 |
|
94 def clean_security_hash(self): |
|
95 """Check the security hash.""" |
|
96 security_hash_dict = { |
|
97 'content_type' : self.data.get("content_type", ""), |
|
98 'object_pk' : self.data.get("object_pk", ""), |
|
99 'timestamp' : self.data.get("timestamp", ""), |
|
100 } |
|
101 expected_hash = self.generate_security_hash(**security_hash_dict) |
|
102 actual_hash = self.cleaned_data["security_hash"] |
|
103 if expected_hash != actual_hash: |
|
104 raise forms.ValidationError("Security hash check failed.") |
|
105 return actual_hash |
|
106 |
|
107 def clean_timestamp(self): |
|
108 """Make sure the timestamp isn't too far (> 2 hours) in the past.""" |
|
109 ts = self.cleaned_data["timestamp"] |
|
110 if time.time() - ts > (2 * 60 * 60): |
|
111 raise forms.ValidationError("Timestamp check failed") |
|
112 return ts |
|
113 |
|
114 def clean_comment(self): |
|
115 """ |
|
116 If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't |
|
117 contain anything in PROFANITIES_LIST. |
|
118 """ |
|
119 comment = self.cleaned_data["comment"] |
|
120 if settings.COMMENTS_ALLOW_PROFANITIES == False: |
|
121 bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()] |
|
122 if bad_words: |
|
123 plural = len(bad_words) > 1 |
|
124 raise forms.ValidationError(ungettext( |
|
125 "Watch your mouth! The word %s is not allowed here.", |
|
126 "Watch your mouth! The words %s are not allowed here.", plural) % \ |
|
127 get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and')) |
|
128 return comment |
|
129 |
|
130 def generate_security_data(self): |
|
131 """Generate a dict of security data for "initial" data.""" |
|
132 timestamp = int(time.time()) |
|
133 security_dict = { |
|
134 'content_type' : str(self.target_object._meta), |
|
135 'object_pk' : str(self.target_object._get_pk_val()), |
|
136 'timestamp' : str(timestamp), |
|
137 'security_hash' : self.initial_security_hash(timestamp), |
|
138 } |
|
139 return security_dict |
|
140 |
|
141 def initial_security_hash(self, timestamp): |
|
142 """ |
|
143 Generate the initial security hash from self.content_object |
|
144 and a (unix) timestamp. |
|
145 """ |
|
146 |
|
147 initial_security_dict = { |
|
148 'content_type' : str(self.target_object._meta), |
|
149 'object_pk' : str(self.target_object._get_pk_val()), |
|
150 'timestamp' : str(timestamp), |
|
151 } |
|
152 return self.generate_security_hash(**initial_security_dict) |
|
153 |
|
154 def generate_security_hash(self, content_type, object_pk, timestamp): |
|
155 """Generate a (SHA1) security hash from the provided info.""" |
|
156 info = (content_type, object_pk, timestamp, settings.SECRET_KEY) |
|
157 return sha_constructor("".join(info)).hexdigest() |