diff options
Diffstat (limited to 'app/django_comments/views')
-rw-r--r-- | app/django_comments/views/__init__.py | 0 | ||||
-rw-r--r-- | app/django_comments/views/comments.py | 140 | ||||
-rw-r--r-- | app/django_comments/views/moderation.py | 166 | ||||
-rw-r--r-- | app/django_comments/views/utils.py | 71 |
4 files changed, 377 insertions, 0 deletions
diff --git a/app/django_comments/views/__init__.py b/app/django_comments/views/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/django_comments/views/__init__.py diff --git a/app/django_comments/views/comments.py b/app/django_comments/views/comments.py new file mode 100644 index 0000000..40dfc60 --- /dev/null +++ b/app/django_comments/views/comments.py @@ -0,0 +1,140 @@ +from __future__ import absolute_import + +from django import http +from django.apps import apps +from django.conf import settings +from django.contrib.sites.shortcuts import get_current_site +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.shortcuts import render +from django.template.loader import render_to_string +from django.utils.html import escape +from django.views.decorators.csrf import csrf_protect, csrf_exempt +from django.views.decorators.http import require_POST + +import django_comments +from django_comments import signals +from django_comments.views.utils import next_redirect, confirmation_view + + +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}) + + +@csrf_exempt +@require_POST +def post_comment(request, next=None, using=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() + try: + user_is_authenticated = request.user.is_authenticated() + except TypeError: # Django >= 1.11 + user_is_authenticated = request.user.is_authenticated + if user_is_authenticated: + if not data.get('name', ''): + data["name"] = request.user.get_full_name() or request.user.get_username() + if not data.get('email', ''): + data["email"] = request.user.email + + # 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 = apps.get_model(*ctype.split(".", 1)) + target = model._default_manager.using(using).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))) + except (ValueError, ValidationError) as e: + return CommentPostBadRequest( + "Attempting go get content-type %r and object PK %r exists raised %s" % ( + escape(ctype), escape(object_pk), e.__class__.__name__)) + + # Do we want to preview the comment? + preview = "preview" in data + + # Construct the comment form + form = django_comments.get_form()(target, data=data) + + # Check security information + if form.security_errors(): + return CommentPostBadRequest( + "The comment form failed security verification: %s" % escape(str(form.security_errors()))) + + # If there are errors or if we requested a preview show the comment + if form.errors or preview: + template_list = [ + # These first two exist for purely historical reasons. + # Django v1.0 and v1.1 allowed the underscore format for + # preview templates, so we have to preserve that format. + "comments/%s_%s_preview.html" % (model._meta.app_label, model._meta.model_name), + "comments/%s_preview.html" % model._meta.app_label, + # Now the usual directory based template hierarchy. + "comments/%s/%s/preview.html" % (model._meta.app_label, model._meta.model_name), + "comments/%s/preview.html" % model._meta.app_label, + "comments/preview.html", + ] + return render(request, template_list, { + "comment": form.data.get("comment", ""), + "form": form, + "next": data.get("next", next), + }, + ) + + # Otherwise create the comment + comment = form.get_comment_object(site_id=get_current_site(request).id) + comment.ip_address = request.META.get("REMOTE_ADDR", None) or None + if user_is_authenticated: + comment.user = request.user + + # Signal that the comment is about to be saved + responses = signals.comment_will_be_posted.send( + sender=comment.__class__, + comment=comment, + request=request + ) + + for (receiver, response) in responses: + if response is False: + return CommentPostBadRequest( + "comment_will_be_posted receiver %r killed the comment" % receiver.__name__) + + # Save the comment and signal that it was saved + comment.save() + signals.comment_was_posted.send( + sender=comment.__class__, + comment=comment, + request=request + ) + + return next_redirect(request, fallback=next or 'comments-comment-done', + c=comment._get_pk_val()) + + +comment_done = confirmation_view( + template="comments/posted.html", + doc="""Display a "comment was posted" success page.""" +) diff --git a/app/django_comments/views/moderation.py b/app/django_comments/views/moderation.py new file mode 100644 index 0000000..04c665f --- /dev/null +++ b/app/django_comments/views/moderation.py @@ -0,0 +1,166 @@ +from __future__ import absolute_import + +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.sites.shortcuts import get_current_site +from django.shortcuts import get_object_or_404, render +from django.views.decorators.csrf import csrf_protect + +import django_comments +from django_comments import signals +from django_comments.views.utils import next_redirect, confirmation_view + + +@csrf_protect +@login_required +def flag(request, comment_id, next=None): + """ + Flags a comment. Confirmation on GET, action on POST. + + Templates: :template:`comments/flag.html`, + Context: + comment + the flagged `comments.comment` object + """ + comment = get_object_or_404(django_comments.get_model(), + pk=comment_id, + site__pk=get_current_site(request).pk) + + # Flag on POST + if request.method == 'POST': + perform_flag(request, comment) + return next_redirect(request, fallback=next or 'comments-flag-done', + c=comment.pk) + + # Render a form on GET + else: + return render(request, 'comments/flag.html', {'comment': comment, "next": next}) + + +@csrf_protect +@permission_required("django_comments.can_moderate") +def delete(request, comment_id, next=None): + """ + Deletes a comment. Confirmation on GET, action on POST. Requires the "can + moderate comments" permission. + + Templates: :template:`comments/delete.html`, + Context: + comment + the flagged `comments.comment` object + """ + comment = get_object_or_404(django_comments.get_model(), + pk=comment_id, + site__pk=get_current_site(request).pk) + + # Delete on POST + if request.method == 'POST': + # Flag the comment as deleted instead of actually deleting it. + perform_delete(request, comment) + return next_redirect(request, fallback=next or 'comments-delete-done', + c=comment.pk) + + # Render a form on GET + else: + return render(request, 'comments/delete.html', {'comment': comment, "next": next}) + + +@csrf_protect +@permission_required("django_comments.can_moderate") +def approve(request, comment_id, next=None): + """ + Approve a comment (that is, mark it as public and non-removed). Confirmation + on GET, action on POST. Requires the "can moderate comments" permission. + + Templates: :template:`comments/approve.html`, + Context: + comment + the `comments.comment` object for approval + """ + comment = get_object_or_404(django_comments.get_model(), + pk=comment_id, + site__pk=get_current_site(request).pk) + + # Delete on POST + if request.method == 'POST': + # Flag the comment as approved. + perform_approve(request, comment) + return next_redirect(request, fallback=next or 'comments-approve-done', + c=comment.pk) + + # Render a form on GET + else: + return render(request, 'comments/approve.html', {'comment': comment, "next": next}) + + +# The following functions actually perform the various flag/aprove/delete +# actions. They've been broken out into separate functions to that they +# may be called from admin actions. + +def perform_flag(request, comment): + """ + Actually perform the flagging of a comment from a request. + """ + flag, created = django_comments.models.CommentFlag.objects.get_or_create( + comment=comment, + user=request.user, + flag=django_comments.models.CommentFlag.SUGGEST_REMOVAL + ) + signals.comment_was_flagged.send( + sender=comment.__class__, + comment=comment, + flag=flag, + created=created, + request=request, + ) + + +def perform_delete(request, comment): + flag, created = django_comments.models.CommentFlag.objects.get_or_create( + comment=comment, + user=request.user, + flag=django_comments.models.CommentFlag.MODERATOR_DELETION + ) + comment.is_removed = True + comment.save() + signals.comment_was_flagged.send( + sender=comment.__class__, + comment=comment, + flag=flag, + created=created, + request=request, + ) + + +def perform_approve(request, comment): + flag, created = django_comments.models.CommentFlag.objects.get_or_create( + comment=comment, + user=request.user, + flag=django_comments.models.CommentFlag.MODERATOR_APPROVAL, + ) + + comment.is_removed = False + comment.is_public = True + comment.save() + + signals.comment_was_flagged.send( + sender=comment.__class__, + comment=comment, + flag=flag, + created=created, + request=request, + ) + +# Confirmation views. + +flag_done = confirmation_view( + template="comments/flagged.html", + doc='Displays a "comment was flagged" success page.' +) +delete_done = confirmation_view( + template="comments/deleted.html", + doc='Displays a "comment was deleted" success page.' +) +approve_done = confirmation_view( + template="comments/approved.html", + doc='Displays a "comment was approved" success page.' +) diff --git a/app/django_comments/views/utils.py b/app/django_comments/views/utils.py new file mode 100644 index 0000000..a5f5c11 --- /dev/null +++ b/app/django_comments/views/utils.py @@ -0,0 +1,71 @@ +""" +A few bits of helper functions for comment views. +""" + +import textwrap + +try: + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode + +from django.http import HttpResponseRedirect +from django.shortcuts import render, resolve_url +from django.core.exceptions import ObjectDoesNotExist +from django.utils.http import is_safe_url + +import django_comments + + +def next_redirect(request, fallback, **get_kwargs): + """ + Handle the "where should I go next?" part of comment views. + + The next value could be a + ``?next=...`` GET arg or the URL of a given view (``fallback``). See + the view modules for examples. + + Returns an ``HttpResponseRedirect``. + """ + next = request.POST.get('next') + if not is_safe_url(url=next, allowed_hosts={request.get_host()}): + next = resolve_url(fallback) + + if get_kwargs: + if '#' in next: + tmp = next.rsplit('#', 1) + next = tmp[0] + anchor = '#' + tmp[1] + else: + anchor = '' + + joiner = ('?' in next) and '&' or '?' + next += joiner + urlencode(get_kwargs) + anchor + return HttpResponseRedirect(next) + + +def confirmation_view(template, doc="Display a confirmation view."): + """ + Confirmation view generator for the "comment was + posted/flagged/deleted/approved" views. + """ + + def confirmed(request): + comment = None + if 'c' in request.GET: + try: + comment = django_comments.get_model().objects.get(pk=request.GET['c']) + except (ObjectDoesNotExist, ValueError): + pass + return render(request, template, {'comment': comment}) + + confirmed.__doc__ = textwrap.dedent("""\ + %s + + Templates: :template:`%s`` + Context: + comment + The posted comment + """ % (doc, template) + ) + return confirmed |