Group support¶
Group support in Pinax allows you to define any type of group. Pinax comes bundled with two types of groups:
- tribes — used in social_project
- projects — used in code_project
A group app can have any content object associated with it. Pinax includes several apps that are group aware:
- tasks
- photos
- wiki
- topics
The idea is a group aware app has the ability to work with or without a group association. This is done using a nullable generic foreign key. Pinax comes with an app to do much of the work to make this all happen.
Writing your own group aware domain objects¶
If you want to write your own domain object that is group aware start an app for it. Follow the guidelines below and you’ll be all set.
Models¶
Let’s look at a basic model that stores data for a blog:
class Blog(models.Model):
name = models.CharField(max_length=140)
To enable group support for this minimal domain object add in a nullable generic foreign key:
from django.db import models
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
class Blog(models.Model):
name = models.CharField(max_length=140)
object_id = models.IntegerField(null=True)
content_type = models.ForeignKey(ContentType, null=True)
group = generic.GenericForeignKey("object_id", "content_type")
We use a nullable generic foreign key to enable it to be optional and we don’t know what group model it will point to.
Views¶
The views you write for your app need to be aware there may be a group association. This is to ensure you properly work with the right subset of data from your models.
Let’s take a look at a view that would be a bit naive:
def blog_list(request):
blogs = Blog.objects.all()
return render_to_response("blog/blog_list.html", {
"blogs": blogs,
}, context_instance=RequestContext(request))
Assuming Blog
is the first model presented above this will work fine.
However, once you introduce the generic foriegn key you will potentially be
selecting objects that don’t belong.
To deal with situation we introduced a ContentBridge
object. This object
is passed to your view from the layer above. Let’s see how to work with it:
from django.http import Http404
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.core.exceptions import ObjectDoesNotExist
def blog_list(request, group_slug=None, bridge=None):
if bridge is not None:
try:
group = bridge.get_group(group_slug)
except ObjectDoesNotExist:
raise Http404
else:
group = None
if group:
blogs = group.content_objects(Blog)
else:
blogs = Blog.objects.all()
return render_to_response("blog/blog_list.html", {
"group": group,
"blogs": blogs,
}, context_instance=RequestContext(request))
Pretty straight-foward code to handle both group and no group association. If you are writing an app that can guarantee group association you can definitely make it simpler.
Checking for user membership¶
In many cases you might want to check if the authenticated user has membership in the group. To do this:
if not request.user.is_authenticated():
is_member = False
else:
is_member = group.user_is_member(request.user)
URLs¶
The urls.py
file of your app will not need anything special. Most of that
is handled by Pinax. However, URL reversal needs to be group aware. We have
some helpers to help you work with this easily.
Let’s say you have the following urls.py
:
from django.conf.urls.defaults import *
urlpatterns = patterns("",
url(r"^blogs/$", "blog.views.blog_list", name="blog_list"),
url(r"^blog/(?P<slug>[-\w]+)/$", "blog.views.blog_detail", name="blog_detail"),
)
To ensure URLs to blog_list
are correctly generated you will need to use
reverse
located on the ContentBridge
object:
def some_view_with_redirect(request, bridge=None):
...
return HttpResponseRedirect(bridge.reverse("blog_list", group))
The reverse
method work almost identical to Django’s reverse
. It is
essentially a wrapper. To reverse the blog_detail
URL:
blog = Blog.objects.get(pk=1)
bridge.reverse("blog_detail", group, kwargs={"slug": blog.slug})
Note
You should be aware that only kwargs
work with the bridge reverse
.
This is significant because URLs with args
mapping will fail reversal.
The reason behind this is because Django does not allow mixing of args
and kwargs
when performing URL reversal.
There are some cases when you don’t have easy access to the ContentBridge
.
You may only have access to a domain object instance. You can get access to
the ContentBridge
from the instance. For example:
blog = Blog.objects.get(pk=1)
blog.content_bridge.reverse(...)
URL reversal in templates¶
In Django you may be familiar with the {% url %}
templatetag. This is
basically a wrapper around reverse
. We provide a similar tag, but works
with our ContentBridge.reverse
. Here is how you might use it:
{% load group_tags %}
<a href="{% groupurl blog_detail group slug=blog.slug %}">{{ blog.name }}</a>
The {% groupurl %}
templatetag will fall back to normal Django URL reversal
if the value of the passed in group
is None
. This enables the ability
to work with no group association.