--- a/app/django/contrib/comments/views/comments.py Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/contrib/comments/views/comments.py Tue Oct 14 16:00:59 2008 +0000
@@ -1,345 +1,121 @@
-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 import http
+from django.conf import settings
+from utils import next_redirect, confirmation_view
from django.core.exceptions import ObjectDoesNotExist
+from django.db import models
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.forms import AuthenticationForm
-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
-import base64, datetime
+from django.template.loader import render_to_string
+from django.utils.html import escape
+from django.views.decorators.http import require_POST
+from django.contrib import comments
+from django.contrib.comments import signals
-COMMENTS_PER_PAGE = 20
+class CommentPostBadRequest(http.HttpResponseBadRequest):
+ """
+ Response returned when a comment post is invalid. If ``DEBUG`` is on a
+ nice-ish error message will be displayed (for debugging purposes), but in
+ production mode a simple opaque 400 page will be displayed.
+ """
+ def __init__(self, why):
+ super(CommentPostBadRequest, self).__init__()
+ if settings.DEBUG:
+ self.content = render_to_string("comments/400-debug.html", {"why": why})
+
+def post_comment(request, next=None):
+ """
+ Post a comment.
+
+ HTTP POST is required. If ``POST['submit'] == "preview"`` or if there are
+ errors a preview template, ``comments/preview.html``, will be rendered.
+ """
+ # Fill out some initial data fields from an authenticated user, if present
+ data = request.POST.copy()
+ if request.user.is_authenticated():
+ if not data.get('name', ''):
+ data["name"] = request.user.get_full_name() or request.user.username
+ if not data.get('email', ''):
+ data["email"] = request.user.email
-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
+ # Look up the object we're trying to comment about
+ ctype = data.get("content_type")
+ object_pk = data.get("object_pk")
+ if ctype is None or object_pk is None:
+ return CommentPostBadRequest("Missing content_type or object_pk field.")
+ try:
+ model = models.get_model(*ctype.split(".", 1))
+ target = model._default_manager.get(pk=object_pk)
+ except TypeError:
+ return CommentPostBadRequest(
+ "Invalid content_type value: %r" % escape(ctype))
+ except AttributeError:
+ return CommentPostBadRequest(
+ "The given content-type %r does not resolve to a valid model." % \
+ escape(ctype))
+ except ObjectDoesNotExist:
+ return CommentPostBadRequest(
+ "No object matching content-type %r and object PK %r exists." % \
+ (escape(ctype), escape(object_pk)))
+
+ # Do we want to preview the comment?
+ preview = "preview" in data
- def hasNoProfanities(self, field_data, all_data):
- if settings.COMMENTS_ALLOW_PROFANITIES:
- return
- return validators.hasNoProfanities(field_data, all_data)
+ # Construct the comment form
+ form = comments.get_form()(target, data=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)
+ # Check security information
+ if form.security_errors():
+ return CommentPostBadRequest(
+ "The comment form failed security verification: %s" % \
+ escape(str(form.security_errors())))
- 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]),
+ # If there are errors or if we requested a preview show the comment
+ if form.errors or preview:
+ template_list = [
+ "comments/%s_%s_preview.html" % tuple(str(model._meta).split(".")),
+ "comments/%s_preview.html" % model._meta.app_label,
+ "comments/preview.html",
+ ]
+ return render_to_response(
+ template_list, {
+ "comment" : form.data.get("comment", ""),
+ "form" : form,
+ },
+ RequestContext(request, {})
)
- 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)
+ # Otherwise create the comment
+ comment = form.get_comment_object()
+ comment.ip_address = request.META.get("REMOTE_ADDR", None)
+ if request.user.is_authenticated():
+ comment.user = request.user
- 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
+ # Signal that the comment is about to be saved
+ responses = signals.comment_will_be_posted.send(
+ sender = comment.__class__,
+ comment = comment,
+ request = request
+ )
-def post_comment(request, extra_context=None, context_processors=None):
- """
- Post a comment
-
- Redirects to the `comments.comments.comment_was_posted` view upon success.
+ for (receiver, response) in responses:
+ if response == False:
+ return CommentPostBadRequest(
+ "comment_will_be_posted receiver %r killed the comment" % receiver.__name__)
- 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'")
+ # Save the comment and signal that it was saved
+ comment.save()
+ signals.comment_was_posted.send(
+ sender = comment.__class__,
+ comment = comment,
+ request = request
+ )
-def post_free_comment(request, extra_context=None, context_processors=None):
- """
- Post a free comment (not requiring a log in)
+ return next_redirect(data, next, comment_done, c=comment._get_pk_val())
- Redirects to `comments.comments.comment_was_posted` view on success.
+post_comment = require_POST(post_comment)
- 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'")
+comment_done = confirmation_view(
+ template = "comments/posted.html",
+ doc = """Display a "comment was posted" success page."""
+)
-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))