Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #8060 - Added permissions-checking for admin inlines. Thanks p.…

…patruno for report and Stephan Jaensch for work on the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16934 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit b1b1da1eac93297503c04b8394fb98e38f552f5f 1 parent e2f9c11
Carl Meyer authored October 07, 2011
149  django/contrib/admin/options.py
@@ -270,6 +270,41 @@ def lookup_allowed(self, lookup, value):
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."
@@ -307,10 +342,6 @@ def __init__(self, model, admin_site):
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:
@@ -320,6 +351,21 @@ def __init__(self, model, admin_site):
320 351
                     break
321 352
         super(ModelAdmin, self).__init__()
322 353
 
  354
+    def get_inline_instances(self, request):
  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 (inline.has_add_permission(request) or
  360
+                        inline.has_change_permission(request) or
  361
+                        inline.has_delete_permission(request)):
  362
+                    continue
  363
+                if not inline.has_add_permission(request):
  364
+                    inline.max_num = 0
  365
+            inline_instances.append(inline)
  366
+
  367
+        return inline_instances
  368
+
323 369
     def get_urls(self):
324 370
         from django.conf.urls import patterns, url
325 371
 
@@ -369,42 +415,6 @@ def media(self):
369 415
             js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js'])
370 416
         return forms.Media(js=[static('admin/js/%s' % url) for url in js])
371 417
 
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.opts
378  
-        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 given
383  
-        Django model instance, the default implementation doesn't examine the
384  
-        `obj` parameter.
385  
-
386  
-        Can be overriden by the user in subclasses. In such case it should
387  
-        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 given
389  
-        request has permission to change *any* object of the given type.
390  
-        """
391  
-        opts = self.opts
392  
-        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 given
397  
-        Django model instance, the default implementation doesn't examine the
398  
-        `obj` parameter.
399  
-
400  
-        Can be overriden by the user in subclasses. In such case it should
401  
-        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 given
403  
-        request has permission to delete *any* object of the given type.
404  
-        """
405  
-        opts = self.opts
406  
-        return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
407  
-
408 418
     def get_model_perms(self, request):
