Django Object Permissions 1.0

We’ve just released Django Object Permissions 1.0.   Object Permissions or row level permissions, allow you to grant users permissions on a specific model instance.  This feature is new in Django 1.2 and required by all authentication back-ends by Django 1.4.  At the OSL we’re building apps that allow our clients to self service, instead of waiting for an official implementation we rolled our own.

Dynamic Registration

Permissions can be registered on any models, and any number of permissions can be created.  This allows flexibility in defining what permissions your objects can have.

from object_permissions import register

class MyModel(models.Model):
    pass

register(['create', 'read','update'], MyModel)

Once registered permissions can be granted, and verified using the included authentication backend

user.grant('create', my_instance)
user.has_perm('create', my_instance)

Additional Helper Methods

Django authentication backends only have limited methods for checking permissions.  When dealing with row level permissions this is often not sufficient.  Certain situations require more elegant ways to check permissions.  For instance, rendering a list of objects filtered by permissions would require iterating the objects and making separate calls to user.has_perms()

# filter list of objects based on permissions, this is slow
query = MyModel.objects.all()
filtered = [obj for obj in MyModel if user.has_perm('foo', obj)]

Instead object_permissions provides user.filter_on_perms() to build a queryset that filters on permissions.

# retrieve all objects a user has any permissions on
user.filter_on_perms(['create','delete'], MyModel)

# check if user has permissions on any instances
user.perm_on_any(['create', 'delete'], MyModel)

# revoke all permissions
user.revoke_all(my_instance)

# set exact permissions without multiple statements
user.set_perms(['delete'])

# get all permissions
user.get_perms(my_instance)
get_model_perms(MyModel)
get_model_perms(my_instance)

# retrieve all users with permissions on an object
get_users(my_instance)

Efficient Queries

Our initial implementation used Content Types for a generic relationship to any Model.  While it was functional it required extra joins and queries that slowed it down.  Here is what it looked like.

class PermissionType(models.Model):
    content_type = models.ForeignKey(ContentType)
    name = models.CharField(max_length=32)

class Permission(models.Model):
    perm_type = models.ForeignKey(PermissionType)
    user = models.ForeignKey(User)
    object_id = models.IntegerField()

# lookup a permission
ct = ContentTypes.get_for_model(my_isntance)
Permission.filter(user=user, object_id=object.id, perm_type__name=perm, perm_type__ct=ct).exists()

Instead object permissions app now dynamically creates tables with foreign keys to the Model. This logic is abstracted away behind has_perm() and the other functions. When calling has_perm() the correct model will be selected to build the query off of.

Since the model is dynamically created the list of permissions become boolean fields part of the model. This reduces or removes the need for joins, resulting in faster permission lookups.

class MyModel_perm(models.Model):
    """ dynamic model generated by object permissions """
    user = models.ForeignKey(User)
    group = models.ForeignKey(UserGroup)
    obj = models.ForeignKey(MyModel)

    # permissions
    create = models.BooleanField()
    delete = models.BooleanField()
    my_perm = models.BooleanField()

# lookup permission
MyModel_perms.filter(user=user, obj=object, create=True).exists()

Since the permission table is related directly to the model, it also allows models to be filtered by permissions. With ContentTypes this requires at least two queries and is either less flexible or introduces scaling issues.

# return instances user has create perm on.
# with content types
ct = ContentType.get_for_model(MyModel)
ids = Perms.objects.filter(user=user, content_type=ct, create=True).values_list('id', flat=true)
MyModel.filter(pk__in=ids)

# same query with object_permissions
MyModel.filter(MyModel_perms__create=True, MyModel_perms__user=user)

0 Responses to “Django Object Permissions 1.0”


  • No Comments

Leave a Reply