changeset 107:e94398f5e027

Forums: implemented post delete feature.
author Brian Neal <bgneal@gmail.com>
date Tue, 22 Sep 2009 03:36:39 +0000
parents cb72577785df
children 80ab249d1adc
files gpp/forums/models.py gpp/forums/urls.py gpp/forums/views.py gpp/templates/forums/display_post.html gpp/templates/forums/topic.html media/js/forums.js
diffstat 6 files changed, 130 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/forums/models.py	Sat Sep 19 21:49:56 2009 +0000
+++ b/gpp/forums/models.py	Tue Sep 22 03:36:39 2009 +0000
@@ -108,6 +108,18 @@
         else:
             self.last_post = None
 
+    def last_post_pre_delete(self):
+        """
+        Call this function prior to deleting the last post in the forum.
+        A new last post will be found, if one exists.
+        This is to avoid the Django cascading delete issue.
+        """
+        try:
+            self.last_post = \
+                Post.objects.filter(topic__forum=self).exclude(pk=self.last_post.pk).latest()
+        except Post.DoesNotExist:
+            self.last_post = None
+
 
 class Topic(models.Model):
     """
@@ -168,6 +180,18 @@
 
         super(Topic, self).save(*args, **kwargs)
 
+    def last_post_pre_delete(self):
+        """
+        Call this function prior to deleting the last post in the topic.
+        A new last post will be found, if one exists.
+        This is to avoid the Django cascading delete issue.
+        """
+        try:
+            self.last_post = \
+                Post.objects.filter(topic=self).exclude(pk=self.last_post.pk).latest()
+        except Post.DoesNotExist:
+            self.last_post = None
+
 
 class Post(models.Model):
     """
@@ -183,6 +207,7 @@
 
     class Meta:
         ordering = ('creation_date', )
+        get_latest_by = 'creation_date'
 
     @models.permalink
     def get_absolute_url(self):
--- a/gpp/forums/urls.py	Sat Sep 19 21:49:56 2009 +0000
+++ b/gpp/forums/urls.py	Tue Sep 22 03:36:39 2009 +0000
@@ -7,6 +7,7 @@
     url(r'^$', 'index', name='forums-index'),
     url(r'^new-topic-success/(?P<tid>\d+)$', 'new_topic_thanks', name='forums-new_topic_thanks'),
     url(r'^topic/(?P<id>\d+)/$', 'topic_index', name='forums-topic_index'),
+    url(r'^delete-post/$', 'delete_post', name='forums-delete_post'),
     url(r'^edit/(?P<id>\d+)/$', 'edit_post', name='forums-edit_post'),
     url(r'^flag-post/$', 'flag_post', name='forums-flag_post'),
     url(r'^forum/(?P<slug>[\w\d-]+)/$', 'forum_index', name='forums-forum_index'),
--- a/gpp/forums/views.py	Sat Sep 19 21:49:56 2009 +0000
+++ b/gpp/forums/views.py	Tue Sep 22 03:36:39 2009 +0000
@@ -67,7 +67,7 @@
     if not forum.category.can_access(request.user):
         return HttpResponseForbidden()
 
-    topics = forum.topics.select_related('last_post', 'last_post__user')
+    topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
     paginator = create_topic_paginator(topics)
     page_num = int(request.GET.get('page', 1))
     try:
@@ -267,3 +267,79 @@
         'can_moderate': True,
         },
         context_instance=RequestContext(request))
