Ticket #8060: admin_inline_permissions_v2.diff
File admin_inline_permissions_v2.diff, 20.8 KB (added by , 13 years ago) |
---|
-
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index f05b5cb..96f5ede 100644
a b class BaseModelAdmin(object): 270 270 clean_lookup = LOOKUP_SEP.join(parts) 271 271 return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy 272 272 273 def has_add_permission(self, request): 274 """ 275 Returns True if the given request has permission to add an object. 276 Can be overriden by the user in subclasses. 277 """ 278 opts = self.opts 279 return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) 280 281 def has_change_permission(self, request, obj=None): 282 """ 283 Returns True if the given request has permission to change the given 284 Django model instance, the default implementation doesn't examine the 285 `obj` parameter. 286 287 Can be overriden by the user in subclasses. In such case it should 288 return True if the given request has permission to change the `obj` 289 model instance. If `obj` is None, this should return True if the given 290 request has permission to change *any* object of the given type. 291 """ 292 opts = self.opts 293 return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) 294 295 def has_delete_permission(self, request, obj=None): 296 """ 297 Returns True if the given request has permission to change the given 298 Django model instance, the default implementation doesn't examine the 299 `obj` parameter. 300 301 Can be overriden by the user in subclasses. In such case it should 302 return True if the given request has permission to delete the `obj` 303 model instance. If `obj` is None, this should return True if the given 304 request has permission to delete *any* object of the given type. 305 """ 306 opts = self.opts 307 return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) 273 308 274 309 class ModelAdmin(BaseModelAdmin): 275 310 "Encapsulates all admin options and functionality for a given model." … … class ModelAdmin(BaseModelAdmin): 307 342 self.model = model 308 343 self.opts = model._meta 309 344 self.admin_site = admin_site 310 self.inline_instances = []311 for inline_class in self.inlines:312 inline_instance = inline_class(self.model, self.admin_site)313 self.inline_instances.append(inline_instance)314 345 if 'action_checkbox' not in self.list_display and self.actions is not None: 315 346 self.list_display = ['action_checkbox'] + list(self.list_display) 316 347 if not self.list_display_links: … … class ModelAdmin(BaseModelAdmin): 320 351 break 321 352 super(ModelAdmin, self).__init__() 322 353 354 def get_inline_instances(self, request=None, action=None): 355 inline_instances = [] 356 for inline_class in self.inlines: 357 inline = inline_class(self.model, self.admin_site) 358 if request: 359 if not ((action == 'add' and inline.has_add_permission(request)) or 360 (inline.has_add_permission(request) or inline.has_change_permission(request) or inline.has_delete_permission(request))): 361 continue 362 if action == 'change' and not inline.has_add_permission(request): 363 inline.max_num = 0 364 inline_instances.append(inline) 365 366 return inline_instances 367 368 @property 369 def inline_instances(self): 370 return self.get_inline_instances() 371 323 372 def get_urls(self): 324 373 from django.conf.urls import patterns, url 325 374 … … class ModelAdmin(BaseModelAdmin): 369 418 js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js']) 370 419 return forms.Media(js=[static('admin/js/%s' % url) for url in js]) 371 420 372 def has_add_permission(self, request):373 """374 Returns True if the given request has permission to add an object.375 Can be overriden by the user in subclasses.376 """377 opts = self.opts378 return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())379 380 def has_change_permission(self, request, obj=None):381 """382 Returns True if the given request has permission to change the given383 Django model instance, the default implementation doesn't examine the384 `obj` parameter.385 386 Can be overriden by the user in subclasses. In such case it should387 return True if the given request has permission to change the `obj`388 model instance. If `obj` is None, this should return True if the given389 request has permission to change *any* object of the given type.390 """391 opts = self.opts392 return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())393 394 def has_delete_permission(self, request, obj=None):395 """396 Returns True if the given request has permission to change the given397 Django model instance, the default implementation doesn't examine the398 `obj` parameter.399 400 Can be overriden by the user in subclasses. In such case it should401 return True if the given request has permission to delete the `obj`402 model instance. If `obj` is None, this should return True if the given403 request has permission to delete *any* object of the given type.404 """405 opts = self.opts406 return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())407 408 421 def get_model_perms(self, request): 409 422 """ 410 423 Returns a dict of all perms for this model. This dict has the keys … … class ModelAdmin(BaseModelAdmin): 500 513 fields=self.list_editable, **defaults) 501 514 502 515 def get_formsets(self, request, obj=None): 503 for inline in self.inline_instances: 516 action = ('change' if obj else 'add') 517 for inline in self.get_inline_instances(request, action): 504 518 yield inline.get_formset(request, obj) 505 519 506 520 def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True): … … class ModelAdmin(BaseModelAdmin): 914 928 915 929 ModelForm = self.get_form(request) 916 930 formsets = [] 931 inline_instances = self.get_inline_instances(request, 'add') 917 932 if request.method == 'POST': 918 933 form = ModelForm(request.POST, request.FILES) 919 934 if form.is_valid(): … … class ModelAdmin(BaseModelAdmin): 923 938 form_validated = False 924 939 new_object = self.model() 925 940 prefixes = {} 926 for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):941 for FormSet, inline in zip(self.get_formsets(request), inline_instances): 927 942 prefix = FormSet.get_default_prefix() 928 943 prefixes[prefix] = prefixes.get(prefix, 0) + 1 929 944 if prefixes[prefix] != 1 or not prefix: … … class ModelAdmin(BaseModelAdmin): 951 966 initial[k] = initial[k].split(",") 952 967 form = ModelForm(initial=initial) 953 968 prefixes = {} 954 for FormSet, inline in zip(self.get_formsets(request), 955 self.inline_instances): 969 for FormSet, inline in zip(self.get_formsets(request), inline_instances): 956 970 prefix = FormSet.get_default_prefix() 957 971 prefixes[prefix] = prefixes.get(prefix, 0) + 1 958 972 if prefixes[prefix] != 1 or not prefix: … … class ModelAdmin(BaseModelAdmin): 968 982 media = self.media + adminForm.media 969 983 970 984 inline_admin_formsets = [] 971 for inline, formset in zip( self.inline_instances, formsets):985 for inline, formset in zip(inline_instances, formsets): 972 986 fieldsets = list(inline.get_fieldsets(request)) 973 987 readonly = list(inline.get_readonly_fields(request)) 974 988 prepopulated = dict(inline.get_prepopulated_fields(request)) … … class ModelAdmin(BaseModelAdmin): 1012 1026 1013 1027 ModelForm = self.get_form(request, obj) 1014 1028 formsets = [] 1029 inline_instances = self.get_inline_instances(request, 'change') 1015 1030 if request.method == 'POST': 1016 1031 form = ModelForm(request.POST, request.FILES, instance=obj) 1017 1032 if form.is_valid(): … … class ModelAdmin(BaseModelAdmin): 1021 1036 form_validated = False 1022 1037 new_object = obj 1023 1038 prefixes = {} 1024 for FormSet, inline in zip(self.get_formsets(request, new_object), 1025 self.inline_instances): 1039 for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances): 1026 1040 prefix = FormSet.get_default_prefix() 1027 1041 prefixes[prefix] = prefixes.get(prefix, 0) + 1 1028 1042 if prefixes[prefix] != 1 or not prefix: … … class ModelAdmin(BaseModelAdmin): 1043 1057 else: 1044 1058 form = ModelForm(instance=obj) 1045 1059 prefixes = {} 1046 for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):1060 for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances): 1047 1061 prefix = FormSet.get_default_prefix() 1048 1062 prefixes[prefix] = prefixes.get(prefix, 0) + 1 1049 1063 if prefixes[prefix] != 1 or not prefix: … … class ModelAdmin(BaseModelAdmin): 1059 1073 media = self.media + adminForm.media 1060 1074 1061 1075 inline_admin_formsets = [] 1062 for inline, formset in zip( self.inline_instances, formsets):1076 for inline, formset in zip(inline_instances, formsets): 1063 1077 fieldsets = list(inline.get_fieldsets(request, obj)) 1064 1078 readonly = list(inline.get_readonly_fields(request, obj)) 1065 1079 prepopulated = dict(inline.get_prepopulated_fields(request, obj)) … … class InlineModelAdmin(BaseModelAdmin): 1377 1391 # if exclude is an empty list we use None, since that's the actual 1378 1392 # default 1379 1393 exclude = exclude or None 1394 can_delete = self.can_delete 1395 if request: # some tests pass None as request 1396 can_delete = can_delete and self.has_delete_permission(request, obj) 1380 1397 defaults = { 1381 1398 "form": self.form, 1382 1399 "formset": self.formset, … … class InlineModelAdmin(BaseModelAdmin): 1386 1403 "formfield_callback": partial(self.formfield_for_dbfield, request=request), 1387 1404 "extra": self.extra, 1388 1405 "max_num": self.max_num, 1389 "can_delete": self.can_delete,1406 "can_delete": can_delete, 1390 1407 } 1391 1408 defaults.update(kwargs) 1392 1409 return inlineformset_factory(self.parent_model, self.model, **defaults) … … class InlineModelAdmin(BaseModelAdmin): 1398 1415 fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) 1399 1416 return [(None, {'fields': fields})] 1400 1417 1418 def queryset(self, request): 1419 queryset = super(InlineModelAdmin, self).queryset(request) 1420 if request and not self.has_change_permission(request): 1421 queryset = queryset.filter(pk__isnull=True) 1422 return queryset 1423 1424 def get_permission_opts(self): 1425 opts = self.opts 1426 if opts.auto_created: 1427 # The model was auto-created as intermediary for a 1428 # ManyToMany-relationship, find out the destination model 1429 for field in opts.fields: 1430 if isinstance(field, models.ForeignKey) and field.rel.to != opts.auto_created: 1431 opts = field.rel.to._meta 1432 break 1433 return opts 1434 1435 def has_add_permission(self, request): 1436 opts = self.get_permission_opts() 1437 return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) 1438 1439 def has_change_permission(self, request, obj=None): 1440 opts = self.get_permission_opts() 1441 return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) 1442 1443 def has_delete_permission(self, request, obj=None): 1444 opts = self.get_permission_opts() 1445 return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) 1446 1401 1447 class StackedInline(InlineModelAdmin): 1402 1448 template = 'admin/edit_inline/stacked.html' 1403 1449 -
django/contrib/contenttypes/generic.py
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 69dd527..e558aee 100644
a b class GenericInlineModelAdmin(InlineModelAdmin): 411 411 # GenericInlineModelAdmin doesn't define its own. 412 412 exclude.extend(self.form._meta.exclude) 413 413 exclude = exclude or None 414 can_delete = self.can_delete 415 if request: # some tests pass None as request 416 can_delete = can_delete and self.has_delete_permission(request, obj) 414 417 defaults = { 415 418 "ct_field": self.ct_field, 416 419 "fk_field": self.ct_fk_field, … … class GenericInlineModelAdmin(InlineModelAdmin): 418 421 "formfield_callback": partial(self.formfield_for_dbfield, request=request), 419 422 "formset": self.formset, 420 423 "extra": self.extra, 421 "can_delete": self.can_delete,424 "can_delete": can_delete, 422 425 "can_order": False, 423 426 "fields": fields, 424 427 "max_num": self.max_num, -
tests/regressiontests/admin_inlines/tests.py
diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index 955d620..7a12724 100644
a b 1 1 from django.contrib.admin.helpers import InlineAdminForm 2 from django.contrib.auth.models import User, Permission 2 3 from django.contrib.contenttypes.models import ContentType 3 4 from django.test import TestCase 4 5 5 6 # local test models 6 7 from models import (Holder, Inner, Holder2, Inner2, Holder3, 7 8 Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, 8 CapoFamiglia, Consigliere, SottoCapo)9 Author, Book) 9 10 from admin import InnerInline 10 11 11 12 … … class TestInline(TestCase): 141 142 '<input id="id_-2-0-name" type="text" class="vTextField" ' 142 143 'name="-2-0-name" maxlength="100" />') 143 144 145 def test_inline_permissions(self): 146 """ 147 Make sure the admin respects permissions for objects that are edited 148 inline. Ref #8060. 149 """ 150 user = User.objects.get(username='super') 151 user.is_superuser = False 152 user.save() 153 154 author_ct = ContentType.objects.get_for_model(Author) 155 holder_ct = ContentType.objects.get_for_model(Holder) 156 book_ct = ContentType.objects.get_for_model(Book) 157 inner_ct = ContentType.objects.get_for_model(Inner) 158 159 permission = Permission.objects.get(codename='add_author', content_type=author_ct) 160 user.user_permissions.add(permission) 161 permission = Permission.objects.get(codename='change_author', content_type=author_ct) 162 user.user_permissions.add(permission) 163 permission = Permission.objects.get(codename='add_holder', content_type=holder_ct) 164 user.user_permissions.add(permission) 165 permission = Permission.objects.get(codename='change_holder', content_type=holder_ct) 166 user.user_permissions.add(permission) 167 168 author = Author.objects.create(pk=1, name=u'The Author') 169 author.books.create(name=u'The inline Book') 170 171 # Make sure both ForeignKey as well as ManyToMany inlines are properly removed 172 response = self.client.get('/admin/admin_inlines/author/add/') 173 # This would be a TabularInline 174 self.assertNotContains(response, '<h2>Author-book relationships</h2>') 175 self.assertNotContains(response, 'Add another Author-Book Relationship') 176 self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') 177 response = self.client.get('/admin/admin_inlines/author/1/') 178 self.assertNotContains(response, '<h2>Author-book relationships</h2>') 179 self.assertNotContains(response, 'Add another Author-Book Relationship') 180 self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') 181 182 response = self.client.get('/admin/admin_inlines/holder/add/') 183 # This would be a StackedInline 184 self.assertNotContains(response, '<h2>Inners</h2>') 185 self.assertNotContains(response, 'Add another Inner') 186 self.assertNotContains(response, 'id="id_inner_set-TOTAL_FORMS"') 187 response = self.client.get(self.change_url) 188 self.assertNotContains(response, '<h2>Inners</h2>') 189 self.assertNotContains(response, 'Add another Inner') 190 self.assertNotContains(response, 'id="id_inner_set-TOTAL_FORMS"') 191 192 # Now let's add the missing add permissions and make sure the inlines are shown 193 permission = Permission.objects.get(codename='add_book', content_type=book_ct) 194 user.user_permissions.add(permission) 195 permission = Permission.objects.get(codename='add_inner', content_type=inner_ct) 196 user.user_permissions.add(permission) 197 198 response = self.client.get('/admin/admin_inlines/author/add/') 199 self.assertContains(response, '<h2>Author-book relationships</h2>') 200 self.assertContains(response, 'Add another Author-Book Relationship') 201 self.assertContains(response, 'value="3" id="id_Author_books-TOTAL_FORMS"') 202 response = self.client.get('/admin/admin_inlines/holder/add/') 203 self.assertContains(response, '<h2>Inners</h2>') 204 self.assertContains(response, 'Add another Inner') 205 self.assertContains(response, 'value="3" id="id_inner_set-TOTAL_FORMS"') 206 207 # The inlines should be in the change view as well, but existing data 208 # should not be shown 209 response = self.client.get('/admin/admin_inlines/author/1/') 210 self.assertContains(response, '<h2>Author-book relationships</h2>') 211 self.assertContains(response, 'Add another Author-Book Relationship') 212 self.assertContains(response, 'value="3" id="id_Author_books-TOTAL_FORMS"') 213 self.assertNotContains(response, '<input type="hidden" name="Author_books-0-id" value="1"') 214 response = self.client.get(self.change_url) 215 self.assertContains(response, '<h2>Inners</h2>') 216 self.assertContains(response, 'Add another Inner') 217 self.assertContains(response, 'value="3" id="id_inner_set-TOTAL_FORMS"') 218 self.assertNotContains(response, '<input type="hidden" name="inner_set-0-id" value="1"') 219 220 # Add the change permissions and check that existing data is shown. 221 permission = Permission.objects.get(codename='change_book', content_type=book_ct) 222 user.user_permissions.add(permission) 223 permission = Permission.objects.get(codename='change_inner', content_type=inner_ct) 224 user.user_permissions.add(permission) 225 response = self.client.get('/admin/admin_inlines/author/1/') 226 self.assertContains(response, '<input type="hidden" name="Author_books-0-id" value="1"') 227 # Deletion should not be possible. 228 self.assertNotContains(response, 'id="id_Author_books-0-DELETE"') 229 response = self.client.get(self.change_url) 230 self.assertContains(response, '<input type="hidden" name="inner_set-0-id" value="1"') 231 232 # Remove the add permissions. inlines should still be there, but 233 # no possibility to add data 234 permission = Permission.objects.get(codename='add_book', content_type=book_ct) 235 user.user_permissions.remove(permission) 236 permission = Permission.objects.get(codename='add_inner', content_type=inner_ct) 237 user.user_permissions.remove(permission) 238 response = self.client.get('/admin/admin_inlines/author/1/') 239 self.assertContains(response, '<h2>Author-book relationships</h2>') 240 self.assertContains(response, '<input type="hidden" name="Author_books-0-id" value="1"') 241 self.assertContains(response, 'value="1" id="id_Author_books-TOTAL_FORMS"') 242 response = self.client.get(self.change_url) 243 self.assertContains(response, '<h2>Inners</h2>') 244 self.assertContains(response, '<input type="hidden" name="inner_set-0-id" value="1"') 245 self.assertContains(response, 'value="1" id="id_inner_set-TOTAL_FORMS"') 246 247 # Check that deletion is possible with the appropriate permissions. 248 # Deletion is only possible for the Author-Book relationship since the 249 # foreign key from Inner to Holder does not allow NULL values. 250 permission = Permission.objects.get(codename='delete_book', content_type=book_ct) 251 user.user_permissions.add(permission) 252 response = self.client.get('/admin/admin_inlines/author/1/') 253 self.assertContains(response, 'id="id_Author_books-0-DELETE"') 144 254 145 255 class TestInlineMedia(TestCase): 146 256 urls = "regressiontests.admin_inlines.urls"