# HG changeset patch # User Brian Neal # Date 1252984520 0 # Node ID eb9f9938247604441decbdfc42b46db87e188562 # Parent 10d6182b9f6e06cb27a617d780d887be4f084ae8 Forums: groups support. Some experimentation with select_related() to reduce queries. There are more opportunities for this, see the TODO comments in views.py. diff -r 10d6182b9f6e -r eb9f99382476 gpp/forums/forms.py --- 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 diff -r 10d6182b9f6e -r eb9f99382476 gpp/forums/models.py --- 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) diff -r 10d6182b9f6e -r eb9f99382476 gpp/forums/views.py --- 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,