409 419
         """
410 420
         Returns a dict of all perms for this model. This dict has the keys
@@ -500,7 +510,7 @@ def get_changelist_formset(self, request, **kwargs):
500 510
             fields=self.list_editable, **defaults)
501 511
 
502 512
     def get_formsets(self, request, obj=None):
503  
-        for inline in self.inline_instances:
  513
+        for inline in self.get_inline_instances(request):
504 514
             yield inline.get_formset(request, obj)
505 515
 
506 516
     def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
@@ -914,6 +924,7 @@ def add_view(self, request, form_url='', extra_context=None):
914 924
 
915 925
         ModelForm = self.get_form(request)
916 926
         formsets = []
  927
+        inline_instances = self.get_inline_instances(request)
917 928
         if request.method == 'POST':
918 929
             form = ModelForm(request.POST, request.FILES)
919 930
             if form.is_valid():
@@ -923,7 +934,7 @@ def add_view(self, request, form_url='', extra_context=None):
923 934
                 form_validated = False
924 935
                 new_object = self.model()
925 936
             prefixes = {}
926  
-            for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
  937
+            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
927 938
                 prefix = FormSet.get_default_prefix()
928 939
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
929 940
                 if prefixes[prefix] != 1 or not prefix:
@@ -951,8 +962,7 @@ def add_view(self, request, form_url='', extra_context=None):
951 962
                     initial[k] = initial[k].split(",")
952 963
             form = ModelForm(initial=initial)
953 964
             prefixes = {}
954  
-            for FormSet, inline in zip(self.get_formsets(request),
955  
-                                       self.inline_instances):
  965
+            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
956 966
                 prefix = FormSet.get_default_prefix()
957 967
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
958 968
                 if prefixes[prefix] != 1 or not prefix:
@@ -968,7 +978,7 @@ def add_view(self, request, form_url='', extra_context=None):
968 978
         media = self.media + adminForm.media
969 979
 
970 980
         inline_admin_formsets = []
971  
-        for inline, formset in zip(self.inline_instances, formsets):
  981
+        for inline, formset in zip(inline_instances, formsets):
972 982
             fieldsets = list(inline.get_fieldsets(request))
973 983
             readonly = list(inline.get_readonly_fields(request))
974 984
             prepopulated = dict(inline.get_prepopulated_fields(request))
@@ -1012,6 +1022,7 @@ def change_view(self, request, object_id, extra_context=None):
1012 1022
 
1013 1023
         ModelForm = self.get_form(request, obj)
1014 1024
         formsets = []
  1025
+        inline_instances = self.get_inline_instances(request)
1015 1026
         if request.method == 'POST':
1016 1027
             form = ModelForm(request.POST, request.FILES, instance=obj)
1017 1028
             if form.is_valid():
@@ -1021,8 +1032,7 @@ def change_view(self, request, object_id, extra_context=None):
1021 1032
                 form_validated = False
1022 1033
                 new_object = obj
1023 1034
             prefixes = {}
1024  
-            for FormSet, inline in zip(self.get_formsets(request, new_object),
1025  
-                                       self.inline_instances):
  1035
+            for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances):
1026 1036
                 prefix = FormSet.get_default_prefix()
1027 1037
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
1028 1038
                 if prefixes[prefix] != 1 or not prefix:
@@ -1043,7 +1053,7 @@ def change_view(self, request, object_id, extra_context=None):
1043 1053
         else:
1044 1054
             form = ModelForm(instance=obj)
1045 1055
             prefixes = {}
1046  
-            for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
  1056
+            for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances):
1047 1057
                 prefix = FormSet.get_default_prefix()
1048 1058
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
1049 1059
                 if prefixes[prefix] != 1 or not prefix:
@@ -1059,7 +1069,7 @@ def change_view(self, request, object_id, extra_context=None):
1059 1069
         media = self.media + adminForm.media
1060 1070
 
1061 1071
         inline_admin_formsets = []
1062  
-        for inline, formset in zip(self.inline_instances, formsets):
  1072
+        for inline, formset in zip(inline_instances, formsets):
1063 1073
             fieldsets = list(inline.get_fieldsets(request, obj))
1064 1074
             readonly = list(inline.get_readonly_fields(request, obj))
1065 1075
             prepopulated = dict(inline.get_prepopulated_fields(request, obj))
@@ -1377,6 +1387,7 @@ def get_formset(self, request, obj=None, **kwargs):
1377 1387
         # if exclude is an empty list we use None, since that's the actual
1378 1388
         # default
1379 1389
         exclude = exclude or None
  1390
+        can_delete = self.can_delete and self.has_delete_permission(request, obj)
1380 1391
         defaults = {
1381 1392
             "form": self.form,
1382 1393
             "formset": self.formset,
@@ -1386,7 +1397,7 @@ def get_formset(self, request, obj=None, **kwargs):
1386 1397
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
1387 1398
             "extra": self.extra,
1388 1399
             "max_num": self.max_num,
1389  
-            "can_delete": self.can_delete,
  1400
+            "can_delete": can_delete,
1390 1401
         }
1391 1402
         defaults.update(kwargs)
1392 1403
         return inlineformset_factory(self.parent_model, self.model, **defaults)
@@ -1398,6 +1409,44 @@ def get_fieldsets(self, request, obj=None):
1398 1409
         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
1399 1410
         return [(None, {'fields': fields})]
1400 1411
 
  1412
+    def queryset(self, request):
  1413
+        queryset = super(InlineModelAdmin, self).queryset(request)
  1414
+        if not self.has_change_permission(request):
  1415
+            queryset = queryset.none()
  1416
+        return queryset
  1417
+
  1418
+    def has_add_permission(self, request):
  1419
+        if self.opts.auto_created:
  1420
+            # We're checking the rights to an auto-created intermediate model,
  1421
+            # which doesn't have its own individual permissions. The user needs
  1422
+            # to have the change permission for the related model in order to
  1423
+            # be able to do anything with the intermediate model.
  1424
+            return self.has_change_permission(request)
  1425
+        return request.user.has_perm(
  1426
+            self.opts.app_label + '.' + self.opts.get_add_permission())
  1427
+
  1428
+    def has_change_permission(self, request, obj=None):
  1429
+        opts = self.opts
  1430
+        if opts.auto_created:
  1431
+            # The model was auto-created as intermediary for a
  1432
+            # ManyToMany-relationship, find the target model
  1433
+            for field in opts.fields:
  1434
+                if field.rel and field.rel.to != self.parent_model:
  1435
+                    opts = field.rel.to._meta
  1436
+                    break
  1437
+        return request.user.has_perm(
  1438
+            opts.app_label + '.' + opts.get_change_permission())
  1439
+
  1440
+    def has_delete_permission(self, request, obj=None):
  1441
+        if self.opts.auto_created:
  1442
+            # We're checking the rights to an auto-created intermediate model,
  1443
+            # which doesn't have its own individual permissions. The user needs
  1444
+            # to have the change permission for the related model in order to
  1445
+            # be able to do anything with the intermediate model.
  1446
+            return self.has_change_permission(request, obj)
  1447
+        return request.user.has_perm(
  1448
+            self.opts.app_label + '.' + self.opts.get_delete_permission())
  1449
+
1401 1450
 class StackedInline(InlineModelAdmin):
1402 1451
     template = 'admin/edit_inline/stacked.html'
1403 1452
 
3  django/contrib/contenttypes/generic.py
@@ -424,6 +424,7 @@ def get_formset(self, request, obj=None, **kwargs):
424 424
             # GenericInlineModelAdmin doesn't define its own.
425 425
             exclude.extend(self.form._meta.exclude)
426 426
         exclude = exclude or None
  427
+        can_delete = self.can_delete and self.has_delete_permission(request, obj)
427 428
         defaults = {
428 429
             "ct_field": self.ct_field,
429 430
             "fk_field": self.ct_fk_field,
@@ -431,7 +432,7 @@ def get_formset(self, request, obj=None, **kwargs):
431 432
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
432 433
             "formset": self.formset,
433 434
             "extra": self.extra,
434  
-            "can_delete": self.can_delete,
  435
+            "can_delete": can_delete,
435 436
             "can_order": False,
436 437
             "fields": fields,
437 438
             "max_num": self.max_num,
8  docs/ref/contrib/admin/index.txt
@@ -1391,11 +1391,17 @@ adds some of its own (the shared features are actually defined in the
1391 1391
 - :attr:`~ModelAdmin.ordering`
1392 1392
 - :meth:`~ModelAdmin.queryset`
1393 1393
 
  1394
+.. versionadded:: 1.4
  1395
+
  1396
+- :meth:`~ModelAdmin.has_add_permission`
  1397
+- :meth:`~ModelAdmin.has_change_permission`
  1398
+- :meth:`~ModelAdmin.has_delete_permission`
  1399
+
1394 1400
 The ``InlineModelAdmin`` class adds:
1395 1401
 
1396 1402
 .. attribute:: InlineModelAdmin.model
1397 1403
 
1398  
-    The model in which the inline is using. This is required.
  1404
+    The model which the inline is using. This is required.
1399 1405
 
1400 1406
 .. attribute:: InlineModelAdmin.fk_name
1401 1407
 
9  docs/releases/1.4.txt
@@ -128,6 +128,15 @@ A new :meth:`~django.contrib.admin.ModelAdmin.save_related` hook was added to
128 128
 :mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how
129 129
 related objects are saved in the admin.
130 130
 
  131
+Admin inlines respect user permissions
  132
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  133
+
  134
+Admin inlines will now only allow those actions for which the user has
  135
+permission. For ``ManyToMany`` relationships with an auto-created intermediate
  136
+model (which does not have its own permissions), the change permission for the
  137
+related model determines if the user has the permission to add, change or
  138
+delete relationships.
  139
+
131 140
 Tools for cryptographic signing
132 141
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133 142
 
183  tests/regressiontests/admin_inlines/tests.py
... ...
@@ -1,11 +1,12 @@
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
 
@@ -141,7 +142,6 @@ def test_non_related_name_inline(self):
141 142
                 '<input id="id_-2-0-name" type="text" class="vTextField" '
142 143
                 'name="-2-0-name" maxlength="100" />')
143 144
 
144  
-
145 145
 class TestInlineMedia(TestCase):
146 146
     urls = "regressiontests.admin_inlines.urls"
147 147
     fixtures = ['admin-views-users.xml']
@@ -196,3 +196,182 @@ def test_immutable_content_type(self):
196 196
         iaf = InlineAdminForm(None, None, {}, {}, joe)
197 197
         parent_ct = ContentType.objects.get_for_model(Parent)
198 198
         self.assertEqual(iaf.original.content_type, parent_ct)
  199
+
  200
+class TestInlinePermissions(TestCase):
  201
+    """
  202
