= 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 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: {{{ #!python 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}}}: {{{ #!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 {{{owner}}} attribute. For example: {{{ #!python user = row_level_permission.owner }}} To return the instance of a row-level permission, use the {{{model}}} attribute. 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 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.