changeset 111:e5faf9f0c11a

Forums: implemented the bulk moderator functions that operate on a forum: bulk sticky, lock, delete, and move. These haven't been tested that well yet.
author Brian Neal <bgneal@gmail.com>
date Mon, 28 Sep 2009 03:57:09 +0000
parents c329bfaed4a7
children d1b0b86441c0
files gpp/forums/forms.py gpp/forums/urls.py gpp/forums/views.py gpp/templates/forums/forum_index.html gpp/templates/forums/mod_forum.html media/css/base.css
diffstat 6 files changed, 206 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/forums/forms.py	Sat Sep 26 20:19:45 2009 +0000
+++ b/gpp/forums/forms.py	Mon Sep 28 03:57:09 2009 +0000
@@ -109,14 +109,20 @@
 
 
 class MoveTopicForm(forms.Form):
-   """
-   Form for a moderator to move a topic to a forum.
-   """
-   forums = forms.ModelChoiceField(label='Move to forum', 
-         queryset=Forum.objects.none())
+    """
+    Form for a moderator to move a topic to a forum.
+    """
+    forums = forms.ModelChoiceField(label='Move to forum', 
+          queryset=Forum.objects.none())
 
-   def __init__(self, user, *args, **kwargs):
-      super(MoveTopicForm, self).__init__(*args, **kwargs)
-      self.fields['forums'].queryset = \
-              Forum.objects.forums_for_user(user).order_by('name')
+    def __init__(self, user, *args, **kwargs):
+        hide_label = kwargs.pop('hide_label', False) 
+        required = kwargs.pop('required', True)
+        super(MoveTopicForm, self).__init__(*args, **kwargs)
+        self.fields['forums'].queryset = \
+            Forum.objects.forums_for_user(user).order_by('name')
+        if hide_label:
+            self.fields['forums'].label = ''
+        self.fields['forums'].required = required
+        print '#############', required
 
--- a/gpp/forums/urls.py	Sat Sep 26 20:19:45 2009 +0000
+++ b/gpp/forums/urls.py	Mon Sep 28 03:57:09 2009 +0000
@@ -12,6 +12,7 @@
     url(r'^flag-post/$', 'flag_post', name='forums-flag_post'),
     url(r'^forum/(?P<slug>[\w\d-]+)/$', 'forum_index', name='forums-forum_index'),
     url(r'^forum/(?P<slug>[\w\d-]+)/new-topic/$', 'new_topic', name='forums-new_topic'),
+    url(r'^mod/forum/(?P<slug>[\w\d-]+)/$', 'mod_forum', name='forums-mod_forum'),
     url(r'^mod/topic/delete/(\d+)/$', 'mod_topic_delete', name='forums-mod_topic_delete'),
     url(r'^mod/topic/lock/(\d+)/$', 'mod_topic_lock', name='forums-mod_topic_lock'),
     url(r'^mod/topic/move/(\d+)/$', 'mod_topic_move', name='forums-mod_topic_move'),
--- a/gpp/forums/views.py	Sat Sep 26 20:19:45 2009 +0000
+++ b/gpp/forums/views.py	Mon Sep 28 03:57:09 2009 +0000
@@ -78,11 +78,14 @@
 
     # we do this for the template since it is rendered twice
     page_nav = render_to_string('forums/pagination.html', {'page': page})
+
+    can_moderate = _can_moderate(forum, request.user)
     
     return render_to_response('forums/forum_index.html', {
         'forum': forum,
         'page': page,
         'page_nav': page_nav,
+        'can_moderate': can_moderate,
         },
         context_instance=RequestContext(request))
 
@@ -443,17 +446,7 @@
         if form.is_valid():
             new_forum = form.cleaned_data['forums']
             old_forum = topic.forum
-            if new_forum != old_forum:
-                topic.forum = new_forum
-                topic.save()
-                # Have to adjust foreign keys to last_post, denormalized counts, etc.:
-                old_forum.topic_count_update()
-                old_forum.post_count_update()
-                old_forum.save()
-                new_forum.topic_count_update()
-                new_forum.post_count_update()
-                new_forum.save()
-
+            _move_topic(topic, old_forum, new_forum)
             return HttpResponseRedirect(topic.get_absolute_url())
     else:
         form = MoveTopicForm(request.user)