+
+
+@require_POST
+def delete_post(request):
+    """
+    This view function allows superusers and forum moderators to delete posts.
+    This function is the target of AJAX calls from the client.
+    """
+    if not request.user.is_authenticated():
+        return HttpResponseForbidden('Please login to delete a post.')
+
+    id = request.POST.get('id')
+    if id is None:
+        return HttpResponseBadRequest('No post id')
+
+    post = get_object_or_404(Post.objects.select_related(), pk=id)
+
+    can_delete = request.user.is_superuser or \
+            request.user in post.topic.forum.moderators.all()
+
+    if not can_delete:
+        return HttpResponseForbidden("You don't have permission to delete that post.")
+
+    if post.topic.post_count == 1 and post == post.topic.last_post:
+        _delete_topic(post.topic)
+    else:
+        _delete_post(post)
+
+    return HttpResponse("The post has been deleted.")
+
+
+def _delete_post(post):
+    """
+    Internal function to delete a single post object.
+    Decrements the post author's post count.
+    Adjusts the parent topic and forum's last_post as needed.
+    """
+    # Adjust post creator's post count
+    profile = post.user.get_profile()
+    if profile.forum_post_count > 0:
+        profile.forum_post_count -= 1
+        profile.save()
+
+    # If this post is the last_post in a topic, we need to update
+    # both the topic and parent forum's last post fields. If we don't
+    # the cascading delete will delete them also!
+
+    topic = post.topic
+    if topic.last_post == post:
+        topic.last_post_pre_delete()
+        topic.save()
+
+    forum = topic.forum
+    if forum.last_post == post:
+        forum.last_post_pre_delete()
+        forum.save()
+
+    # Should be safe to delete the post now:
+    post.delete()
+
+
+def _delete_topic(topic):
+    """
+    Internal function to delete an entire topic.
+    Deletes the topic and all posts contained within.
+    Adjusts the parent forum's last_post as needed.
+    Note that we don't bother adjusting all the users'
+    post counts as that doesn't seem to be worth the effort.
+    """
+    if topic.forum.last_post.topic == topic:
+        topic.forum.last_post_pre_delete()
+        topic.forum.save()
+
+    # It should be safe to just delete the topic now. This will
+    # automatically delete all posts in the topic.
+    topic.delete()
--- a/gpp/templates/forums/display_post.html	Sat Sep 19 21:49:56 2009 +0000
+++ b/gpp/templates/forums/display_post.html	Tue Sep 22 03:36:39 2009 +0000
@@ -1,6 +1,6 @@
 {% load avatar_tags %}
 {% load forum_tags %}
-<tr class="forum-post {% cycle 'odd' 'even' %}">
+<tr class="forum-post {% cycle 'odd' 'even' %}" id="post-{{ post.id }}">
    <td class="forum-post-author">
       <a name="p{{ post.id }}"></a>
       <a href="{% url bio-view_profile username=post.user.username %}" title="View Profile for {{ post.user.username }}">{{ post.user.username }}</a><br />
@@ -28,7 +28,8 @@
          title="Flag this post as spam, abuse, or a violation of site rules.">
          <img src="{{ MEDIA_URL }}icons/flag_red.png" alt="Flag" /></a>
       {% if can_moderate %}
-      <a href=""><img src="{{ MEDIA_URL }}icons/cross.png" alt="Delete post" title="Delete post" /></a>
+      <a href="#" class="post-delete" id="dp-{{ post.id }}"
+         title="Delete this post"><img src="{{ MEDIA_URL }}icons/cross.png" alt="Delete post" /></a>
       {% endif %}
       </div>
    </td>
--- a/gpp/templates/forums/topic.html	Sat Sep 19 21:49:56 2009 +0000
+++ b/gpp/templates/forums/topic.html	Tue Sep 22 03:36:39 2009 +0000
@@ -1,6 +1,6 @@
 {% extends 'base.html' %}
 {% block title %}Forums: {{ topic.name }}{% endblock %}
-{% block custom_js %}{% if last_page %}{{ form.media }}{% endif %}{% endblock %}
+{% block custom_js %}{{ form.media }}{% endblock %}
 {% block content %}
 <h2>Forums: {{ topic.name }}</h2>
 
--- a/media/js/forums.js	Sat Sep 19 21:49:56 2009 +0000
+++ b/media/js/forums.js	Tue Sep 22 03:36:39 2009 +0000
@@ -33,7 +33,7 @@
    });
    $('a.post-flag').click(function () {
       var id = this.id;
-      if (id.match(/fp-(\d)/)) {
+      if (id.match(/fp-(\d+)/)) {
          id = RegExp.$1;
          if (confirm('Only flag a post if you feel it is spam, abuse, violates site rules, ' +
                  'or is not appropriate. ' +
@@ -55,5 +55,27 @@
      }
      return false;
    });
+   $('a.post-delete').click(function () {
+      var id = this.id;
+      if (id.match(/dp-(\d+)/)) {
+         id = RegExp.$1;
+         if (confirm('Are you sure you want to delete this post?')) {
+             $.ajax({
+               url: '/forums/delete-post/',
+               type: 'POST',
+               data: {id: id}, 
+               dataType: 'text',
+               success: function (response, textStatus) {
+                  alert(response);
+                  $('#post-' + id).fadeOut(3000);
+               },
+               error: function (xhr, textStatus, ex) {
+                  alert('Oops, an error occurred: ' + xhr.statusText + ' - ' + xhr.responseText);
+               }
+             });
+         }
+     }
+     return false;
+   });
    $('#id_body').markItUp(mySettings);
 });