+    Make sure the admin respects permissions for objects that are edited
  203
+    inline. Refs #8060.
  204
+
  205
+    """
  206
+    urls = "regressiontests.admin_inlines.urls"
  207
+
  208
+    def setUp(self):
  209
+        self.user = User(username='admin')
  210
+        self.user.is_staff = True
  211
+        self.user.is_active = True
  212
+        self.user.set_password('secret')
  213
+        self.user.save()
  214
+
  215
+        self.author_ct = ContentType.objects.get_for_model(Author)
  216
+        self.holder_ct = ContentType.objects.get_for_model(Holder2)
  217
+        self.book_ct = ContentType.objects.get_for_model(Book)
  218
+        self.inner_ct = ContentType.objects.get_for_model(Inner2)
  219
+
  220
+        # User always has permissions to add and change Authors, and Holders,
  221
+        # the main (parent) models of the inlines. Permissions on the inlines
  222
+        # vary per test.
  223
+        permission = Permission.objects.get(codename='add_author', content_type=self.author_ct)
  224
+        self.user.user_permissions.add(permission)
  225
+        permission = Permission.objects.get(codename='change_author', content_type=self.author_ct)
  226
+        self.user.user_permissions.add(permission)
  227
+        permission = Permission.objects.get(codename='add_holder2', content_type=self.holder_ct)
  228
+        self.user.user_permissions.add(permission)
  229
+        permission = Permission.objects.get(codename='change_holder2', content_type=self.holder_ct)
  230
+        self.user.user_permissions.add(permission)
  231
+
  232
+        author = Author.objects.create(pk=1, name=u'The Author')
  233
+        author.books.create(name=u'The inline Book')
  234
+        self.author_change_url = '/admin/admin_inlines/author/%i/' % author.id
  235
+
  236
+        holder = Holder2.objects.create(dummy=13)
  237
+        Inner2.objects.create(dummy=42, holder=holder)
  238
+        self.holder_change_url = '/admin/admin_inlines/holder2/%i/' % holder.id
  239
+
  240
+        self.assertEqual(
  241
+            self.client.login(username='admin', password='secret'),
  242
+            True)
  243
+
  244
+    def tearDown(self):
  245
+        self.client.logout()
  246
+
  247
+    def test_inline_add_m2m_noperm(self):
  248
+        response = self.client.get('/admin/admin_inlines/author/add/')
  249
+        # No change permission on books, so no inline
  250
+        self.assertNotContains(response, '<h2>Author-book relationships</h2>')
  251
+        self.assertNotContains(response, 'Add another Author-Book Relationship')
  252
+        self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
  253
+
  254
+    def test_inline_add_fk_noperm(self):
  255
+        response = self.client.get('/admin/admin_inlines/holder2/add/')
  256
+        # No permissions on Inner2s, so no inline
  257
+        self.assertNotContains(response, '<h2>Inner2s</h2>')
  258
+        self.assertNotContains(response, 'Add another Inner2')
  259
+        self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"')
  260
+
  261
+    def test_inline_change_m2m_noperm(self):
  262
+        response = self.client.get(self.author_change_url)
  263
+        # No change permission on books, so no inline
  264
+        self.assertNotContains(response, '<h2>Author-book relationships</h2>')
  265
+        self.assertNotContains(response, 'Add another Author-Book Relationship')
  266
+        self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
  267
+
  268
+    def test_inline_change_fk_noperm(self):
  269
+        response = self.client.get(self.holder_change_url)
  270
+        # No permissions on Inner2s, so no inline
  271
+        self.assertNotContains(response, '<h2>Inner2s</h2>')
  272
+        self.assertNotContains(response, 'Add another Inner2')
  273
+        self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"')
  274
+
  275
+    def test_inline_add_m2m_add_perm(self):
  276
+        permission = Permission.objects.get(codename='add_book', content_type=self.book_ct)
  277
+        self.user.user_permissions.add(permission)
  278
+        response = self.client.get('/admin/admin_inlines/author/add/')
  279
+        # No change permission on Books, so no inline
  280
+        self.assertNotContains(response, '<h2>Author-book relationships</h2>')
  281
+        self.assertNotContains(response, 'Add another Author-Book Relationship')
  282
+        self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
  283
+
  284
+    def test_inline_add_fk_add_perm(self):
  285
+        permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
  286
+        self.user.user_permissions.add(permission)
  287
+        response = self.client.get('/admin/admin_inlines/holder2/add/')
  288
+        # Add permission on inner2s, so we get the inline
  289
+        self.assertContains(response, '<h2>Inner2s</h2>')
  290
+        self.assertContains(response, 'Add another Inner2')
  291
+        self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"')
  292
+
  293
+    def test_inline_change_m2m_add_perm(self):
  294
+        permission = Permission.objects.get(codename='add_book', content_type=self.book_ct)
  295
+        self.user.user_permissions.add(permission)
  296
+        response = self.client.get(self.author_change_url)
  297
+        # No change permission on books, so no inline
  298
+        self.assertNotContains(response, '<h2>Author-book relationships</h2>')
  299
+        self.assertNotContains(response, 'Add another Author-Book Relationship')
  300
+        self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
  301
+        self.assertNotContains(response, 'id="id_Author_books-0-DELETE"')
  302
+
  303
+    def test_inline_change_m2m_change_perm(self):
  304
+        permission = Permission.objects.get(codename='change_book', content_type=self.book_ct)
  305
+        self.user.user_permissions.add(permission)
  306
+        response = self.client.get(self.author_change_url)
  307
+        # We have change perm on books, so we can add/change/delete inlines
  308
+        self.assertContains(response, '<h2>Author-book relationships</h2>')
  309
+        self.assertContains(response, 'Add another Author-Book Relationship')
  310
+        self.assertContains(response, 'value="4" id="id_Author_books-TOTAL_FORMS"')
  311
+        self.assertContains(response, '<input type="hidden" name="Author_books-0-id" value="1"')
  312
+        self.assertContains(response, 'id="id_Author_books-0-DELETE"')
  313
+
  314
+    def test_inline_change_fk_add_perm(self):
  315
+        permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
  316
+        self.user.user_permissions.add(permission)
  317
+        response = self.client.get(self.holder_change_url)
  318
+        # Add permission on inner2s, so we can add but not modify existing
  319
+        self.assertContains(response, '<h2>Inner2s</h2>')
  320
+        self.assertContains(response, 'Add another Inner2')
  321
+        # 3 extra forms only, not the existing instance form
  322
+        self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"')
  323
+        self.assertNotContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"')
  324
+
  325
+    def test_inline_change_fk_change_perm(self):
  326
+        permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct)
  327
+        self.user.user_permissions.add(permission)
  328
+        response = self.client.get(self.holder_change_url)
  329
+        # Change permission on inner2s, so we can change existing but not add new
  330
+        self.assertContains(response, '<h2>Inner2s</h2>')
  331
+        # Just the one form for existing instances
  332
+        self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"')
  333
+        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"')
  334
+        # max-num 0 means we can't add new ones
  335
+        self.assertContains(response, 'value="0" id="id_inner2_set-MAX_NUM_FORMS"')
  336
+
  337
+    def test_inline_change_fk_add_change_perm(self):
  338
+        permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
  339
+        self.user.user_permissions.add(permission)
  340
+        permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct)
  341
+        self.user.user_permissions.add(permission)
  342
+        response = self.client.get(self.holder_change_url)
  343
+        # Add/change perm, so we can add new and change existing
  344
+        self.assertContains(response, '<h2>Inner2s</h2>')
  345
+        # One form for existing instance and three extra for new
  346
+        self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
  347
+        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"')
  348
+
  349
+
  350
+    def test_inline_change_fk_change_del_perm(self):
  351
+        permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct)
  352
+        self.user.user_permissions.add(permission)
  353
+        permission = Permission.objects.get(codename='delete_inner2', content_type=self.inner_ct)
  354
+        self.user.user_permissions.add(permission)
  355
+        response = self.client.get(self.holder_change_url)
  356
+        # Change/delete perm on inner2s, so we can change/delete existing
  357
+        self.assertContains(response, '<h2>Inner2s</h2>')
  358
+        # One form for existing instance only, no new
  359
+        self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"')
  360
+        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"')
  361
+        self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
  362
+
  363
+
  364
+    def test_inline_change_fk_all_perms(self):
  365
+        permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
  366
+        self.user.user_permissions.add(permission)
  367
+        permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct)
  368
+        self.user.user_permissions.add(permission)
  369
+        permission = Permission.objects.get(codename='delete_inner2', content_type=self.inner_ct)
  370
+        self.user.user_permissions.add(permission)
  371
+        response = self.client.get(self.holder_change_url)
  372
+        # All perms on inner2s, so we can add/change/delete
  373
+        self.assertContains(response, '<h2>Inner2s</h2>')
  374
+        # One form for existing instance only, three for new
  375
+        self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
  376
+        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"')
  377
+        self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
22  tests/regressiontests/admin_ordering/tests.py
@@ -4,6 +4,18 @@
4 4
 
5 5
 from models import Band, Song, SongInlineDefaultOrdering, SongInlineNewOrdering, DynOrderingBandAdmin
6 6
 
  7
+
  8
+class MockRequest(object):
  9
+    pass
  10
+
  11
+class MockSuperUser(object):
  12
+    def has_perm(self, perm):
  13
+        return True
  14
+
  15
+request = MockRequest()
  16
+request.user = MockSuperUser()
  17
+
  18
+
7 19
 class TestAdminOrdering(TestCase):
8 20
     """
