app/django/contrib/comments/views/comments.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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))