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 }}
-
-{% endblock %}
-
-{% block branding %}
-
{% trans "Comment moderation queue" %}
-{% endblock %}
-
-{% block breadcrumbs %}{% endblock %}
-
-{% block content %}
-{% if empty %}
-
-{% else %}
-
-
-
-
-
- {% trans "Action" %} |
- {% trans "Name" %} |
- {% trans "Comment" %} |
- {% trans "Email" %} |
- {% trans "URL" %} |
- {% trans "Authenticated?" %} |
- {% trans "IP Address" %} |
- {% trans "Date posted" %} |
-
-
-
- {% for comment in comments %}
-
-
-
-
- |
- {{ comment.name }} |
- {{ comment.comment|truncatewords:"50" }} |
- {{ comment.email }} |
- {{ comment.url }} |
-
-
- |
- {{ comment.ip_address }} |
- {{ comment.submit_date|date:"F j, P" }} |
-
- {% endfor %}
-
-
-
-
-{% 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.
-
- 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
-
+ Actually perform the flagging of a comment from a request.
"""
- qs = comments.get_model().objects.filter(is_public=False, is_removed=False)
- paginator = Paginator(qs, 100)
+ 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,
+ )
- try:
- page = int(request.GET.get("page", 1))
- except ValueError:
- raise Http404
+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,
+ )
- try:
- comments_per_page = paginator.page(page)
- except InvalidPage:
- raise Http404
- 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))
+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,
+ )
-moderation_queue = permission_required("comments.can_moderate")(moderation_queue)
+ 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",
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/")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertEquals("approve_comments" in response.content, False)
- makeModerator("normaluser")
- response = self.client.get("/moderate/")
- self.assertEqual(response.status_code, 200)
-
- 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)),
+)