9 21
     Let's make sure that ModelAdmin.queryset uses the ordering we define in
@@ -26,7 +38,7 @@ def test_default_ordering(self):
26 38
         class.
27 39
         """
28 40
         ma = ModelAdmin(Band, None)
29  
-        names = [b.name for b in ma.queryset(None)]
  41
+        names = [b.name for b in ma.queryset(request)]
30 42
         self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names)
31 43
 
32 44
     def test_specified_ordering(self):
@@ -37,7 +49,7 @@ def test_specified_ordering(self):
37 49
         class BandAdmin(ModelAdmin):
38 50
             ordering = ('rank',) # default ordering is ('name',)
39 51
         ma = BandAdmin(Band, None)
40  
-        names = [b.name for b in ma.queryset(None)]
  52
+        names = [b.name for b in ma.queryset(request)]
41 53
         self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names)
42 54
 
43 55
     def test_dynamic_ordering(self):
@@ -79,7 +91,7 @@ def test_default_ordering(self):
79 91
         class.
80 92
         """
81 93
         inline = SongInlineDefaultOrdering(self.b, None)
82  
-        names = [s.name for s in inline.queryset(None)]
  94
+        names = [s.name for s in inline.queryset(request)]
83 95
         self.assertEqual([u'Dude (Looks Like a Lady)', u'Jaded', u'Pink'], names)
