changeset 100:eb9f99382476

Forums: groups support. Some experimentation with select_related() to reduce queries. There are more opportunities for this, see the TODO comments in views.py.
author Brian Neal <bgneal@gmail.com>
date Tue, 15 Sep 2009 03:15:20 +0000 (2009-09-15)
parents 10d6182b9f6e
children 4bbb6a9aa317
files gpp/forums/forms.py gpp/forums/models.py gpp/forums/views.py
diffstat 3 files changed, 79 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/forums/forms.py	Mon Sep 14 00:06:08 2009 +0000
+++ b/gpp/forums/forms.py	Tue Sep 15 03:15:20 2009 +0000
@@ -32,10 +32,8 @@
 
     def clean_topic_id(self):
         id = self.cleaned_data['topic_id']
-        print '*********', id
         try:
             self.topic = Topic.objects.get(pk=id)
-            print '******** Got a topic'
         except Topic.DoesNotExist:
             raise forms.ValidationError('invalid topic')
         return id 
--- a/gpp/forums/models.py	Mon Sep 14 00:06:08 2009 +0000
+++ b/gpp/forums/models.py	Tue Sep 15 03:15:20 2009 +0000
@@ -2,11 +2,15 @@
 Models for the forums application.
 """
 from django.db import models
+from django.db.models import Q
 from django.contrib.auth.models import User, Group
 from django.template.loader import render_to_string
 
 
 class Category(models.Model):
+    """
+    Forums belong to a category, whose access may be assigned to groups.
+    """
     name = models.CharField(max_length=80)
     slug = models.SlugField(max_length=80)
     position = models.IntegerField(blank=True, default=0)
@@ -21,8 +25,49 @@
     def __unicode__(self):
         return self.name
 
+    def can_access(self, user):
+        """
+        Checks to see if the given user has permission to access
+        this category.
+        If this category has no groups assigned to it, return true.
+        Else, return true if the user belongs to a group that has been
+        assigned to this category, and false otherwise.
+        """
+        if self.groups.count() == 0:
+            return True
+        if user.is_authenticated():
+            return self.groups.filter(user__pk=user.id).count() > 0
+        return False
+
+
+class ForumManager(models.Manager):
+    """
+    The manager for the Forum model. Provides a centralized place to
+    put commonly used and useful queries.
+    """
+
+    def forums_for_user(self, user):
+        """
+        Returns a queryset containing the forums that the given user can
+        "see" due to authenticated status, superuser status and group membership.
+        """
+        if user.is_superuser:
+            qs = self.all()
+        else:
+            user_groups = []
+            if user.is_authenticated():
+                user_groups = user.groups.all()
+
+            qs = self.filter(Q(category__groups__isnull=True) | \
+                    Q(category__groups__in=user_groups))
+
+        return qs.select_related('category', 'last_post', 'last_post__user')
+
 
 class Forum(models.Model):
+    """
+    A forum is a collection of topics.
+    """
     category = models.ForeignKey(Category, related_name='forums')
     name = models.CharField(max_length=80)
     slug = models.SlugField(max_length=80)
@@ -36,6 +81,8 @@
     last_post = models.OneToOneField('Post', blank=True, null=True,
         related_name='parent_forum')
 
+    objects = ForumManager()
+
     class Meta:
         ordering = ('position', )
 
@@ -61,6 +108,9 @@
 
 
 class Topic(models.Model):
+    """
+    A topic is a thread of discussion, consisting of a series of posts.
+    """
     forum = models.ForeignKey(Forum, related_name='topics')
     name = models.CharField(max_length=255)
     creation_date = models.DateTimeField(auto_now_add=True)
@@ -110,6 +160,9 @@
 
 
 class Post(models.Model):
+    """
+    A post is an instance of a user's single contribution to a topic.
+    """
     topic = models.ForeignKey(Topic, related_name='posts')
     user = models.ForeignKey(User, related_name='posts')
     creation_date = models.DateTimeField(auto_now_add=True)
--- a/gpp/forums/views.py	Mon Sep 14 00:06:08 2009 +0000
+++ b/gpp/forums/views.py	Tue Sep 15 03:15:20 2009 +0000
@@ -41,7 +41,7 @@
     """
     This view displays all the forums available, ordered in each category.
     """
-    forums = Forum.objects.all().select_related()
+    forums = Forum.objects.forums_for_user(request.user)
     cats = {}
     for forum in forums:
         cat = cats.setdefault(forum.category.id, {
@@ -63,8 +63,13 @@
     """
     Displays all the topics in a forum.
     """
+    # TODO: use select_related to save queries
     forum = get_object_or_404(Forum, slug=slug)
-    topics = forum.topics.select_related()
+
+    if not forum.category.can_access(request.user):
+        return HttpResponseForbidden()
+
+    topics = forum.topics.select_related('last_post', 'last_post__user')
     paginator = create_topic_paginator(topics)
     page_num = int(request.GET.get('page', 1))
     try:
@@ -87,7 +92,18 @@
     """
     Displays all the posts in a topic.
     """
-    topic = get_object_or_404(Topic, pk=id)
+    #topic = get_object_or_404(Topic, pk=id)
+    #TODO: optimize this or package it up somehow
+    # this saves 2 queries vs the get_object_or_404
+    qs = Topic.objects.filter(pk=id).select_related()
+    try:
+        topic = qs[0]
+    except Topic.DoesNotExist:
+        raise Http404
+
+    if not topic.forum.category.can_access(request.user):
+        return HttpResponseForbidden()
+
     topic.view_count += 1
     topic.save()
 
@@ -121,6 +137,10 @@
     This view handles the creation of new topics.
     """
     forum = get_object_or_404(Forum, slug=slug)
+
+    if not forum.category.can_access(request.user):
+        return HttpResponseForbidden()
+
     if request.method == 'POST':
         form = NewTopicForm(request.POST)
         if form.is_valid():
@@ -163,6 +183,9 @@
 
     form = PostForm(request.POST)
     if form.is_valid():
+        if not form.topic.forum.category.can_access(request.user):
+            return HttpResponseForbidden()
+
         post = form.save(request.user, request.META.get("REMOTE_ADDR"))
         return render_to_response('forums/display_post.html', {
             'post': post,