@@ -466,6 +459,62 @@
         context_instance=RequestContext(request))
 
 
+@login_required
+def mod_forum(request, slug):
+    """
+    Displays a view to allow moderators to perform various operations
+    on topics in a forum in bulk. We currently support mass locking/unlocking,
+    stickying and unstickying, moving, and deleting topics.
+    """
+    forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
+    if not _can_moderate(forum, request.user):
+        return HttpResponseForbidden()
+
+    topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
+    paginator = create_topic_paginator(topics)
+    page_num = int(request.REQUEST.get('page', 1))
+    try:
+        page = paginator.page(page_num)
+    except InvalidPage:
+        raise Http404
+
+    # we do this for the template since it is rendered twice
+    page_nav = render_to_string('forums/pagination.html', {'page': page})
+    form = None
+
+    if request.method == 'POST':
+        topic_ids = request.POST.getlist('topic_ids')
+        url = reverse('forums-mod_forum', kwargs={'slug':forum.slug})
+        url += '?page=%s' % page_num
+
+        if len(topic_ids):
+            if request.POST.get('sticky'):
+                _bulk_sticky(forum, topic_ids)
+                return HttpResponseRedirect(url)
+            elif request.POST.get('lock'):
+                _bulk_lock(forum, topic_ids)
+                return HttpResponseRedirect(url)
+            elif request.POST.get('delete'):
+                _bulk_delete(forum, topic_ids)
+                return HttpResponseRedirect(url)
+            elif request.POST.get('move'):
+                form = MoveTopicForm(request.user, request.POST, hide_label=True)
+                if form.is_valid():
+                    _bulk_move(topic_ids, forum, form.cleaned_data['forums'])
+                    return HttpResponseRedirect(url)
+    
+    if form is None:
+        form = MoveTopicForm(request.user, hide_label=True)
+
+    return render_to_response('forums/mod_forum.html', {
+        'forum': forum,
+        'page': page,
+        'page_nav': page_nav,
+        'form': form,
+        },
+        context_instance=RequestContext(request))
+
+
 def _can_moderate(forum, user):
     """
     Determines if a user has permission to moderate a given forum.
@@ -493,12 +542,68 @@
 
 
 def _quote_message(who, message):
-   """
-   Builds a message reply by quoting the existing message in a
-   typical email-like fashion. The quoting is compatible with Markdown.
-   """
-   header = '*%s wrote:*\n\n' % (who, )
-   lines = wrap(message, 55).split('\n')
-   for i, line in enumerate(lines):
-      lines[i] = '> ' + line
-   return header + '\n'.join(lines)
+    """
+    Builds a message reply by quoting the existing message in a
+    typical email-like fashion. The quoting is compatible with Markdown.
+    """
+    header = '*%s wrote:*\n\n' % (who, )
+    lines = wrap(message, 55).split('\n')
+    for i, line in enumerate(lines):
+        lines[i] = '> ' + line
+    return header + '\n'.join(lines)
+
+
+def _move_topic(topic, old_forum, new_forum):
+    if new_forum != old_forum:
+        topic.forum = new_forum
+        topic.save()
+        # Have to adjust foreign keys to last_post, denormalized counts, etc.:
+        old_forum.topic_count_update()
+        old_forum.post_count_update()
+        old_forum.save()
+        new_forum.topic_count_update()
+        new_forum.post_count_update()
+        new_forum.save()
+
+
+def _bulk_sticky(forum, topic_ids):
+    """
+    Performs a toggle on the sticky status for a given list of topic ids.
+    """
+    topics = Topic.objects.filter(pk__in=topic_ids)
+    for topic in topics:
+        if topic.forum == forum:
+            topic.sticky = not topic.sticky
+            topic.save()
+
+
+def _bulk_lock(forum, topic_ids):
+    """
+    Performs a toggle on the locked status for a given list of topic ids.
+    """
+    topics = Topic.objects.filter(pk__in=topic_ids)
+    for topic in topics:
+        if topic.forum == forum:
+            topic.locked = not topic.locked
+            topic.save()
+
+
+def _bulk_delete(forum, topic_ids):
+    """
+    Deletes the list of topics.
+    """
+    topics = Topic.objects.filter(pk__in=topic_ids).select_related()
+    for topic in topics:
+        if topic.forum == forum:
+            _delete_topic(topic)
+
+
+def _bulk_move(topic_ids, old_forum, new_forum):
+    """
+    Moves the list of topics to a new forum.
+    """
+    topics = Topic.objects.filter(pk__in=topic_ids).select_related()
+    for topic in topics:
+        if topic.forum == old_forum:
+            _move_topic(topic, old_forum, new_forum)
+
--- a/gpp/templates/forums/forum_index.html	Sat Sep 26 20:19:45 2009 +0000
+++ b/gpp/templates/forums/forum_index.html	Mon Sep 28 03:57:09 2009 +0000
@@ -38,7 +38,7 @@
       </tr>
    {% empty %}
       <tr>
-         <td colspan="4">
+         <td colspan="5">
             <i>No topics available.</i>
          </td>
       </tr>
@@ -46,5 +46,6 @@
    </tbody>
 </table>
 {{ page_nav }}
+<p><a href="{% url forums-mod_forum slug=forum.slug %}">Moderate this forum</a></p>
 </div>
 {% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/templates/forums/mod_forum.html	Mon Sep 28 03:57:09 2009 +0000
@@ -0,0 +1,59 @@
+{% extends 'base.html' %}
+{% load forum_tags %}
+{% block title %}Moderate Forum: {{ forum.name }}{% endblock %}
+{% block content %}
+<h2>Moderate Forum: {{ forum.name }}</h2>
+
+<h3>
+   <a href="{% url forums-index %}">SurfGuitar101 Forum Index</a> &raquo;
+   <a href="{% url forums-forum_index slug=forum.slug %}">{{ forum.name }}</a>
+</h3>
+
+<div class="forum-block">
+{{ page_nav }}
+<form action="." method="post">
+<table class="forum-index-table">
+   <thead>
+      <tr>
+         <th class="forum-index_title">Topics</th>
+         <th class="forum-index_replies">Replies</th>
+         <th class="forum-index_author">Author</th>
+         <th class="forum-index_last_post">Last Post</th>
+         <th class="forum-index_select">Select<br /><input type="checkbox" id="forums-master-topic" /></th>
+      </tr>
+   </thead>
+   <tbody>
+   {% for topic in page.object_list %}
+      <tr class="{% cycle 'odd' 'even' %}">
+         <td>{% if topic.sticky %}<img src="{{ MEDIA_URL }}icons/asterisk_orange.png" alt="Sticky" title="Sticky" class="forums-topic-icon" />{% endif %}
+            {% if topic.locked %}<img src="{{ MEDIA_URL }}icons/lock.png" alt="Locked" title="Locked"
+            class="forums-topic-icon" />{% endif %}
+         <h4><a href="{{ topic.get_absolute_url }}">{{ topic.name }}</a></h4></td>
+         <td class="forum-index_replies">{{ topic.reply_count }}</td>
+         <td class="forum-index_author"><a href="{% url bio-view_profile username=topic.user.username %}" title="View profile for {{ topic.user.username }}">{{ topic.user.username }}</a></td>
+         <td class="forum-index_last_post">
+            {% last_post_info topic.last_post MEDIA_URL %}
+         </td>
+         <td class="forum-index_select"><input type="checkbox" name="topic_ids" value="{{ topic.id }}" /></td>
+      </tr>
+   {% empty %}
+      <tr>
+         <td colspan="5">
+            <i>No topics available.</i>
+         </td>
+      </tr>
+   {% endfor %}
+   </tbody>
+</table>
+{{ page_nav }}
+<div class="forum-mod-controls">
+   <input type="submit" value="Toggle Sticky" name="sticky" />
+   <input type="submit" value="Toggle Lock" name="lock" />
+   <input type="submit" value="Delete" name="delete" /><br />
+   <input type="submit" value="Move To:" name="move" />
+   {{ form }}
+   <input type="hidden" name="page" value="{{ page.number }}" />
+</div>
+</form>
+</div>
+{% endblock %}
--- a/media/css/base.css	Sat Sep 26 20:19:45 2009 +0000
+++ b/media/css/base.css	Mon Sep 28 03:57:09 2009 +0000
@@ -194,6 +194,10 @@
    width:20%;
    text-align:center;
 }
+table.forum-index-table .forum-index_select {
+   width:10%;
+   text-align:center;
+}
 table.forum-topic {
    border-top:1px solid black;
    border-left:1px solid black;