84 96
 
85 97
     def test_specified_ordering(self):
@@ -87,5 +99,5 @@ def test_specified_ordering(self):
87 99
         Let's check with ordering set to something different than the default.
88 100
         """
89 101
         inline = SongInlineNewOrdering(self.b, None)
90  
-        names = [s.name for s in inline.queryset(None)]
91  
-        self.assertEqual([u'Jaded', u'Pink', u'Dude (Looks Like a Lady)'], names)
  102
+        names = [s.name for s in inline.queryset(request)]
  103
+        self.assertEqual([u'Jaded', u'Pink', u'Dude (Looks Like a Lady)'], names)
22  tests/regressiontests/generic_inline_admin/tests.py
@@ -216,6 +216,18 @@ def test_no_deletion(self):
216 216
         formset = inline.get_formset(fake_request)
217 217
         self.assertFalse(formset.can_delete)
218 218
 
  219
+
  220
+class MockRequest(object):
  221
+    pass
  222
+
  223
+class MockSuperUser(object):
  224
+    def has_perm(self, perm):
  225
+        return True
  226
+
  227
+request = MockRequest()
  228
+request.user = MockSuperUser()
  229
+
  230
+
219 231
 class GenericInlineModelAdminTest(TestCase):
220 232
     urls = "regressiontests.generic_inline_admin.urls"
221 233
 
@@ -226,12 +238,12 @@ def test_get_formset_kwargs(self):
226 238
         media_inline = MediaInline(Media, AdminSite())
227 239
 
228 240
         # Create a formset with default arguments
229  
-        formset = media_inline.get_formset(None)
  241
+        formset = media_inline.get_formset(request)
230 242
         self.assertEqual(formset.max_num, None)
231 243
         self.assertEqual(formset.can_order, False)
232 244
 
233 245
         # Create a formset with custom keyword arguments
234  
-        formset = media_inline.get_formset(None, max_num=100, can_order=True)
  246
+        formset = media_inline.get_formset(request, max_num=100, can_order=True)
235 247
         self.assertEqual(formset.max_num, 100)
236 248
         self.assertEqual(formset.can_order, True)
237 249
 
@@ -241,9 +253,6 @@ def test_custom_form_meta_exclude_with_readonly(self):
241 253
         used in conjunction with `GenericInlineModelAdmin.readonly_fields`
242 254
         and when no `ModelAdmin.exclude` is defined.
243 255
         """
