= 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 [http://code.djangoproject.com/browser/django/branches/per-object-permissions 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: {{{ #!python 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}}}: {{{ #!python 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}}}: {{{ #!python 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: {{{ #!python 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: {{{ #!python 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: {{{ #!python 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: {{{ #!python 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 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: {{{ #!python ... 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: {{{ #!python ... 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: {{{ #!python ... user = row_level_permission.owner ... }}} To return the instance of a row level permission use the attribute "model". For example: {{{ #!python ... 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