Code


Version 33 (modified by clong, 8 years ago) (diff)

Added contact info

TOC(inline)?

Introduction

What are Row Level Permissions?

An example of row level permissions would be: "User A has read-access to article 234" or "User D has read, write access to article 234".

Why do we need this?

An example of where this would be useful is a forum or message board. With the current permission system, a user is capable of editing all the posts or unable to edit any posts. After implementing a row level permission, it can be modified so a user is capable of editing only their own personal posts.

Using Row Level Permissions

Basic Idea

There are a few things you need to know about row level permissions before working with them:

  • Row level permissions use the permissions table to determine an object's possible permissions, you need to create permissions in the permissions table before using them in row level permissions.
  • Row level permissions can be negative, this is determined by an attribute called "negative".
  • The order of checking permissions will work in the following order: User Row Level Permission -> Group Row Level Permission -> User Model Level Permission -> Group Model Level Permission. The checking will stop either at the first positive or negative, and if no permission is found will return a negative (or false).

Enabling Row Level Permissions

Enabling row level permissions is done by using the Meta class, you enable row level permissions by setting the "row_level_permissions" attribute to true. By default, row level permissions are assumed to be disabled.

Example: To enable row level permissions for the mineral model, the model would look like:

class Mineral(models.Model):
    name = models.CharField(maxlength=150)
    hardness = models.PositiveSmallIntegerField()
    
    class Admin:
        pass
    
    class Meta:
        unique_together = (('name', 'hardness'),)
        row_level_permissions = True
  
    def __str__(self):
        return self.name

You do not need the class Admin but it does create the default change, add and delete permissions for the object which are useful to have.

Creating a Row Level Permission

There are two helper methods to create row level permissions. These can be accessed by using the Row Level Permissions manager (e.g. RowLevelPermission.objects)

The first is create_row_level_permission:

def create_row_level_permission(self, object_instance, owner_instance, permission, negative=False):
   ...

The permission parameter can either be the codename of the permission or a permission instance. The negative parameter is optional and will default to false. You must pass an instance of the object and owner to this method.

The second is create_default_row_permissions:

def create_default_row_level_permissions(self, object_instance, owner_instance, change=True, delete=True, negChange=False, negDel=False):
   ...

This will set up a row level permission with the default permissions set up for an object. The default permissions are: add, change and delete.

An example of its use, this creates a change row level permission on the quartz object:

...
RowLevelPermissions.objects.create_default_row_level_permissions(quartz, user, delete=False)
...

Checking Permissions

In the next week, GenericAuthorization and row level permissions will be merged. Therefore, I have shown two different methods of checking for permissions, one using the generic authorization and the currently implemented technique.

The current method uses the has_perm method in the User model. Note: The object parameter is optional, this is to allow backwards compatibility, so if you do not want to check for row level permissions do not include the object parameter.

Example:

...
user.has_perm("mine.can_mine", object=mineral)
...

This will return either True or False depending on if the user has the correct permission. It will return false if the user has a negative row level permission on the object.

This will also check group row level permissions. If the user is in two groups, the first having a positive row level permission and the second having a negative row level permission, it will take the positive row level permission over the negative.

Second method using GenericAuthorization will be written after the merge. It will follow the documentation written on GenericAuthorization

Has Row Level Permission

The method contains_permission checks if the user has the given permission on a model (not an instance of a model but the model). This checks if there is exists the given row level permission on any of the instances of the model. It is used in the admin interface to determine if the change list should be shown to the user.

E.g. If user A has change permission on article 234, and you did:

...
userA.contains_permission("mine.change_mineral", Mineral)
...

It will return True.

I am not a fan of contains_permission name, but for now it does work. If you have any ideas on how to change it, please let me know.

Checking Permissions in a Template

In a template, you can use the tag if_has_perm to check for permissions. The tag has the following syntax:

{% load auth %}
{% if_has_perm [not] (permission codename) [object] %}
...
{% else %}
...
{% end_if_has_perm %}

The parameters in square brackets are optional and the normal brackets are required. The else statement is optional. The permission codename should be in the format: app_label.codename.

Administration

You can set up row level permissions to be created automatically by the admin interface when a user creates an object by using the options: grant_change_row_level_perm and grant_delete_row_level_perm. By default these are turned off. An example:

...
class Mineral(models.Model):
...    
    class Admin:
        grant_change_row_level_perm=True
        grant_delete_row_level_perm=True
    
    class Meta:
        row_level_permissions = True
...

Row level permissions can be edited in the administration interface, on the change form for each object with row level permissions enabled is a link beside History to edit row level permissions. To edit row level permissions, you must have the change RLP permission and change permission on the object. To add row level permissions, you must have the add RLP permisison and change permission on the object.

By default, all instances of a model are shown to a user when they access the change list for a model. To turn off this behaviour, you must set the show_all_rows admin option to false. Doing this will increase the number of database queries made to the server, which is why the default option is that all rows are shown. An example:

...
class Mineral(models.Model):
...    
    class Admin:
        show_all_rows = False
    
    class Meta:
        row_level_permissions = True
...

Accessing Row Level Permissions from a Model

The relation name for row level permissions from a model is "row_level_permissions", this will return all row level permissions related to the instance of the object. For example, this will return all row level permissions related to the object quartz:

...
rlp_list = quartz.row_level_permissions.all()
...

Accessing the Owner and Model of a Row Level Permission

To return the owner of a row level permission use the attribute "owner". For example:

...
user = row_level_permission.owner
...

To return the instance of a row level permission use the attribute "model". For example:

...
object = row_level_permission.model
...

Implementation Notes

Please see RowLevelPermissionsDeveloper for more information on how row level permissions are implemented.

Known Bugs

  • Connecting more then one "owner" to the RLP model causes a M2M conflict, this is a bug with the generic relation code. Work around for now is to rename the related_name for each different owner. - This is a bug with generic relations, see ticket #2573
  • Row level permissions can not have row level permissions enabled
  • Error message in admin interface is displayed with a checkmark not an error icon

Download

Row Level Permissions are currently hosted in a branch on Django SVN. Please use: svn co http://code.djangoproject.com/svn/django/branches/per-object-permissions to download the current code.

Contact

If there are any problems, please contact myself Chris at indirecthit[at]gmail.com