| import base64 |
| import datetime |
| |
| from django.core import validators |
| from django import oldforms |
| from django.core.mail import mail_admins, mail_managers |
| from django.http import Http404 |
| from django.core.exceptions import ObjectDoesNotExist |
| from django.shortcuts import render_to_response |
| from django.template import RequestContext |
| from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC |
| from django.contrib.contenttypes.models import ContentType |
| from django.contrib.auth import authenticate |
| from django.http import HttpResponseRedirect |
| from django.utils.text import normalize_newlines |
| from django.conf import settings |
| from django.utils.translation import ungettext, ugettext as _ |
| from django.utils.encoding import smart_unicode |
| |
| COMMENTS_PER_PAGE = 20 |
| |
| # TODO: This is a copy of the manipulator-based form that used to live in |
| # contrib.auth.forms. It should be replaced with the newforms version that |
| # has now been added to contrib.auth.forms when the comments app gets updated |
| # for newforms. |
| |
| class AuthenticationForm(oldforms.Manipulator): |
| """ |
| Base class for authenticating users. Extend this to get a form that accepts |
| username/password logins. |
| """ |
| def __init__(self, request=None): |
| """ |
| If request is passed in, the manipulator will validate that cookies are |
| enabled. Note that the request (a HttpRequest object) must have set a |
| cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before |
| running this validator. |
| """ |
| self.request = request |
| self.fields = [ |
| oldforms.TextField(field_name="username", length=15, max_length=30, is_required=True, |
| validator_list=[self.isValidUser, self.hasCookiesEnabled]), |
| oldforms.PasswordField(field_name="password", length=15, max_length=30, is_required=True), |
| ] |
| self.user_cache = None |
| |
| def hasCookiesEnabled(self, field_data, all_data): |
| if self.request and not self.request.session.test_cookie_worked(): |
| raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.") |
| |
| def isValidUser(self, field_data, all_data): |
| username = field_data |
| password = all_data.get('password', None) |
| self.user_cache = authenticate(username=username, password=password) |
| if self.user_cache is None: |
| raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") |
| elif not self.user_cache.is_active: |
| raise validators.ValidationError, _("This account is inactive.") |
| |
| def get_user_id(self): |
| if self.user_cache: |
| return self.user_cache.id |
| return None |
| |
| def get_user(self): |
| return self.user_cache |
| |
| class PublicCommentManipulator(AuthenticationForm): |
| "Manipulator that handles public registered comments" |
| def __init__(self, user, ratings_required, ratings_range, num_rating_choices): |
| AuthenticationForm.__init__(self) |
| self.ratings_range, self.num_rating_choices = ratings_range, num_rating_choices |
| choices = [(c, c) for c in ratings_range] |
| def get_validator_list(rating_num): |
| if rating_num <= num_rating_choices: |
| 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."))] |
| else: |
| return [] |
| self.fields.extend([ |
| oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True, |
| validator_list=[self.hasNoProfanities]), |
| oldforms.RadioSelectField(field_name="rating1", choices=choices, |
| is_required=ratings_required and num_rating_choices > 0, |
| validator_list=get_validator_list(1), |
| ), |
| oldforms.RadioSelectField(field_name="rating2", choices=choices, |
| is_required=ratings_required and num_rating_choices > 1, |
| validator_list=get_validator_list(2), |
| ), |
| oldforms.RadioSelectField(field_name="rating3", choices=choices, |
| is_required=ratings_required and num_rating_choices > 2, |
| validator_list=get_validator_list(3), |
| ), |
| oldforms.RadioSelectField(field_name="rating4", choices=choices, |
| is_required=ratings_required and num_rating_choices > 3, |
| validator_list=get_validator_list(4), |
| ), |
| oldforms.RadioSelectField(field_name="rating5", choices=choices, |
| is_required=ratings_required and num_rating_choices > 4, |
| validator_list=get_validator_list(5), |
| ), |
| oldforms.RadioSelectField(field_name="rating6", choices=choices, |
| is_required=ratings_required and num_rating_choices > 5, |
| validator_list=get_validator_list(6), |
| ), |
| oldforms.RadioSelectField(field_name="rating7", choices=choices, |
| is_required=ratings_required and num_rating_choices > 6, |
| validator_list=get_validator_list(7), |
| ), |
| oldforms.RadioSelectField(field_name="rating8", choices=choices, |
| is_required=ratings_required and num_rating_choices > 7, |
| validator_list=get_validator_list(8), |
| ), |
| ]) |
| if user.is_authenticated(): |
| self["username"].is_required = False |
| self["username"].validator_list = [] |
| self["password"].is_required = False |
| self["password"].validator_list = [] |
| self.user_cache = user |
| |
| def hasNoProfanities(self, field_data, all_data): |
| if settings.COMMENTS_ALLOW_PROFANITIES: |
| return |
| return validators.hasNoProfanities(field_data, all_data) |
| |
| def get_comment(self, new_data): |
| "Helper function" |
| return Comment(None, self.get_user_id(), new_data["content_type_id"], |
| new_data["object_id"], new_data.get("headline", "").strip(), |
| new_data["comment"].strip(), new_data.get("rating1", None), |
| new_data.get("rating2", None), new_data.get("rating3", None), |
| new_data.get("rating4", None), new_data.get("rating5", None), |
| new_data.get("rating6", None), new_data.get("rating7", None), |
| new_data.get("rating8", None), new_data.get("rating1", None) is not None, |
| datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.SITE_ID) |
| |
| def save(self, new_data): |
| today = datetime.date.today() |
| c = self.get_comment(new_data) |
| for old in Comment.objects.filter(content_type__id__exact=new_data["content_type_id"], |
| object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()): |
| # Check that this comment isn't duplicate. (Sometimes people post |
| # comments twice by mistake.) If it is, fail silently by pretending |
| # the comment was posted successfully. |
| if old.submit_date.date() == today and old.comment == c.comment \ |
| and old.rating1 == c.rating1 and old.rating2 == c.rating2 \ |
| and old.rating3 == c.rating3 and old.rating4 == c.rating4 \ |
| and old.rating5 == c.rating5 and old.rating6 == c.rating6 \ |
| and old.rating7 == c.rating7 and old.rating8 == c.rating8: |
| return old |
| # If the user is leaving a rating, invalidate all old ratings. |
| if c.rating1 is not None: |
| old.valid_rating = False |
| old.save() |
| c.save() |
| # If the commentor has posted fewer than COMMENTS_FIRST_FEW comments, |
| # send the comment to the managers. |
| if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW: |
| message = ungettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s', |
| 'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s', settings.COMMENTS_FIRST_FEW) % \ |
| {'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()} |
| mail_managers("Comment posted by rookie user", message) |
| if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.groups.all()]: |
| message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': c.get_as_text()} |
| mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text()) |
| return c |
| |
| class PublicFreeCommentManipulator(oldforms.Manipulator): |
| "Manipulator that handles public free (unregistered) comments" |
| def __init__(self): |
| self.fields = ( |
| oldforms.TextField(field_name="person_name", max_length=50, is_required=True, |
| validator_list=[self.hasNoProfanities]), |
| oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True, |
| validator_list=[self.hasNoProfanities]), |
| ) |
| |
| def hasNoProfanities(self, field_data, all_data): |
| if settings.COMMENTS_ALLOW_PROFANITIES: |
| return |
| return validators.hasNoProfanities(field_data, all_data) |
| |
| def get_comment(self, new_data): |
| "Helper function" |
| return FreeComment(None, new_data["content_type_id"], |
| new_data["object_id"], new_data["comment"].strip(), |
| new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"], |
| new_data["ip_address"], False, settings.SITE_ID) |
| |
| def save(self, new_data): |
| today = datetime.date.today() |
| c = self.get_comment(new_data) |
| # Check that this comment isn't duplicate. (Sometimes people post |
| # comments twice by mistake.) If it is, fail silently by pretending |
| # the comment was posted successfully. |
| for old_comment in FreeComment.objects.filter(content_type__id__exact=new_data["content_type_id"], |
| object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"], |
| submit_date__year=today.year, submit_date__month=today.month, |
| submit_date__day=today.day): |
| if old_comment.comment == c.comment: |
| return old_comment |
| c.save() |
| return c |
| |
| def post_comment(request, extra_context=None, context_processors=None): |
| """ |
| Post a comment |
| |
| Redirects to the `comments.comments.comment_was_posted` view upon success. |
| |
| Templates: `comment_preview` |
| Context: |
| comment |
| the comment being posted |
| comment_form |
| the comment form |
| options |
| comment options |
| target |
| comment target |
| hash |
| security hash (must be included in a posted form to succesfully |
| post a comment). |
| rating_options |
| comment ratings options |
| ratings_optional |
| are ratings optional? |
| ratings_required |
| are ratings required? |
| rating_range |
| range of ratings |
| rating_choices |
| choice of ratings |
| """ |
| if extra_context is None: extra_context = {} |
| if not request.POST: |
| raise Http404, _("Only POSTs are allowed") |
| try: |
| options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo'] |
| except KeyError: |
| raise Http404, _("One or more of the required fields wasn't submitted") |
| photo_options = request.POST.get('photo_options', '') |
| rating_options = normalize_newlines(request.POST.get('rating_options', '')) |
| if Comment.objects.get_security_hash(options, photo_options, rating_options, target) != security_hash: |
| raise Http404, _("Somebody tampered with the comment form (security violation)") |
| # Now we can be assured the data is valid. |
| if rating_options: |
| rating_range, rating_choices = Comment.objects.get_rating_options(base64.decodestring(rating_options)) |
| else: |
| rating_range, rating_choices = [], [] |
| content_type_id, object_id = target.split(':') # target is something like '52:5157' |
| try: |
| obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=object_id) |
| except ObjectDoesNotExist: |
| raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid") |
| option_list = options.split(',') # options is something like 'pa,ra' |
| new_data = request.POST.copy() |
| new_data['content_type_id'] = content_type_id |
| new_data['object_id'] = object_id |
| new_data['ip_address'] = request.META.get('REMOTE_ADDR') |
| new_data['is_public'] = IS_PUBLIC in option_list |
| manipulator = PublicCommentManipulator(request.user, |
| ratings_required=RATINGS_REQUIRED in option_list, |
| ratings_range=rating_range, |
| num_rating_choices=len(rating_choices)) |
| errors = manipulator.get_validation_errors(new_data) |
| # If user gave correct username/password and wasn't already logged in, log them in |
| # so they don't have to enter a username/password again. |
| 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']): |
| from django.contrib.auth import login |
| login(request, manipulator.get_user()) |
| if errors or 'preview' in request.POST: |
| class CommentFormWrapper(oldforms.FormWrapper): |
| def __init__(self, manipulator, new_data, errors, rating_choices): |
| oldforms.FormWrapper.__init__(self, manipulator, new_data, errors) |
| self.rating_choices = rating_choices |
| def ratings(self): |
| field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))] |
| for i, f in enumerate(field_list): |
| f.choice = rating_choices[i] |
| return field_list |
| comment = errors and '' or manipulator.get_comment(new_data) |
| comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices) |
| return render_to_response('comments/preview.html', { |
| 'comment': comment, |
| 'comment_form': comment_form, |
| 'options': options, |
| 'target': target, |
| 'hash': security_hash, |
| 'rating_options': rating_options, |
| 'ratings_optional': RATINGS_OPTIONAL in option_list, |
| 'ratings_required': RATINGS_REQUIRED in option_list, |
| 'rating_range': rating_range, |
| 'rating_choices': rating_choices, |
| }, context_instance=RequestContext(request, extra_context, context_processors)) |
| elif 'post' in request.POST: |
| # If the IP is banned, mail the admins, do NOT save the comment, and |
| # serve up the "Thanks for posting" page as if the comment WAS posted. |
| if request.META['REMOTE_ADDR'] in settings.BANNED_IPS: |
| mail_admins("Banned IP attempted to post comment", smart_unicode(request.POST) + "\n\n" + str(request.META)) |
| else: |
| manipulator.do_html2python(new_data) |
| comment = manipulator.save(new_data) |
| return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id)) |
| else: |
| raise Http404, _("The comment form didn't provide either 'preview' or 'post'") |
| |
| def post_free_comment(request, extra_context=None, context_processors=None): |
| """ |
| Post a free comment (not requiring a log in) |
| |
| Redirects to `comments.comments.comment_was_posted` view on success. |
| |
| Templates: `comment_free_preview` |
| Context: |
| comment |
| comment being posted |
| comment_form |
| comment form object |
| options |
| comment options |
| target |
| comment target |
| hash |
| security hash (must be included in a posted form to succesfully |
| post a comment). |
| """ |
| if extra_context is None: extra_context = {} |
| if not request.POST: |
| raise Http404, _("Only POSTs are allowed") |
| try: |
| options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo'] |
| except KeyError: |
| raise Http404, _("One or more of the required fields wasn't submitted") |
| if Comment.objects.get_security_hash(options, '', '', target) != security_hash: |
| raise Http404, _("Somebody tampered with the comment form (security violation)") |
| content_type_id, object_id = target.split(':') # target is something like '52:5157' |
| content_type = ContentType.objects.get(pk=content_type_id) |
| try: |
| obj = content_type.get_object_for_this_type(pk=object_id) |
| except ObjectDoesNotExist: |
| raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid") |
| option_list = options.split(',') |
| new_data = request.POST.copy() |
| new_data['content_type_id'] = content_type_id |
| new_data['object_id'] = object_id |
| new_data['ip_address'] = request.META['REMOTE_ADDR'] |
| new_data['is_public'] = IS_PUBLIC in option_list |
| manipulator = PublicFreeCommentManipulator() |
| errors = manipulator.get_validation_errors(new_data) |
| if errors or 'preview' in request.POST: |
| comment = errors and '' or manipulator.get_comment(new_data) |
| return render_to_response('comments/free_preview.html', { |
| 'comment': comment, |
| 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors), |
| 'options': options, |
| 'target': target, |
| 'hash': security_hash, |
| }, context_instance=RequestContext(request, extra_context, context_processors)) |
| elif 'post' in request.POST: |
| # If the IP is banned, mail the admins, do NOT save the comment, and |
| # serve up the "Thanks for posting" page as if the comment WAS posted. |
| if request.META['REMOTE_ADDR'] in settings.BANNED_IPS: |
| from django.core.mail import mail_admins |
| mail_admins("Practical joker", smart_unicode(request.POST) + "\n\n" + str(request.META)) |
| else: |
| manipulator.do_html2python(new_data) |
| comment = manipulator.save(new_data) |
| return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id)) |
| else: |
| raise Http404, _("The comment form didn't provide either 'preview' or 'post'") |
| |
| def comment_was_posted(request, extra_context=None, context_processors=None): |
| """ |
| Display "comment was posted" success page |
| |
| Templates: `comment_posted` |
| Context: |
| object |
| The object the comment was posted on |
| """ |
| if extra_context is None: extra_context = {} |
| obj = None |
| if 'c' in request.GET: |
| content_type_id, object_id = request.GET['c'].split(':') |
| try: |
| content_type = ContentType.objects.get(pk=content_type_id) |
| obj = content_type.get_object_for_this_type(pk=object_id) |
| except ObjectDoesNotExist: |
| pass |
| return render_to_response('comments/posted.html', {'object': obj}, |
| context_instance=RequestContext(request, extra_context, context_processors)) |