diff options
| -rw-r--r-- | django/contrib/comments/admin.py | 41 | ||||
| -rw-r--r-- | django/contrib/comments/templates/comments/moderation_queue.html | 75 | ||||
| -rw-r--r-- | django/contrib/comments/urls.py | 1 | ||||
| -rw-r--r-- | django/contrib/comments/views/moderation.py | 161 | ||||
| -rw-r--r-- | tests/regressiontests/comment_tests/tests/moderation_view_tests.py | 44 | ||||
| -rw-r--r-- | tests/regressiontests/comment_tests/urls_admin.py | 8 |
6 files changed, 125 insertions, 205 deletions
diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py index c2f8e564f4..ede833f530 100644 --- a/django/contrib/comments/admin.py +++ b/django/contrib/comments/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin from django.contrib.comments.models import Comment -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ungettext from django.contrib.comments import get_model +from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete class CommentsAdmin(admin.ModelAdmin): fieldsets = ( @@ -22,6 +23,44 @@ class CommentsAdmin(admin.ModelAdmin): ordering = ('-submit_date',) raw_id_fields = ('user',) search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address') + actions = ["flag_comments", "approve_comments", "remove_comments"] + + def get_actions(self, request): + actions = super(CommentsAdmin, self).get_actions(request) + # Only superusers should be able to delete the comments from the DB. + if not request.user.is_superuser: + actions.pop('delete_selected') + if not request.user.has_perm('comments.can_moderate'): + actions.pop('approve_comments') + actions.pop('remove_comments') + return actions + + def flag_comments(self, request, queryset): + self._bulk_flag(request, queryset, perform_flag, _("flagged")) + flag_comments.short_description = _("Flag selected comments") + + def approve_comments(self, request, queryset): + self._bulk_flag(request, queryset, perform_approve, _('approved')) + approve_comments.short_description = _("Approve selected comments") + + def remove_comments(self, request, queryset): + self._bulk_flag(request, queryset, perform_delete, _('removed')) + remove_comments.short_description = _("Remove selected comments") + + def _bulk_flag(self, request, queryset, action, description): + """ + Flag, approve, or remove some comments from an admin action. Actually + calls the `action` argument to perform the heavy lifting. + """ + n_comments = 0 + for comment in queryset: + action(request, comment) + n_comments += 1 + + msg = ungettext(u'1 comment was successfully %(action)s.', + u'%(count)s comments were successfully %(action)s.', + n_comments) + self.message_user(request, msg % {'count': n_comments, 'action': description}) # Only register the default admin if the model is the built-in comment model # (this won't be true if there's a custom comment app). diff --git a/django/contrib/comments/templates/comments/moderation_queue.html b/django/contrib/comments/templates/comments/moderation_queue.html deleted file mode 100644 index 73012b3539..0000000000 --- a/django/contrib/comments/templates/comments/moderation_queue.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "admin/change_list.html" %} -{% load adminmedia i18n %} - -{% block title %}{% trans "Comment moderation queue" %}{% endblock %} - -{% block extrahead %} - {{ block.super }} - <style type="text/css" media="screen"> - p#nocomments { font-size: 200%; text-align: center; border: 1px #ccc dashed; padding: 4em; } - td.actions { width: 11em; } - td.actions form { display: inline; } - td.actions form input.submit { width: 5em; padding: 2px 4px; margin-right: 4px;} - td.actions form input.approve { background: green; color: white; } - td.actions form input.remove { background: red; color: white; } - </style> -{% endblock %} - -{% block branding %} -<h1 id="site-name">{% trans "Comment moderation queue" %}</h1> -{% endblock %} - -{% block breadcrumbs %}{% endblock %} - -{% block content %} -{% if empty %} -<p id="nocomments">{% trans "No comments to moderate" %}.</p> -{% else %} -<div id="content-main"> - <div class="module" id="changelist"> - <table cellspacing="0"> - <thead> - <tr> - <th>{% trans "Action" %}</th> - <th>{% trans "Name" %}</th> - <th>{% trans "Comment" %}</th> - <th>{% trans "Email" %}</th> - <th>{% trans "URL" %}</th> - <th>{% trans "Authenticated?" %}</th> - <th>{% trans "IP Address" %}</th> - <th class="sorted desc">{% trans "Date posted" %}</th> - </tr> - </thead> - <tbody> - {% for comment in comments %} - <tr class="{% cycle 'row1' 'row2' %}"> - <td class="actions"> - <form action="{% url comments-approve comment.pk %}" method="post"> - <input type="hidden" name="next" value="{% url comments-moderation-queue %}" /> - <input class="approve submit" type="submit" name="submit" value="{% trans "Approve" %}" /> - </form> - <form action="{% url comments-delete comment.pk %}" method="post"> - <input type="hidden" name="next" value="{% url comments-moderation-queue %}" /> - <input class="remove submit" type="submit" name="submit" value="{% trans "Remove" %}" /> - </form> - </td> - <td>{{ comment.name }}</td> - <td>{{ comment.comment|truncatewords:"50" }}</td> - <td>{{ comment.email }}</td> - <td>{{ comment.url }}</td> - <td> - <img - src="{% admin_media_prefix %}img/admin/icon-{% if comment.user %}yes{% else %}no{% endif %}.gif" - alt="{% if comment.user %}{% trans "yes" %}{% else %}{% trans "no" %}{% endif %}" - /> - </td> - <td>{{ comment.ip_address }}</td> - <td>{{ comment.submit_date|date:"F j, P" }}</td> - </tr> - {% endfor %} - </tbody> - </table> - </div> -</div> -{% endif %} -{% endblock %} diff --git a/django/contrib/comments/urls.py b/django/contrib/comments/urls.py index 5caef9c7d4..2bfefa3e2d 100644 --- a/django/contrib/comments/urls.py +++ b/django/contrib/comments/urls.py @@ -7,7 +7,6 @@ urlpatterns = patterns('django.contrib.comments.views', url(r'^flagged/$', 'moderation.flag_done', name='comments-flag-done'), url(r'^delete/(\d+)/$', 'moderation.delete', name='comments-delete'), url(r'^deleted/$', 'moderation.delete_done', name='comments-delete-done'), - url(r'^moderate/$', 'moderation.moderation_queue', name='comments-moderation-queue'), url(r'^approve/(\d+)/$', 'moderation.approve', name='comments-approve'), url(r'^approved/$', 'moderation.approve_done', name='comments-approve-done'), ) diff --git a/django/contrib/comments/views/moderation.py b/django/contrib/comments/views/moderation.py index 3334b0927e..d47fa8b4e7 100644 --- a/django/contrib/comments/views/moderation.py +++ b/django/contrib/comments/views/moderation.py @@ -3,12 +3,10 @@ from django.conf import settings from django.shortcuts import get_object_or_404, render_to_response from django.contrib.auth.decorators import login_required, permission_required from utils import next_redirect, confirmation_view -from django.core.paginator import Paginator, InvalidPage -from django.http import Http404 from django.contrib import comments from django.contrib.comments import signals -#@login_required +@login_required def flag(request, comment_id, next=None): """ Flags a comment. Confirmation on GET, action on POST. @@ -22,18 +20,7 @@ def flag(request, comment_id, next=None): # Flag on POST if request.method == 'POST': - flag, created = comments.models.CommentFlag.objects.get_or_create( - comment = comment, - user = request.user, - flag = comments.models.CommentFlag.SUGGEST_REMOVAL - ) - signals.comment_was_flagged.send( - sender = comment.__class__, - comment = comment, - flag = flag, - created = created, - request = request, - ) + perform_flag(request, comment) return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk) # Render a form on GET @@ -42,9 +29,8 @@ def flag(request, comment_id, next=None): {'comment': comment, "next": next}, template.RequestContext(request) ) -flag = login_required(flag) -#@permission_required("comments.delete_comment") +@permission_required("comments.can_moderate") def delete(request, comment_id, next=None): """ Deletes a comment. Confirmation on GET, action on POST. Requires the "can @@ -60,20 +46,7 @@ def delete(request, comment_id, next=None): # Delete on POST if request.method == 'POST': # Flag the comment as deleted instead of actually deleting it. - flag, created = comments.models.CommentFlag.objects.get_or_create( - comment = comment, - user = request.user, - flag = 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, - ) + perform_delete(request, comment) return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk) # Render a form on GET @@ -82,9 +55,8 @@ def delete(request, comment_id, next=None): {'comment': comment, "next": next}, template.RequestContext(request) ) -delete = permission_required("comments.can_moderate")(delete) -#@permission_required("comments.can_moderate") +@permission_required("comments.can_moderate") def approve(request, comment_id, next=None): """ Approve a comment (that is, mark it as public and non-removed). Confirmation @@ -100,23 +72,7 @@ def approve(request, comment_id, next=None): # Delete on POST if request.method == 'POST': # Flag the comment as approved. - flag, created = comments.models.CommentFlag.objects.get_or_create( - comment = comment, - user = request.user, - flag = 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, - ) + perform_approve(request, comment) return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk) # Render a form on GET @@ -126,69 +82,64 @@ def approve(request, comment_id, next=None): template.RequestContext(request) ) -approve = permission_required("comments.can_moderate")(approve) - +# The following functions actually perform the various flag/aprove/delete +# actions. They've been broken out into seperate functions to that they +# may be called from admin actions. -#@permission_required("comments.can_moderate") -def moderation_queue(request): +def perform_flag(request, comment): """ - Displays a list of unapproved comments to be approved. + Actually perform the flagging of a comment from a request. + """ + flag, created = comments.models.CommentFlag.objects.get_or_create( + comment = comment, + user = request.user, + flag = comments.models.CommentFlag.SUGGEST_REMOVAL + ) + signals.comment_was_flagged.send( + sender = comment.__class__, + comment = comment, + flag = flag, + created = created, + request = request, + ) - Templates: `comments/moderation_queue.html` - Context: - comments - Comments to be approved (paginated). - empty - Is the comment list empty? - is_paginated - Is there more than one page? - results_per_page - Number of comments per page - has_next - Is there a next page? - has_previous - Is there a previous page? - page - The current page number - next - The next page number - pages - Number of pages - hits - Total number of comments - page_range - Range of page numbers +def perform_delete(request, comment): + flag, created = comments.models.CommentFlag.objects.get_or_create( + comment = comment, + user = request.user, + flag = 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, + ) - """ - qs = comments.get_model().objects.filter(is_public=False, is_removed=False) - paginator = Paginator(qs, 100) - try: - page = int(request.GET.get("page", 1)) - except ValueError: - raise Http404 +def perform_approve(request, comment): + flag, created = comments.models.CommentFlag.objects.get_or_create( + comment = comment, + user = request.user, + flag = comments.models.CommentFlag.MODERATOR_APPROVAL, + ) - try: - comments_per_page = paginator.page(page) - except InvalidPage: - raise Http404 + comment.is_removed = False + comment.is_public = True + comment.save() - return render_to_response("comments/moderation_queue.html", { - 'comments' : comments_per_page.object_list, - 'empty' : page == 1 and paginator.count == 0, - 'is_paginated': paginator.num_pages > 1, - 'results_per_page': 100, - 'has_next': comments_per_page.has_next(), - 'has_previous': comments_per_page.has_previous(), - 'page': page, - 'next': page + 1, - 'previous': page - 1, - 'pages': paginator.num_pages, - 'hits' : paginator.count, - 'page_range' : paginator.page_range - }, context_instance=template.RequestContext(request)) + signals.comment_was_flagged.send( + sender = comment.__class__, + comment = comment, + flag = flag, + created = created, + request = request, + ) -moderation_queue = permission_required("comments.can_moderate")(moderation_queue) +# Confirmation views. flag_done = confirmation_view( template = "comments/flagged.html", diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py index b9eadd78b4..61e90bc220 100644 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py @@ -159,31 +159,29 @@ class ApproveViewTests(CommentTestCase): response = self.client.get("/approved/", data={"c":pk}) self.assertTemplateUsed(response, "comments/approved.html") +class AdminActionsTests(CommentTestCase): + urls = "regressiontests.comment_tests.urls_admin" + + def setUp(self): + super(AdminActionsTests, self).setUp() + + # Make "normaluser" a moderator + u = User.objects.get(username="normaluser") + u.is_staff = True + u.user_permissions.add(Permission.objects.get(codename='add_comment')) + u.user_permissions.add(Permission.objects.get(codename='change_comment')) + u.user_permissions.add(Permission.objects.get(codename='delete_comment')) + u.save() -class ModerationQueueTests(CommentTestCase): - - def testModerationQueuePermissions(self): - """Only moderators can view the moderation queue""" + def testActionsNonModerator(self): + comments = self.createSomeComments() self.client.login(username="normaluser", password="normaluser") - response = self.client.get("/moderate/") - self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/moderate/") - - makeModerator("normaluser") - response = self.client.get("/moderate/") - self.assertEqual(response.status_code, 200) + response = self.client.get("/admin/comments/comment/") + self.assertEquals("approve_comments" in response.content, False) - def testModerationQueueContents(self): - """Moderation queue should display non-public, non-removed comments.""" - c1, c2, c3, c4 = self.createSomeComments() + def testActionsModerator(self): + comments = self.createSomeComments() makeModerator("normaluser") self.client.login(username="normaluser", password="normaluser") - - c1.is_public = c2.is_public = False - c1.save(); c2.save() - response = self.client.get("/moderate/") - self.assertEqual(list(response.context[0]["comments"]), [c1, c2]) - - c2.is_removed = True - c2.save() - response = self.client.get("/moderate/") - self.assertEqual(list(response.context[0]["comments"]), [c1]) + response = self.client.get("/admin/comments/comment/") + self.assertEquals("approve_comments" in response.content, True) diff --git a/tests/regressiontests/comment_tests/urls_admin.py b/tests/regressiontests/comment_tests/urls_admin.py new file mode 100644 index 0000000000..9e43d34865 --- /dev/null +++ b/tests/regressiontests/comment_tests/urls_admin.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import * +from django.contrib import admin + +admin.autodiscover() + +urlpatterns = patterns('', + (r'^admin/', include(admin.site.urls)), +) |
