|
1 from django.core import validators |
|
2 from django import oldforms |
|
3 from django.core.mail import mail_admins, mail_managers |
|
4 from django.http import Http404 |
|
5 from django.core.exceptions import ObjectDoesNotExist |
|
6 from django.shortcuts import render_to_response |
|
7 from django.template import RequestContext |
|
8 from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC |
|
9 from django.contrib.contenttypes.models import ContentType |
|
10 from django.contrib.auth.forms import AuthenticationForm |
|
11 from django.http import HttpResponseRedirect |
|
12 from django.utils.text import normalize_newlines |
|
13 from django.conf import settings |
|
14 from django.utils.translation import ungettext, ugettext as _ |
|
15 from django.utils.encoding import smart_unicode |
|
16 import base64, datetime |
|
17 |
|
18 COMMENTS_PER_PAGE = 20 |
|
19 |
|
20 class PublicCommentManipulator(AuthenticationForm): |
|
21 "Manipulator that handles public registered comments" |
|
22 def __init__(self, user, ratings_required, ratings_range, num_rating_choices): |
|
23 AuthenticationForm.__init__(self) |
|
24 self.ratings_range, self.num_rating_choices = ratings_range, num_rating_choices |
|
25 choices = [(c, c) for c in ratings_range] |
|
26 def get_validator_list(rating_num): |
|
27 if rating_num <= num_rating_choices: |
|
28 return [validators.RequiredIfOtherFieldsGiven(['rating%d' % i for i in range(1, 9) if i != rating_num], _("This rating is required because you've entered at least one other rating."))] |
|
29 else: |
|
30 return [] |
|
31 self.fields.extend([ |
|
32 oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True, |
|
33 validator_list=[self.hasNoProfanities]), |
|
34 oldforms.RadioSelectField(field_name="rating1", choices=choices, |
|
35 is_required=ratings_required and num_rating_choices > 0, |
|
36 validator_list=get_validator_list(1), |
|
37 ), |
|
38 oldforms.RadioSelectField(field_name="rating2", choices=choices, |
|
39 is_required=ratings_required and num_rating_choices > 1, |
|
40 validator_list=get_validator_list(2), |
|
41 ), |
|
42 oldforms.RadioSelectField(field_name="rating3", choices=choices, |
|
43 is_required=ratings_required and num_rating_choices > 2, |
|
44 validator_list=get_validator_list(3), |
|
45 ), |
|
46 oldforms.RadioSelectField(field_name="rating4", choices=choices, |
|
47 is_required=ratings_required and num_rating_choices > 3, |
|
48 validator_list=get_validator_list(4), |
|
49 ), |
|
50 oldforms.RadioSelectField(field_name="rating5", choices=choices, |
|
51 is_required=ratings_required and num_rating_choices > 4, |
|
52 validator_list=get_validator_list(5), |
|
53 ), |
|
54 oldforms.RadioSelectField(field_name="rating6", choices=choices, |
|
55 is_required=ratings_required and num_rating_choices > 5, |
|
56 validator_list=get_validator_list(6), |
|
57 ), |
|
58 oldforms.RadioSelectField(field_name="rating7", choices=choices, |
|
59 is_required=ratings_required and num_rating_choices > 6, |
|
60 validator_list=get_validator_list(7), |
|
61 ), |
|
62 oldforms.RadioSelectField(field_name="rating8", choices=choices, |
|
63 is_required=ratings_required and num_rating_choices > 7, |
|
64 validator_list=get_validator_list(8), |
|
65 ), |
|
66 ]) |
|
67 if user.is_authenticated(): |
|
68 self["username"].is_required = False |
|
69 self["username"].validator_list = [] |
|
70 self["password"].is_required = False |
|
71 self["password"].validator_list = [] |
|
72 self.user_cache = user |
|
73 |
|
74 def hasNoProfanities(self, field_data, all_data): |
|
75 if settings.COMMENTS_ALLOW_PROFANITIES: |
|
76 return |
|
77 return validators.hasNoProfanities(field_data, all_data) |
|
78 |
|
79 def get_comment(self, new_data): |
|
80 "Helper function" |
|
81 return Comment(None, self.get_user_id(), new_data["content_type_id"], |
|
82 new_data["object_id"], new_data.get("headline", "").strip(), |
|
83 new_data["comment"].strip(), new_data.get("rating1", None), |
|
84 new_data.get("rating2", None), new_data.get("rating3", None), |
|
85 new_data.get("rating4", None), new_data.get("rating5", None), |
|
86 new_data.get("rating6", None), new_data.get("rating7", None), |
|
87 new_data.get("rating8", None), new_data.get("rating1", None) is not None, |
|
88 datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.SITE_ID) |
|
89 |
|
90 def save(self, new_data): |
|
91 today = datetime.date.today() |
|
92 c = self.get_comment(new_data) |
|
93 for old in Comment.objects.filter(content_type__id__exact=new_data["content_type_id"], |
|
94 object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()): |
|
95 # Check that this comment isn't duplicate. (Sometimes people post |
|
96 # comments twice by mistake.) If it is, fail silently by pretending |
|
97 # the comment was posted successfully. |
|
98 if old.submit_date.date() == today and old.comment == c.comment \ |
|
99 and old.rating1 == c.rating1 and old.rating2 == c.rating2 \ |
|
100 and old.rating3 == c.rating3 and old.rating4 == c.rating4 \ |
|
101 and old.rating5 == c.rating5 and old.rating6 == c.rating6 \ |
|
102 and old.rating7 == c.rating7 and old.rating8 == c.rating8: |
|
103 return old |
|
104 # If the user is leaving a rating, invalidate all old ratings. |
|
105 if c.rating1 is not None: |
|
106 old.valid_rating = False |
|
107 old.save() |
|
108 c.save() |
|
109 # If the commentor has posted fewer than COMMENTS_FIRST_FEW comments, |
|
110 # send the comment to the managers. |
|
111 if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW: |
|
112 message = ungettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s', |
|
113 'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s', settings.COMMENTS_FIRST_FEW) % \ |
|
114 {'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()} |
|
115 mail_managers("Comment posted by rookie user", message) |
|
116 if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.groups.all()]: |
|
117 message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': c.get_as_text()} |
|
118 mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text()) |
|
119 return c |
|
120 |
|
121 class PublicFreeCommentManipulator(oldforms.Manipulator): |
|
122 "Manipulator that handles public free (unregistered) comments" |
|
123 def __init__(self): |
|
124 self.fields = ( |
|
125 oldforms.TextField(field_name="person_name", max_length=50, is_required=True, |
|
126 validator_list=[self.hasNoProfanities]), |
|
127 oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True, |
|
128 validator_list=[self.hasNoProfanities]), |
|
129 ) |
|
130 |
|
131 def hasNoProfanities(self, field_data, all_data): |
|
132 if settings.COMMENTS_ALLOW_PROFANITIES: |
|
133 return |
|
134 return validators.hasNoProfanities(field_data, all_data) |
|
135 |
|
136 def get_comment(self, new_data): |
|
137 "Helper function" |
|
138 return FreeComment(None, new_data["content_type_id"], |
|
139 new_data["object_id"], new_data["comment"].strip(), |
|
140 new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"], |
|
141 new_data["ip_address"], False, settings.SITE_ID) |
|
142 |
|
143 def save(self, new_data): |
|
144 today = datetime.date.today() |
|
145 c = self.get_comment(new_data) |
|
146 # Check that this comment isn't duplicate. (Sometimes people post |
|
147 # comments twice by mistake.) If it is, fail silently by pretending |
|
148 # the comment was posted successfully. |
|
149 for old_comment in FreeComment.objects.filter(content_type__id__exact=new_data["content_type_id"], |
|
150 object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"], |
|
151 submit_date__year=today.year, submit_date__month=today.month, |
|
152 submit_date__day=today.day): |
|
153 if old_comment.comment == c.comment: |
|
154 return old_comment |
|
155 c.save() |
|
156 return c |
|
157 |
|
158 def post_comment(request, extra_context=None, context_processors=None): |
|
159 """ |
|
160 Post a comment |
|
161 |
|
162 Redirects to the `comments.comments.comment_was_posted` view upon success. |
|
163 |
|
164 Templates: `comment_preview` |
|
165 Context: |
|
166 comment |
|
167 the comment being posted |
|
168 comment_form |
|
169 the comment form |
|
170 options |
|
171 comment options |
|
172 target |
|
173 comment target |
|
174 hash |
|
175 security hash (must be included in a posted form to succesfully |
|
176 post a comment). |
|
177 rating_options |
|
178 comment ratings options |
|
179 ratings_optional |
|
180 are ratings optional? |
|
181 ratings_required |
|
182 are ratings required? |
|
183 rating_range |
|
184 range of ratings |
|
185 rating_choices |
|
186 choice of ratings |
|
187 """ |
|
188 if extra_context is None: extra_context = {} |
|
189 if not request.POST: |
|
190 raise Http404, _("Only POSTs are allowed") |
|
191 try: |
|
192 options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo'] |
|
193 except KeyError: |
|
194 raise Http404, _("One or more of the required fields wasn't submitted") |
|
195 photo_options = request.POST.get('photo_options', '') |
|
196 rating_options = normalize_newlines(request.POST.get('rating_options', '')) |
|
197 if Comment.objects.get_security_hash(options, photo_options, rating_options, target) != security_hash: |
|
198 raise Http404, _("Somebody tampered with the comment form (security violation)") |
|
199 # Now we can be assured the data is valid. |
|
200 if rating_options: |
|
201 rating_range, rating_choices = Comment.objects.get_rating_options(base64.decodestring(rating_options)) |
|
202 else: |
|
203 rating_range, rating_choices = [], [] |
|
204 content_type_id, object_id = target.split(':') # target is something like '52:5157' |
|
205 try: |
|
206 obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=object_id) |
|
207 except ObjectDoesNotExist: |
|
208 raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid") |
|
209 option_list = options.split(',') # options is something like 'pa,ra' |
|
210 new_data = request.POST.copy() |
|
211 new_data['content_type_id'] = content_type_id |
|
212 new_data['object_id'] = object_id |
|
213 new_data['ip_address'] = request.META.get('REMOTE_ADDR') |
|
214 new_data['is_public'] = IS_PUBLIC in option_list |
|
215 manipulator = PublicCommentManipulator(request.user, |
|
216 ratings_required=RATINGS_REQUIRED in option_list, |
|
217 ratings_range=rating_range, |
|
218 num_rating_choices=len(rating_choices)) |
|
219 errors = manipulator.get_validation_errors(new_data) |
|
220 # If user gave correct username/password and wasn't already logged in, log them in |
|
221 # so they don't have to enter a username/password again. |
|
222 if manipulator.get_user() and not manipulator.get_user().is_authenticated() and 'password' in new_data and manipulator.get_user().check_password(new_data['password']): |
|
223 from django.contrib.auth import login |
|
224 login(request, manipulator.get_user()) |
|
225 if errors or 'preview' in request.POST: |
|
226 class CommentFormWrapper(oldforms.FormWrapper): |
|
227 def __init__(self, manipulator, new_data, errors, rating_choices): |
|
228 oldforms.FormWrapper.__init__(self, manipulator, new_data, errors) |
|
229 self.rating_choices = rating_choices |
|
230 def ratings(self): |
|
231 field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))] |
|
232 for i, f in enumerate(field_list): |
|
233 f.choice = rating_choices[i] |
|
234 return field_list |
|
235 comment = errors and '' or manipulator.get_comment(new_data) |
|
236 comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices) |
|
237 return render_to_response('comments/preview.html', { |
|
238 'comment': comment, |
|
239 'comment_form': comment_form, |
|
240 'options': options, |
|
241 'target': target, |
|
242 'hash': security_hash, |
|
243 'rating_options': rating_options, |
|
244 'ratings_optional': RATINGS_OPTIONAL in option_list, |
|
245 'ratings_required': RATINGS_REQUIRED in option_list, |
|
246 'rating_range': rating_range, |
|
247 'rating_choices': rating_choices, |
|
248 }, context_instance=RequestContext(request, extra_context, context_processors)) |
|
249 elif 'post' in request.POST: |
|
250 # If the IP is banned, mail the admins, do NOT save the comment, and |
|
251 # serve up the "Thanks for posting" page as if the comment WAS posted. |
|
252 if request.META['REMOTE_ADDR'] in settings.BANNED_IPS: |
|
253 mail_admins("Banned IP attempted to post comment", smart_unicode(request.POST) + "\n\n" + str(request.META)) |
|
254 else: |
|
255 manipulator.do_html2python(new_data) |
|
256 comment = manipulator.save(new_data) |
|
257 return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id)) |
|
258 else: |
|
259 raise Http404, _("The comment form didn't provide either 'preview' or 'post'") |
|
260 |
|
261 def post_free_comment(request, extra_context=None, context_processors=None): |
|
262 """ |
|
263 Post a free comment (not requiring a log in) |
|
264 |
|
265 Redirects to `comments.comments.comment_was_posted` view on success. |
|
266 |
|
267 Templates: `comment_free_preview` |
|
268 Context: |
|
269 comment |
|
270 comment being posted |
|
271 comment_form |
|
272 comment form object |
|
273 options |
|
274 comment options |
|
275 target |
|
276 comment target |
|
277 hash |
|
278 security hash (must be included in a posted form to succesfully |
|
279 post a comment). |
|
280 """ |
|
281 if extra_context is None: extra_context = {} |
|
282 if not request.POST: |
|
283 raise Http404, _("Only POSTs are allowed") |
|
284 try: |
|
285 options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo'] |
|
286 except KeyError: |
|
287 raise Http404, _("One or more of the required fields wasn't submitted") |
|
288 if Comment.objects.get_security_hash(options, '', '', target) != security_hash: |
|
289 raise Http404, _("Somebody tampered with the comment form (security violation)") |
|
290 content_type_id, object_id = target.split(':') # target is something like '52:5157' |
|
291 content_type = ContentType.objects.get(pk=content_type_id) |
|
292 try: |
|
293 obj = content_type.get_object_for_this_type(pk=object_id) |
|
294 except ObjectDoesNotExist: |
|
295 raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid") |
|
296 option_list = options.split(',') |
|
297 new_data = request.POST.copy() |
|
298 new_data['content_type_id'] = content_type_id |
|
299 new_data['object_id'] = object_id |
|
300 new_data['ip_address'] = request.META['REMOTE_ADDR'] |
|
301 new_data['is_public'] = IS_PUBLIC in option_list |
|
302 manipulator = PublicFreeCommentManipulator() |
|
303 errors = manipulator.get_validation_errors(new_data) |
|
304 if errors or 'preview' in request.POST: |
|
305 comment = errors and '' or manipulator.get_comment(new_data) |
|
306 return render_to_response('comments/free_preview.html', { |
|
307 'comment': comment, |
|
308 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors), |
|
309 'options': options, |
|
310 'target': target, |
|
311 'hash': security_hash, |
|
312 }, context_instance=RequestContext(request, extra_context, context_processors)) |
|
313 elif 'post' in request.POST: |
|
314 # If the IP is banned, mail the admins, do NOT save the comment, and |
|
315 # serve up the "Thanks for posting" page as if the comment WAS posted. |
|
316 if request.META['REMOTE_ADDR'] in settings.BANNED_IPS: |
|
317 from django.core.mail import mail_admins |
|
318 mail_admins("Practical joker", smart_unicode(request.POST) + "\n\n" + str(request.META)) |
|
319 else: |
|
320 manipulator.do_html2python(new_data) |
|
321 comment = manipulator.save(new_data) |
|
322 return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id)) |
|
323 else: |
|
324 raise Http404, _("The comment form didn't provide either 'preview' or 'post'") |
|
325 |
|
326 def comment_was_posted(request, extra_context=None, context_processors=None): |
|
327 """ |
|
328 Display "comment was posted" success page |
|
329 |
|
330 Templates: `comment_posted` |
|
331 Context: |
|
332 object |
|
333 The object the comment was posted on |
|
334 """ |
|
335 if extra_context is None: extra_context = {} |
|
336 obj = None |
|
337 if 'c' in request.GET: |
|
338 content_type_id, object_id = request.GET['c'].split(':') |
|
339 try: |
|
340 content_type = ContentType.objects.get(pk=content_type_id) |
|
341 obj = content_type.get_object_for_this_type(pk=object_id) |
|
342 except ObjectDoesNotExist: |
|
343 pass |
|
344 return render_to_response('comments/posted.html', {'object': obj}, |
|
345 context_instance=RequestContext(request, extra_context, context_processors)) |