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.
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)
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)