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. |