| 7 | | Some examples of row-level permissions: "User A has read-access to article 234" or "User D has read, write access to article 234." |
| 8 | | |
| 9 | | === Why do we need this? === |
| 10 | | |
| 11 | | 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. |
| 12 | | |
| 13 | | === Status in Django === |
| 14 | | |
| 15 | | 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: |
| 16 | | |
| 17 | | {{{ |
| 18 | | svn co http://code.djangoproject.com/svn/django/branches/per-object-permissions |
| 19 | | }}} |
| 20 | | |
| 21 | | == Using row-level permissions == |
| 22 | | |
| 23 | | === The basics === |
| 24 | | |
| 25 | | There are a few things you need to know about row-level permissions before working with them: |
| 26 | | |
| 27 | | * 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. |
| 28 | | * Row-level permissions can be negative. This is determined by an attribute called "negative." |
| 29 | | * Django checks permissions in the following order: |
| 30 | | |
| 31 | | * User row-level permission |
| 32 | | * Group row-level permission |
| 33 | | * User model-level permission |
| 34 | | * Group model-level permission |
| 35 | | |
| 36 | | Permission checking stops either at the first positive or negative. If no permission is found, it will return a negative (or false). |
| 37 | | |
| 38 | | === Enabling row-level permissions === |
| 39 | | |
| 40 | | 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. |
| 41 | | |
| 42 | | For example: |
| 43 | | |
| 44 | | {{{ |
| 45 | | #!python |
| 46 | | class Mineral(models.Model): |
| 47 | | name = models.CharField(maxlength=150) |
| 48 | | hardness = models.PositiveSmallIntegerField() |
| 49 | | |
| 50 | | class Admin: |
| 51 | | pass |
| 52 | | |
| 53 | | class Meta: |
| 54 | | row_level_permissions = True |
| 55 | | |
| 56 | | def __str__(self): |
| 57 | | return self.name |
| 58 | | }}} |
| 59 | | |
| 60 | | The {{{class Admin}}} is not required, but its presence does create the default add/change/delete model-level permissions, which are useful to have. |
| 61 | | |
| 62 | | === Creating a row-level permission === |
| 63 | | |
| 64 | | Django has two helper methods for use in creating row-level permissions. They both live on the {{{RowLevelPermission}}} model manager (i.e., {{{RowLevelPermission.objects}}}). |
| 65 | | |
| 66 | | The first helper function is {{{create_row_level_permission}}}: |
| 67 | | |
| 68 | | {{{ |
| 69 | | #!python |
| 70 | | def create_row_level_permission(self, object_instance, owner_instance, permission, negative=False): |
| 71 | | # ... |
| 72 | | }}} |
| 73 | | |
| 74 | | 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. |
| 75 | | |
| 76 | | The second helper function is {{{create_default_row_permissions}}}: |
| 77 | | |
| 78 | | {{{ |
| 79 | | #!python |
| 80 | | def create_default_row_level_permissions(self, object_instance, owner_instance, change=True, delete=True, negChange=False, negDel=False): |
| 81 | | # ... |
| 82 | | }}} |
| 83 | | |
| 84 | | 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. |
| 85 | | |
| 86 | | For example, this creates a change row-level permission on the {{{quartz}}} object: |
| 87 | | |
| 88 | | {{{ |
| 89 | | #!python |
| 90 | | RowLevelPermission.objects.create_default_row_level_permissions(quartz, user, delete=False) |
| 91 | | }}} |
| 92 | | |
| 93 | | === Checking permissions === |
| 94 | | |
| 95 | | To check whether a user has permission to edit an object, use the {{{has_perm()}}} method on the {{{User}}} object: |
| 96 | | |
| 97 | | {{{ |
| 98 | | #!python |
| 99 | | user.has_perm("mine.can_mine", object=mineral) |
| 100 | | }}} |
| 101 | | |
| 102 | | The {{{object}}} parameter is optional for backwards-compatibility. If you don't want to check row-level permissions, exclude the {{{object}}} parameter. |
| 103 | | |
| 104 | | {{{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. |
| 105 | | |
| 106 | | 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. |
| 107 | | |
| 108 | | ==== Has row-level permission ==== |
| 109 | | |
| 110 | | 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.) |
| 111 | | |
| 112 | | For example, the following would return {{{True}}} if {{{some_user}}} had change permission on article 234: |
| 113 | | {{{ |
| 114 | | #!python |
| 115 | | some_user.contains_permission("mine.change_mineral", Mineral) |
| 116 | | }}} |
| 117 | | It will return True. |
| 118 | | |
| 119 | | 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. |
| 120 | | |
| 121 | | === Checking permissions in a template === |
| 122 | | |
| 123 | | Use the template tag {{{if_has_perm}}} to check permission in a template. The tag has the following syntax: |
| 124 | | |
| 125 | | {{{ |
| 126 | | {% load auth %} |
| 127 | | {% if_has_perm [not] (permission codename) [object] %} |
| 128 | | ... |
| 129 | | {% else %} |
| 130 | | ... |
| 131 | | {% end_if_has_perm %} |
| 132 | | }}} |
| 133 | | |
| 134 | | 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. |
| 135 | | |
| 136 | | For example: |
| 137 | | |
| 138 | | {{{ |
| 139 | | {% load auth %} |
| 140 | | {% if_has_perm mine.change_mineral obj %} |
| 141 | | ... |
| 142 | | {% else %} |
| 143 | | ... |
| 144 | | {% end_if_has_perm %} |
| 145 | | }}} |
| 146 | | |
| 147 | | === Administration === |
| 148 | | |
| 149 | | 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. |
| 150 | | |
| 151 | | For example: |
| 152 | | |
| 153 | | {{{ |
| 154 | | #!python |
| 155 | | class Mineral(models.Model): |
| 156 | | # ... |
| 157 | | class Admin: |
| 158 | | grant_change_row_level_perm = True |
| 159 | | grant_delete_row_level_perm = True |
| 160 | | |
| 161 | | class Meta: |
| 162 | | row_level_permissions = True |
| 163 | | }}} |
| 164 | | |
| 165 | | 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. |
| 166 | | |
| 167 | | 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: |
| 168 | | |
| 169 | | {{{ |
| 170 | | #!python |
| 171 | | class Mineral(models.Model): |
| 172 | | # ... |
| 173 | | class Admin: |
| 174 | | show_all_rows = False |
| 175 | | |
| 176 | | class Meta: |
| 177 | | row_level_permissions = True |
| 178 | | }}} |
| 179 | | |
| 180 | | === Accessing row-level permissions from a model === |
| 181 | | |
| 182 | | 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}}}: |
| 183 | | |
| 184 | | {{{ |
| 185 | | #!python |
| 186 | | rlp_list = quartz.row_level_permissions.all() |
| 187 | | }}} |
| 188 | | |
| 189 | | === Accessing the owner and model of a row-level permission === |
| 190 | | |
| 191 | | To return the owner of a row-level permission, use the {{{owner}}} attribute. For example: |
| 192 | | |
| 193 | | {{{ |
| 194 | | #!python |
| 195 | | user = row_level_permission.owner |
| 196 | | }}} |
| 197 | | |
| 198 | | To return the instance of a row-level permission, use the {{{model}}} attribute. For example: |
| 199 | | |
| 200 | | {{{ |
| 201 | | #!python |
| 202 | | object = row_level_permission.model |
| 203 | | }}} |
| 204 | | |
| 205 | | == Implementation notes == |
| 206 | | |
| 207 | | Please see RowLevelPermissionsDeveloper for more information on how row-level permissions are implemented. |
| 208 | | |
| 209 | | == Known bugs == |
| 210 | | |
| 211 | | * 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.) |
| 212 | | * Row-level permission objects cannot have row-level permissions enabled. |
| 213 | | * The error message in the admin interface is displayed with a checkmark, not an error icon. |
| 214 | | |
| 215 | | == Contact == |
| 216 | | |
| 217 | | If there are any problems, please contact Chris, the branch maintainer, at indirecthit [at] gmail.com. |
| | 10 | For public-facing (i.e., non-admin) views, you are of course free to implement whatever form of permission-checking logic your application requires. |