Version 38 (modified by 18 years ago) ( diff ) | ,
---|
Row-level permissions
Introduction
What are row-level permissions?
Some examples of row-level permissions: "User A has read-access to article 234" or "User D has read, write access to article 234."
Why do we need this?
With the current permission system, a user of Django's admin interface is capable either of editing *all* objects of a certain type or editing *none* of the objects of a certain type. After implementing a row level permission, the user can be capable of editing only certain objects of that type -- e.g., just the objects he created himself.
Status in Django
Row-level permissions are being implemented in a branch -- the per-object-permissions branch. This branch needs your help in testing. To get the code, use this Subversion command:
svn co http://code.djangoproject.com/svn/django/branches/per-object-permissions
Using row-level permissions
The basics
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."
- Django checks permissions in the following order:
- User row-level permission
- Group row-level permission
- User model-level permission
- Group model-level permission
Permission checking stops either at the first positive or negative. If no permission is found, it will return a negative (or false).
Enabling row-level permissions
To enable row-level permissions for a model, add a row_level_permissions = True
to the model's class Meta
. By default, row-level permissions are disabled.
For example:
class Mineral(models.Model): name = models.CharField(maxlength=150) hardness = models.PositiveSmallIntegerField() class Admin: pass class Meta: row_level_permissions = True def __str__(self): return self.name
The class Admin
is not required, but its presence does create the default add/change/delete model-level permissions, which are useful to have.
Creating a row-level permission
Django has two helper methods for use in creating row-level permissions. They both live on the RowLevelPermission
model manager (i.e., RowLevelPermission.objects
).
The first helper function 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 defaults to False
. You must pass an instance of the object and owner to this method.
The second helper function 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.
For example, this creates a change row-level permission on the quartz
object:
RowLevelPermission.objects.create_default_row_level_permissions(quartz, user, delete=False)
Checking permissions
To check whether a user has permission to edit an object, use the has_perm()
method on the User
object:
user.has_perm("mine.can_mine", object=mineral)
The object
parameter is optional for backwards-compatibility. If you don't want to check row-level permissions, exclude the object
parameter.
has_perm()
returns either True
or False
. It returns False
if the user has a negative row-level permission on the object. It also checks 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 permission over the negative.
Note that the GenericAuthorization branch (yet another of Django's many current branches) implements a different way of checking permissions. See the GenericAuthorization page for how row-level permissions fit in with that scheme.
Has row-level permission
The User
method contains_permission()
checks whether a user has the given permission on a model -- not an *instance* of a model but the model class itself. It returns True
if *any* row-level permission exists for the given user for the given model. (This is used in the admin interface to determine if the change list should be displayed for a user.)
For example, the following would return True
if some_user
had change permission on article 234:
some_user.contains_permission("mine.change_mineral", Mineral)
It will return True.
The name contains_permission
is still up for debate, as it's not perfect. If you have any ideas, please leave them here by editing this page, or leave a message on the django-developers mailing list.
Checking permissions in a template
Use the template tag if_has_perm
to check permission in a template. 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.
For example:
{% load auth %} {% if_has_perm mine.change_mineral obj %} ... {% else %} ... {% end_if_has_perm %}
Administration
You can configure row-level permissions to be created automatically by the admin interface when a user creates an object by using the class Admin
options grant_change_row_level_perm
and grant_delete_row_level_perm
. These are False
by default.
For 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 admin 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 behavior, set the show_all_rows
class 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. Example:
class Mineral(models.Model): # ... class Admin: show_all_rows = False class Meta: row_level_permissions = True
Accessing row-level permissions from a model
Use the row_level_permissions
attribute on a model object to retrieve 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 owner
attribute. For example:
user = row_level_permission.owner
To return the instance of a row-level permission, use the model
attribute. 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 many-to-many conflict. This is a bug with the generic relation code. The workaround for now is to rename the
related_name
for each different owner. (See ticket #2573 for the bug with generic relations.) - Row-level permission objects cannot have row-level permissions enabled.
- The error message in the admin interface is displayed with a checkmark, not an error icon.
Contact
If there are any problems, please contact Chris, the branch maintainer, at indirecthit [at] gmail.com.