Wednesday, March 30, 2011

Django Pattern For Model Permissions

I think I've found an interesting way to set up a permissions system in Django.  This system allows you to specify granular permissions on specific object instances and is template friendly.

Setup The Permissions Infrastructure

First, you create an "abstract" Permissions class that models will use as a permissions base class.  This base permissions class sets up simple caching for the permissions and creates some common methods to be used by all permissions objects.  The current_user represents the logged in that you want to check permissions for a particular object.

Notice the use of threadlocals here.  If current_user is not supplied in the constructor, the class will try to lookup the current_user from a variable by the threadlocals middleware (see below).  Be warned, however, some people don't seem to like the threadlocals approach.  To be honest, I'm still trying to evaluate the pluses and minuses, but it sure makes certain things in the templates easier.  Comments are welcome.


from core.middleware import threadlocals                        
class Permissions(object):

    def __init__(self, obj, current_user=False):
        if not current_user:
        if not current_user:
            current_user = None
        self.current_user = current_user
        self.obj = obj

        self.cache = {}
    def clear_cache(self, key=None):
        if key:
               del self.cache[key]
            except KeyError:

    def get_current_user(self):
        return self.current_user

    def __unicode__(self):
        return "Permissions for: %s" % (self.obj)

The thredlocals middleware stores the current user in the local thread.   You need to include this middleware in my middleware settings.

core/middleware/ - Reference

import threading

_thread_locals = threading.local()

def get_current_user():
    return getattr(_thread_locals, 'user', None)

class ThreadLocals(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""
    def process_request(self, request):
        _thread_locals.user = getattr(request, 'user', None)

Now you can create a Permission subclass within my model class.  This Permissions model inherits the abstract Permissions class that we defined earlier.   Now, you can create simple permissions methods within my Permissions child class.  For example, I've created is_creator() and can_edit() methods in this class.

Finally, inside of MyModel class, you create a permissions() method that instantiates a permissions instance and returns that instance.


from core import permissions 

class MyModel(models.Model):

    creator = models.ForeignKey('auth.User')
    field1 = models...
    field2 = models...  # define Django Model fields here 

    class Permissions(permissions.Permissions):

        def can_edit(self):
            if self.cache.has_key(KEY):
                return self.cache[KEY]

            if self.is_creator():
                return_value = True
                return_value = False

            return return_value

        def is_creator(self):
            if self.cache.has_key(KEY):
                return self.cache[KEY]
            if self.obj.creator == self.current_user:
                return_value = True
                return_value = False
            return return_value

    def permissions(self, current_user=None):
         return self.Permissions(self, current_user)

Using The Permissions

You can use the permissions system in a view as exemplified by the following code.  Because the abstract Permissions class contains some basic caching, multiple calls to the same permissions function will not hit the database multiple times.


from myapp import

def index(request, .... ):
    current_user = request.user 
    mymodel = MyModel.objects.get_mymodel_instance(.....)  

    mymodel_permissions = mymodel.permissions(current_user=current_user)

    if mymodel_permissions.can_edit():


Now what's really cool is how you can make use of this in templates.  Because of the threadlocals, you simply can make calls the my Permission instance methods directly in the template without using a templatetag to pass in the current user -- because of the framework that we setup above, the current user will be assumed automatically.   Using the "with" statement, you can instantiate the permissions object only once so you can take advantage of the caching in the template.

{% with mymodel.permissions as permissions %}
   <div>{% if permissions.can_edit %}<a href="">edit</a>{% endif %}</div>
{% endwith %}

You can still unittest this pattern easily by creating your tests similar to the way you create your views; you can simply pass in the current_user to the mymodel.permissions() function.

I hope this pattern is useful to someone.  I'm sure there are better approaches.  I'm glad to hear your comments.

Tuesday, March 22, 2011

Django Environment Quick Setup

aptitude install python-virtualenv  # for debian-based systems
virtualenv --no-site-packages myproject
cd myproject/
source ./bin/activate
pip install django  south  django-extensions
pip install flup  psycopg2
mkdir -p ./etc/ ./var/log/ ./app/django/
cd ./app/django/ startproject mymain

Django Project Setup Conveniences

Whenever I start a new Django project, there are several common steps that I to help standardize my setups.


I define a PROJECT_ROOT variable which contains the path to the project directory. 

This is useful for various settings in the
import sys, os
PROJECT_ROOT = os.path.dirname(__file__)

I set my MEDIA_ROOT to be a path relative to the PROJECT_ROOT.  
Of course, I will need to create this directory on the filesystem. 
MEDIA_ROOT = os.path.join(PROJECT_ROOT, '..','htdocs','media').replace('\\','/') + '/'

And I set a relative TEMPLATE_DIRS path.  I will need to make this directory as well.
    os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),

Sometimes I add extra directories to the path so I can store specific packages within the project directory. (I might use this if I want to keep all related modules together).
sys.path.insert(0, os.path.join(PROJECT_DIR, "contrib"))
sys.path.insert(0, os.path.join(PROJECT_DIR, "src"))

I generally like to put admin media in the following location:
ADMIN_MEDIA_PREFIX = '/media/admin/'

Almost all the time, I install the following modules in INSTALLED_APPS:
'south', # django-south
'django_extensions', # django-command-extensions


If I want to use the Django debug server, I'll set this near the top:
from django.conf import settings
urlpatterns = patterns('',)
if settings.DEBUG:
    urlpatterns = patterns('',
        (r'^media/(?P<path>.*)$', 'django.views.static.serve',
                   {'document_root': settings.MEDIA_ROOT}),


This blog will document my adventures in Django, the Python web framework.  It is a direct fork of my Linux Info blog, but will be specifically utilized for documenting my Django activity.  Though my aim is to post Django code and examples that are helpful, there will most certainly be better/different ways to approach various challenges.  Any comments are much appreciated.