244  
-
245  
-        request = None
246  
-
247 256
         class MediaForm(ModelForm):
248 257
 
249 258
             class Meta:
@@ -272,9 +281,6 @@ def test_custom_form_meta_exclude(self):
272 281
         `ModelAdmin.exclude` or `GenericInlineModelAdmin.exclude` are defined.
273 282
         Refs #15907.
274 283
         """
275  
-
276  
-        request = None
277  
-
278 284
         # First with `GenericInlineModelAdmin`  -----------------
279 285
 
280 286
         class MediaForm(ModelForm):
17  tests/regressiontests/modeladmin/tests.py
@@ -19,9 +19,15 @@
19 19
     ValidationTestInlineModel)
20 20
 
21 21
 
22  
-# None of the following tests really depend on the content of the request,
23  
-# so we'll just pass in None.
24  
-request = None
  22
+class MockRequest(object):
  23
+    pass
  24
+
  25
+class MockSuperUser(object):
  26
+    def has_perm(self, perm):
  27
+        return True
  28
+
  29
+request = MockRequest()
  30
+request.user = MockSuperUser()
25 31
 
26 32
 
27 33
 class ModelAdminTests(TestCase):
@@ -357,9 +363,10 @@ class BandAdmin(ModelAdmin):
357 363
 
358 364
         concert = Concert.objects.create(main_band=self.band, opening_band=self.band, day=1)
359 365
         ma = BandAdmin(Band, self.site)
360  
-        fieldsets = list(ma.inline_instances[0].get_fieldsets(request))
  366
+        inline_instances = ma.get_inline_instances(request)
  367
+        fieldsets = list(inline_instances[0].get_fieldsets(request))
361 368
         self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport'])
362  
-        fieldsets = list(ma.inline_instances[0].get_fieldsets(request, ma.inline_instances[0].model))
  369
+        fieldsets = list(inline_instances[0].get_fieldsets(request, inline_instances[0].model))
363 370
         self.assertEqual(fieldsets[0][1]['fields'], ['day'])
364 371
 
365 372
     # radio_fields behavior ###########################################

0 notes on commit b1b1da1

Please sign in to comment.
Something went wrong with that request. Please try again.