mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -270,6 +270,41 @@ class BaseModelAdmin(object): | |||||||
|             clean_lookup = LOOKUP_SEP.join(parts) |             clean_lookup = LOOKUP_SEP.join(parts) | ||||||
|             return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy |             return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy | ||||||
|  |  | ||||||
|  |     def has_add_permission(self, request): | ||||||
|  |         """ | ||||||
|  |         Returns True if the given request has permission to add an object. | ||||||
|  |         Can be overriden by the user in subclasses. | ||||||
|  |         """ | ||||||
|  |         opts = self.opts | ||||||
|  |         return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) | ||||||
|  |  | ||||||
|  |     def has_change_permission(self, request, obj=None): | ||||||
|  |         """ | ||||||
|  |         Returns True if the given request has permission to change the given | ||||||
|  |         Django model instance, the default implementation doesn't examine the | ||||||
|  |         `obj` parameter. | ||||||
|  |  | ||||||
|  |         Can be overriden by the user in subclasses. In such case it should | ||||||
|  |         return True if the given request has permission to change the `obj` | ||||||
|  |         model instance. If `obj` is None, this should return True if the given | ||||||
|  |         request has permission to change *any* object of the given type. | ||||||
|  |         """ | ||||||
|  |         opts = self.opts | ||||||
|  |         return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) | ||||||
|  |  | ||||||
|  |     def has_delete_permission(self, request, obj=None): | ||||||
|  |         """ | ||||||
|  |         Returns True if the given request has permission to change the given | ||||||
|  |         Django model instance, the default implementation doesn't examine the | ||||||
|  |         `obj` parameter. | ||||||
|  |  | ||||||
|  |         Can be overriden by the user in subclasses. In such case it should | ||||||
|  |         return True if the given request has permission to delete the `obj` | ||||||
|  |         model instance. If `obj` is None, this should return True if the given | ||||||
|  |         request has permission to delete *any* object of the given type. | ||||||
|  |         """ | ||||||
|  |         opts = self.opts | ||||||
|  |         return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) | ||||||
|  |  | ||||||
| class ModelAdmin(BaseModelAdmin): | class ModelAdmin(BaseModelAdmin): | ||||||
|     "Encapsulates all admin options and functionality for a given model." |     "Encapsulates all admin options and functionality for a given model." | ||||||
| @@ -307,10 +342,6 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|         self.model = model |         self.model = model | ||||||
|         self.opts = model._meta |         self.opts = model._meta | ||||||
|         self.admin_site = admin_site |         self.admin_site = admin_site | ||||||
|         self.inline_instances = [] |  | ||||||
|         for inline_class in self.inlines: |  | ||||||
|             inline_instance = inline_class(self.model, self.admin_site) |  | ||||||
|             self.inline_instances.append(inline_instance) |  | ||||||
|         if 'action_checkbox' not in self.list_display and self.actions is not None: |         if 'action_checkbox' not in self.list_display and self.actions is not None: | ||||||
|             self.list_display = ['action_checkbox'] +  list(self.list_display) |             self.list_display = ['action_checkbox'] +  list(self.list_display) | ||||||
|         if not self.list_display_links: |         if not self.list_display_links: | ||||||
| @@ -320,6 +351,21 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|                     break |                     break | ||||||
|         super(ModelAdmin, self).__init__() |         super(ModelAdmin, self).__init__() | ||||||
|  |  | ||||||
|  |     def get_inline_instances(self, request): | ||||||
|  |         inline_instances = [] | ||||||
|  |         for inline_class in self.inlines: | ||||||
|  |             inline = inline_class(self.model, self.admin_site) | ||||||
|  |             if request: | ||||||
|  |                 if not (inline.has_add_permission(request) or | ||||||
|  |                         inline.has_change_permission(request) or | ||||||
|  |                         inline.has_delete_permission(request)): | ||||||
|  |                     continue | ||||||
|  |                 if not inline.has_add_permission(request): | ||||||
|  |                     inline.max_num = 0 | ||||||
|  |             inline_instances.append(inline) | ||||||
|  |  | ||||||
|  |         return inline_instances | ||||||
|  |  | ||||||
|     def get_urls(self): |     def get_urls(self): | ||||||
|         from django.conf.urls import patterns, url |         from django.conf.urls import patterns, url | ||||||
|  |  | ||||||
| @@ -369,42 +415,6 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js']) |             js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js']) | ||||||
|         return forms.Media(js=[static('admin/js/%s' % url) for url in js]) |         return forms.Media(js=[static('admin/js/%s' % url) for url in js]) | ||||||
|  |  | ||||||
|     def has_add_permission(self, request): |  | ||||||
|         """ |  | ||||||
|         Returns True if the given request has permission to add an object. |  | ||||||
|         Can be overriden by the user in subclasses. |  | ||||||
|         """ |  | ||||||
|         opts = self.opts |  | ||||||
|         return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) |  | ||||||
|  |  | ||||||
|     def has_change_permission(self, request, obj=None): |  | ||||||
|         """ |  | ||||||
|         Returns True if the given request has permission to change the given |  | ||||||
|         Django model instance, the default implementation doesn't examine the |  | ||||||
|         `obj` parameter. |  | ||||||
|  |  | ||||||
|         Can be overriden by the user in subclasses. In such case it should |  | ||||||
|         return True if the given request has permission to change the `obj` |  | ||||||
|         model instance. If `obj` is None, this should return True if the given |  | ||||||
|         request has permission to change *any* object of the given type. |  | ||||||
|         """ |  | ||||||
|         opts = self.opts |  | ||||||
|         return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) |  | ||||||
|  |  | ||||||
|     def has_delete_permission(self, request, obj=None): |  | ||||||
|         """ |  | ||||||
|         Returns True if the given request has permission to change the given |  | ||||||
|         Django model instance, the default implementation doesn't examine the |  | ||||||
|         `obj` parameter. |  | ||||||
|  |  | ||||||
|         Can be overriden by the user in subclasses. In such case it should |  | ||||||
|         return True if the given request has permission to delete the `obj` |  | ||||||
|         model instance. If `obj` is None, this should return True if the given |  | ||||||
|         request has permission to delete *any* object of the given type. |  | ||||||
|         """ |  | ||||||
|         opts = self.opts |  | ||||||
|         return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) |  | ||||||
|  |  | ||||||
|     def get_model_perms(self, request): |     def get_model_perms(self, request): | ||||||
|         """ |         """ | ||||||
|         Returns a dict of all perms for this model. This dict has the keys |         Returns a dict of all perms for this model. This dict has the keys | ||||||
| @@ -500,7 +510,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             fields=self.list_editable, **defaults) |             fields=self.list_editable, **defaults) | ||||||
|  |  | ||||||
|     def get_formsets(self, request, obj=None): |     def get_formsets(self, request, obj=None): | ||||||
|         for inline in self.inline_instances: |         for inline in self.get_inline_instances(request): | ||||||
|             yield inline.get_formset(request, obj) |             yield inline.get_formset(request, obj) | ||||||
|  |  | ||||||
|     def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True): |     def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True): | ||||||
| @@ -914,6 +924,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|  |  | ||||||
|         ModelForm = self.get_form(request) |         ModelForm = self.get_form(request) | ||||||
|         formsets = [] |         formsets = [] | ||||||
|  |         inline_instances = self.get_inline_instances(request) | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|             form = ModelForm(request.POST, request.FILES) |             form = ModelForm(request.POST, request.FILES) | ||||||
|             if form.is_valid(): |             if form.is_valid(): | ||||||
| @@ -923,7 +934,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|                 form_validated = False |                 form_validated = False | ||||||
|                 new_object = self.model() |                 new_object = self.model() | ||||||
|             prefixes = {} |             prefixes = {} | ||||||
|             for FormSet, inline in zip(self.get_formsets(request), self.inline_instances): |             for FormSet, inline in zip(self.get_formsets(request), inline_instances): | ||||||
|                 prefix = FormSet.get_default_prefix() |                 prefix = FormSet.get_default_prefix() | ||||||
|                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 | ||||||
|                 if prefixes[prefix] != 1 or not prefix: |                 if prefixes[prefix] != 1 or not prefix: | ||||||
| @@ -951,8 +962,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|                     initial[k] = initial[k].split(",") |                     initial[k] = initial[k].split(",") | ||||||
|             form = ModelForm(initial=initial) |             form = ModelForm(initial=initial) | ||||||
|             prefixes = {} |             prefixes = {} | ||||||
|             for FormSet, inline in zip(self.get_formsets(request), |             for FormSet, inline in zip(self.get_formsets(request), inline_instances): | ||||||
|                                        self.inline_instances): |  | ||||||
|                 prefix = FormSet.get_default_prefix() |                 prefix = FormSet.get_default_prefix() | ||||||
|                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 | ||||||
|                 if prefixes[prefix] != 1 or not prefix: |                 if prefixes[prefix] != 1 or not prefix: | ||||||
| @@ -968,7 +978,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|         media = self.media + adminForm.media |         media = self.media + adminForm.media | ||||||
|  |  | ||||||
|         inline_admin_formsets = [] |         inline_admin_formsets = [] | ||||||
|         for inline, formset in zip(self.inline_instances, formsets): |         for inline, formset in zip(inline_instances, formsets): | ||||||
|             fieldsets = list(inline.get_fieldsets(request)) |             fieldsets = list(inline.get_fieldsets(request)) | ||||||
|             readonly = list(inline.get_readonly_fields(request)) |             readonly = list(inline.get_readonly_fields(request)) | ||||||
|             prepopulated = dict(inline.get_prepopulated_fields(request)) |             prepopulated = dict(inline.get_prepopulated_fields(request)) | ||||||
| @@ -1012,6 +1022,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|  |  | ||||||
|         ModelForm = self.get_form(request, obj) |         ModelForm = self.get_form(request, obj) | ||||||
|         formsets = [] |         formsets = [] | ||||||
|  |         inline_instances = self.get_inline_instances(request) | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|             form = ModelForm(request.POST, request.FILES, instance=obj) |             form = ModelForm(request.POST, request.FILES, instance=obj) | ||||||
|             if form.is_valid(): |             if form.is_valid(): | ||||||
| @@ -1021,8 +1032,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|                 form_validated = False |                 form_validated = False | ||||||
|                 new_object = obj |                 new_object = obj | ||||||
|             prefixes = {} |             prefixes = {} | ||||||
|             for FormSet, inline in zip(self.get_formsets(request, new_object), |             for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances): | ||||||
|                                        self.inline_instances): |  | ||||||
|                 prefix = FormSet.get_default_prefix() |                 prefix = FormSet.get_default_prefix() | ||||||
|                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 | ||||||
|                 if prefixes[prefix] != 1 or not prefix: |                 if prefixes[prefix] != 1 or not prefix: | ||||||
| @@ -1043,7 +1053,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|         else: |         else: | ||||||
|             form = ModelForm(instance=obj) |             form = ModelForm(instance=obj) | ||||||
|             prefixes = {} |             prefixes = {} | ||||||
|             for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances): |             for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances): | ||||||
|                 prefix = FormSet.get_default_prefix() |                 prefix = FormSet.get_default_prefix() | ||||||
|                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |                 prefixes[prefix] = prefixes.get(prefix, 0) + 1 | ||||||
|                 if prefixes[prefix] != 1 or not prefix: |                 if prefixes[prefix] != 1 or not prefix: | ||||||
| @@ -1059,7 +1069,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|         media = self.media + adminForm.media |         media = self.media + adminForm.media | ||||||
|  |  | ||||||
|         inline_admin_formsets = [] |         inline_admin_formsets = [] | ||||||
|         for inline, formset in zip(self.inline_instances, formsets): |         for inline, formset in zip(inline_instances, formsets): | ||||||
|             fieldsets = list(inline.get_fieldsets(request, obj)) |             fieldsets = list(inline.get_fieldsets(request, obj)) | ||||||
|             readonly = list(inline.get_readonly_fields(request, obj)) |             readonly = list(inline.get_readonly_fields(request, obj)) | ||||||
|             prepopulated = dict(inline.get_prepopulated_fields(request, obj)) |             prepopulated = dict(inline.get_prepopulated_fields(request, obj)) | ||||||
| @@ -1377,6 +1387,7 @@ class InlineModelAdmin(BaseModelAdmin): | |||||||
|         # if exclude is an empty list we use None, since that's the actual |         # if exclude is an empty list we use None, since that's the actual | ||||||
|         # default |         # default | ||||||
|         exclude = exclude or None |         exclude = exclude or None | ||||||
|  |         can_delete = self.can_delete and self.has_delete_permission(request, obj) | ||||||
|         defaults = { |         defaults = { | ||||||
|             "form": self.form, |             "form": self.form, | ||||||
|             "formset": self.formset, |             "formset": self.formset, | ||||||
| @@ -1386,7 +1397,7 @@ class InlineModelAdmin(BaseModelAdmin): | |||||||
|             "formfield_callback": partial(self.formfield_for_dbfield, request=request), |             "formfield_callback": partial(self.formfield_for_dbfield, request=request), | ||||||
|             "extra": self.extra, |             "extra": self.extra, | ||||||
|             "max_num": self.max_num, |             "max_num": self.max_num, | ||||||
|             "can_delete": self.can_delete, |             "can_delete": can_delete, | ||||||
|         } |         } | ||||||
|         defaults.update(kwargs) |         defaults.update(kwargs) | ||||||
|         return inlineformset_factory(self.parent_model, self.model, **defaults) |         return inlineformset_factory(self.parent_model, self.model, **defaults) | ||||||
| @@ -1398,6 +1409,44 @@ class InlineModelAdmin(BaseModelAdmin): | |||||||
|         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) |         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) | ||||||
|         return [(None, {'fields': fields})] |         return [(None, {'fields': fields})] | ||||||
|  |  | ||||||
|  |     def queryset(self, request): | ||||||
|  |         queryset = super(InlineModelAdmin, self).queryset(request) | ||||||
|  |         if not self.has_change_permission(request): | ||||||
|  |             queryset = queryset.none() | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|  |     def has_add_permission(self, request): | ||||||
|  |         if self.opts.auto_created: | ||||||
|  |             # We're checking the rights to an auto-created intermediate model, | ||||||
|  |             # which doesn't have its own individual permissions. The user needs | ||||||
|  |             # to have the change permission for the related model in order to | ||||||
|  |             # be able to do anything with the intermediate model. | ||||||
|  |             return self.has_change_permission(request) | ||||||
|  |         return request.user.has_perm( | ||||||
|  |             self.opts.app_label + '.' + self.opts.get_add_permission()) | ||||||
|  |  | ||||||
|  |     def has_change_permission(self, request, obj=None): | ||||||
|  |         opts = self.opts | ||||||
|  |         if opts.auto_created: | ||||||
|  |             # The model was auto-created as intermediary for a | ||||||
|  |             # ManyToMany-relationship, find the target model | ||||||
|  |             for field in opts.fields: | ||||||
|  |                 if field.rel and field.rel.to != self.parent_model: | ||||||
|  |                     opts = field.rel.to._meta | ||||||
|  |                     break | ||||||
|  |         return request.user.has_perm( | ||||||
|  |             opts.app_label + '.' + opts.get_change_permission()) | ||||||
|  |  | ||||||
|  |     def has_delete_permission(self, request, obj=None): | ||||||
|  |         if self.opts.auto_created: | ||||||
|  |             # We're checking the rights to an auto-created intermediate model, | ||||||
|  |             # which doesn't have its own individual permissions. The user needs | ||||||
|  |             # to have the change permission for the related model in order to | ||||||
|  |             # be able to do anything with the intermediate model. | ||||||
|  |             return self.has_change_permission(request, obj) | ||||||
|  |         return request.user.has_perm( | ||||||
|  |             self.opts.app_label + '.' + self.opts.get_delete_permission()) | ||||||
|  |  | ||||||
| class StackedInline(InlineModelAdmin): | class StackedInline(InlineModelAdmin): | ||||||
|     template = 'admin/edit_inline/stacked.html' |     template = 'admin/edit_inline/stacked.html' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -424,6 +424,7 @@ class GenericInlineModelAdmin(InlineModelAdmin): | |||||||
|             # GenericInlineModelAdmin doesn't define its own. |             # GenericInlineModelAdmin doesn't define its own. | ||||||
|             exclude.extend(self.form._meta.exclude) |             exclude.extend(self.form._meta.exclude) | ||||||
|         exclude = exclude or None |         exclude = exclude or None | ||||||
|  |         can_delete = self.can_delete and self.has_delete_permission(request, obj) | ||||||
|         defaults = { |         defaults = { | ||||||
|             "ct_field": self.ct_field, |             "ct_field": self.ct_field, | ||||||
|             "fk_field": self.ct_fk_field, |             "fk_field": self.ct_fk_field, | ||||||
| @@ -431,7 +432,7 @@ class GenericInlineModelAdmin(InlineModelAdmin): | |||||||
|             "formfield_callback": partial(self.formfield_for_dbfield, request=request), |             "formfield_callback": partial(self.formfield_for_dbfield, request=request), | ||||||
|             "formset": self.formset, |             "formset": self.formset, | ||||||
|             "extra": self.extra, |             "extra": self.extra, | ||||||
|             "can_delete": self.can_delete, |             "can_delete": can_delete, | ||||||
|             "can_order": False, |             "can_order": False, | ||||||
|             "fields": fields, |             "fields": fields, | ||||||
|             "max_num": self.max_num, |             "max_num": self.max_num, | ||||||
|   | |||||||
| @@ -1391,11 +1391,17 @@ adds some of its own (the shared features are actually defined in the | |||||||
| - :attr:`~ModelAdmin.ordering` | - :attr:`~ModelAdmin.ordering` | ||||||
| - :meth:`~ModelAdmin.queryset` | - :meth:`~ModelAdmin.queryset` | ||||||
|  |  | ||||||
|  | .. versionadded:: 1.4 | ||||||
|  |  | ||||||
|  | - :meth:`~ModelAdmin.has_add_permission` | ||||||
|  | - :meth:`~ModelAdmin.has_change_permission` | ||||||
|  | - :meth:`~ModelAdmin.has_delete_permission` | ||||||
|  |  | ||||||
| The ``InlineModelAdmin`` class adds: | The ``InlineModelAdmin`` class adds: | ||||||
|  |  | ||||||
| .. attribute:: InlineModelAdmin.model | .. attribute:: InlineModelAdmin.model | ||||||
|  |  | ||||||
|     The model in which the inline is using. This is required. |     The model which the inline is using. This is required. | ||||||
|  |  | ||||||
| .. attribute:: InlineModelAdmin.fk_name | .. attribute:: InlineModelAdmin.fk_name | ||||||
|  |  | ||||||
|   | |||||||
| @@ -128,6 +128,15 @@ A new :meth:`~django.contrib.admin.ModelAdmin.save_related` hook was added to | |||||||
| :mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how | :mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how | ||||||
| related objects are saved in the admin. | related objects are saved in the admin. | ||||||
|  |  | ||||||
|  | Admin inlines respect user permissions | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | Admin inlines will now only allow those actions for which the user has | ||||||
|  | permission. For ``ManyToMany`` relationships with an auto-created intermediate | ||||||
|  | model (which does not have its own permissions), the change permission for the | ||||||
|  | related model determines if the user has the permission to add, change or | ||||||
|  | delete relationships. | ||||||
|  |  | ||||||
| Tools for cryptographic signing | Tools for cryptographic signing | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| from django.contrib.admin.helpers import InlineAdminForm | from django.contrib.admin.helpers import InlineAdminForm | ||||||
|  | from django.contrib.auth.models import User, Permission | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
|  |  | ||||||
| # local test models | # local test models | ||||||
| from models import (Holder, Inner, Holder2, Inner2, Holder3, | from models import (Holder, Inner, Holder2, Inner2, Holder3, | ||||||
|     Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, |     Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, | ||||||
|     CapoFamiglia, Consigliere, SottoCapo) |     Author, Book) | ||||||
| from admin import InnerInline | from admin import InnerInline | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -141,7 +142,6 @@ class TestInline(TestCase): | |||||||
|                 '<input id="id_-2-0-name" type="text" class="vTextField" ' |                 '<input id="id_-2-0-name" type="text" class="vTextField" ' | ||||||
|                 'name="-2-0-name" maxlength="100" />') |                 'name="-2-0-name" maxlength="100" />') | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestInlineMedia(TestCase): | class TestInlineMedia(TestCase): | ||||||
|     urls = "regressiontests.admin_inlines.urls" |     urls = "regressiontests.admin_inlines.urls" | ||||||
|     fixtures = ['admin-views-users.xml'] |     fixtures = ['admin-views-users.xml'] | ||||||
| @@ -196,3 +196,182 @@ class TestInlineAdminForm(TestCase): | |||||||
|         iaf = InlineAdminForm(None, None, {}, {}, joe) |         iaf = InlineAdminForm(None, None, {}, {}, joe) | ||||||
|         parent_ct = ContentType.objects.get_for_model(Parent) |         parent_ct = ContentType.objects.get_for_model(Parent) | ||||||
|         self.assertEqual(iaf.original.content_type, parent_ct) |         self.assertEqual(iaf.original.content_type, parent_ct) | ||||||
|  |  | ||||||
|  | class TestInlinePermissions(TestCase): | ||||||
|  |     """ | ||||||
|  |     Make sure the admin respects permissions for objects that are edited | ||||||
|  |     inline. Refs #8060. | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     urls = "regressiontests.admin_inlines.urls" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.user = User(username='admin') | ||||||
|  |         self.user.is_staff = True | ||||||
|  |         self.user.is_active = True | ||||||
|  |         self.user.set_password('secret') | ||||||
|  |         self.user.save() | ||||||
|  |  | ||||||
|  |         self.author_ct = ContentType.objects.get_for_model(Author) | ||||||
|  |         self.holder_ct = ContentType.objects.get_for_model(Holder2) | ||||||
|  |         self.book_ct = ContentType.objects.get_for_model(Book) | ||||||
|  |         self.inner_ct = ContentType.objects.get_for_model(Inner2) | ||||||
|  |  | ||||||
|  |         # User always has permissions to add and change Authors, and Holders, | ||||||
|  |         # the main (parent) models of the inlines. Permissions on the inlines | ||||||
|  |         # vary per test. | ||||||
|  |         permission = Permission.objects.get(codename='add_author', content_type=self.author_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='change_author', content_type=self.author_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='add_holder2', content_type=self.holder_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='change_holder2', content_type=self.holder_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |  | ||||||
|  |         author = Author.objects.create(pk=1, name=u'The Author') | ||||||
|  |         author.books.create(name=u'The inline Book') | ||||||
|  |         self.author_change_url = '/admin/admin_inlines/author/%i/' % author.id | ||||||
|  |  | ||||||
|  |         holder = Holder2.objects.create(dummy=13) | ||||||
|  |         Inner2.objects.create(dummy=42, holder=holder) | ||||||
|  |         self.holder_change_url = '/admin/admin_inlines/holder2/%i/' % holder.id | ||||||
|  |  | ||||||
|  |         self.assertEqual( | ||||||
|  |             self.client.login(username='admin', password='secret'), | ||||||
|  |             True) | ||||||
|  |  | ||||||
|  |     def tearDown(self): | ||||||
|  |         self.client.logout() | ||||||
|  |  | ||||||
|  |     def test_inline_add_m2m_noperm(self): | ||||||
|  |         response = self.client.get('/admin/admin_inlines/author/add/') | ||||||
|  |         # No change permission on books, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Author-book relationships</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Author-Book Relationship') | ||||||
|  |         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_add_fk_noperm(self): | ||||||
|  |         response = self.client.get('/admin/admin_inlines/holder2/add/') | ||||||
|  |         # No permissions on Inner2s, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Inner2') | ||||||
|  |         self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_m2m_noperm(self): | ||||||
|  |         response = self.client.get(self.author_change_url) | ||||||
|  |         # No change permission on books, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Author-book relationships</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Author-Book Relationship') | ||||||
|  |         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_noperm(self): | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # No permissions on Inner2s, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Inner2') | ||||||
|  |         self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_add_m2m_add_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_book', content_type=self.book_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get('/admin/admin_inlines/author/add/') | ||||||
|  |         # No change permission on Books, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Author-book relationships</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Author-Book Relationship') | ||||||
|  |         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_add_fk_add_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get('/admin/admin_inlines/holder2/add/') | ||||||
|  |         # Add permission on inner2s, so we get the inline | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         self.assertContains(response, 'Add another Inner2') | ||||||
|  |         self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_m2m_add_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_book', content_type=self.book_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.author_change_url) | ||||||
|  |         # No change permission on books, so no inline | ||||||
|  |         self.assertNotContains(response, '<h2>Author-book relationships</h2>') | ||||||
|  |         self.assertNotContains(response, 'Add another Author-Book Relationship') | ||||||
|  |         self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') | ||||||
|  |         self.assertNotContains(response, 'id="id_Author_books-0-DELETE"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_m2m_change_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='change_book', content_type=self.book_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.author_change_url) | ||||||
|  |         # We have change perm on books, so we can add/change/delete inlines | ||||||
|  |         self.assertContains(response, '<h2>Author-book relationships</h2>') | ||||||
|  |         self.assertContains(response, 'Add another Author-Book Relationship') | ||||||
|  |         self.assertContains(response, 'value="4" id="id_Author_books-TOTAL_FORMS"') | ||||||
|  |         self.assertContains(response, '<input type="hidden" name="Author_books-0-id" value="1"') | ||||||
|  |         self.assertContains(response, 'id="id_Author_books-0-DELETE"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_add_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # Add permission on inner2s, so we can add but not modify existing | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         self.assertContains(response, 'Add another Inner2') | ||||||
|  |         # 3 extra forms only, not the existing instance form | ||||||
|  |         self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |         self.assertNotContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_change_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # Change permission on inner2s, so we can change existing but not add new | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         # Just the one form for existing instances | ||||||
|  |         self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |         self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"') | ||||||
|  |         # max-num 0 means we can't add new ones | ||||||
|  |         self.assertContains(response, 'value="0" id="id_inner2_set-MAX_NUM_FORMS"') | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_add_change_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # Add/change perm, so we can add new and change existing | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         # One form for existing instance and three extra for new | ||||||
|  |         self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |         self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_change_del_perm(self): | ||||||
|  |         permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='delete_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # Change/delete perm on inner2s, so we can change/delete existing | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         # One form for existing instance only, no new | ||||||
|  |         self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |         self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"') | ||||||
|  |         self.assertContains(response, 'id="id_inner2_set-0-DELETE"') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def test_inline_change_fk_all_perms(self): | ||||||
|  |         permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         permission = Permission.objects.get(codename='delete_inner2', content_type=self.inner_ct) | ||||||
|  |         self.user.user_permissions.add(permission) | ||||||
|  |         response = self.client.get(self.holder_change_url) | ||||||
|  |         # All perms on inner2s, so we can add/change/delete | ||||||
|  |         self.assertContains(response, '<h2>Inner2s</h2>') | ||||||
|  |         # One form for existing instance only, three for new | ||||||
|  |         self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') | ||||||
|  |         self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="1"') | ||||||
|  |         self.assertContains(response, 'id="id_inner2_set-0-DELETE"') | ||||||
|   | |||||||
| @@ -4,6 +4,18 @@ from django.contrib.admin.options import ModelAdmin | |||||||
|  |  | ||||||
| from models import Band, Song, SongInlineDefaultOrdering, SongInlineNewOrdering, DynOrderingBandAdmin | from models import Band, Song, SongInlineDefaultOrdering, SongInlineNewOrdering, DynOrderingBandAdmin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MockRequest(object): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | class MockSuperUser(object): | ||||||
|  |     def has_perm(self, perm): | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  | request = MockRequest() | ||||||
|  | request.user = MockSuperUser() | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAdminOrdering(TestCase): | class TestAdminOrdering(TestCase): | ||||||
|     """ |     """ | ||||||
|     Let's make sure that ModelAdmin.queryset uses the ordering we define in |     Let's make sure that ModelAdmin.queryset uses the ordering we define in | ||||||
| @@ -26,7 +38,7 @@ class TestAdminOrdering(TestCase): | |||||||
|         class. |         class. | ||||||
|         """ |         """ | ||||||
|         ma = ModelAdmin(Band, None) |         ma = ModelAdmin(Band, None) | ||||||
|         names = [b.name for b in ma.queryset(None)] |         names = [b.name for b in ma.queryset(request)] | ||||||
|         self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) |         self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) | ||||||
|  |  | ||||||
|     def test_specified_ordering(self): |     def test_specified_ordering(self): | ||||||
| @@ -37,7 +49,7 @@ class TestAdminOrdering(TestCase): | |||||||
|         class BandAdmin(ModelAdmin): |         class BandAdmin(ModelAdmin): | ||||||
|             ordering = ('rank',) # default ordering is ('name',) |             ordering = ('rank',) # default ordering is ('name',) | ||||||
|         ma = BandAdmin(Band, None) |         ma = BandAdmin(Band, None) | ||||||
|         names = [b.name for b in ma.queryset(None)] |         names = [b.name for b in ma.queryset(request)] | ||||||
|         self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) |         self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) | ||||||
|  |  | ||||||
|     def test_dynamic_ordering(self): |     def test_dynamic_ordering(self): | ||||||
| @@ -79,7 +91,7 @@ class TestInlineModelAdminOrdering(TestCase): | |||||||
|         class. |         class. | ||||||
|         """ |         """ | ||||||
|         inline = SongInlineDefaultOrdering(self.b, None) |         inline = SongInlineDefaultOrdering(self.b, None) | ||||||
|         names = [s.name for s in inline.queryset(None)] |         names = [s.name for s in inline.queryset(request)] | ||||||
|         self.assertEqual([u'Dude (Looks Like a Lady)', u'Jaded', u'Pink'], names) |         self.assertEqual([u'Dude (Looks Like a Lady)', u'Jaded', u'Pink'], names) | ||||||
|  |  | ||||||
|     def test_specified_ordering(self): |     def test_specified_ordering(self): | ||||||
| @@ -87,5 +99,5 @@ class TestInlineModelAdminOrdering(TestCase): | |||||||
|         Let's check with ordering set to something different than the default. |         Let's check with ordering set to something different than the default. | ||||||
|         """ |         """ | ||||||
|         inline = SongInlineNewOrdering(self.b, None) |         inline = SongInlineNewOrdering(self.b, None) | ||||||
|         names = [s.name for s in inline.queryset(None)] |         names = [s.name for s in inline.queryset(request)] | ||||||
|         self.assertEqual([u'Jaded', u'Pink', u'Dude (Looks Like a Lady)'], names) |         self.assertEqual([u'Jaded', u'Pink', u'Dude (Looks Like a Lady)'], names) | ||||||
| @@ -216,6 +216,18 @@ class NoInlineDeletionTest(TestCase): | |||||||
|         formset = inline.get_formset(fake_request) |         formset = inline.get_formset(fake_request) | ||||||
|         self.assertFalse(formset.can_delete) |         self.assertFalse(formset.can_delete) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MockRequest(object): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | class MockSuperUser(object): | ||||||
|  |     def has_perm(self, perm): | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  | request = MockRequest() | ||||||
|  | request.user = MockSuperUser() | ||||||
|  |  | ||||||
|  |  | ||||||
| class GenericInlineModelAdminTest(TestCase): | class GenericInlineModelAdminTest(TestCase): | ||||||
|     urls = "regressiontests.generic_inline_admin.urls" |     urls = "regressiontests.generic_inline_admin.urls" | ||||||
|  |  | ||||||
| @@ -226,12 +238,12 @@ class GenericInlineModelAdminTest(TestCase): | |||||||
|         media_inline = MediaInline(Media, AdminSite()) |         media_inline = MediaInline(Media, AdminSite()) | ||||||
|  |  | ||||||
|         # Create a formset with default arguments |         # Create a formset with default arguments | ||||||
|         formset = media_inline.get_formset(None) |         formset = media_inline.get_formset(request) | ||||||
|         self.assertEqual(formset.max_num, None) |         self.assertEqual(formset.max_num, None) | ||||||
|         self.assertEqual(formset.can_order, False) |         self.assertEqual(formset.can_order, False) | ||||||
|  |  | ||||||
|         # Create a formset with custom keyword arguments |         # Create a formset with custom keyword arguments | ||||||
|         formset = media_inline.get_formset(None, max_num=100, can_order=True) |         formset = media_inline.get_formset(request, max_num=100, can_order=True) | ||||||
|         self.assertEqual(formset.max_num, 100) |         self.assertEqual(formset.max_num, 100) | ||||||
|         self.assertEqual(formset.can_order, True) |         self.assertEqual(formset.can_order, True) | ||||||
|  |  | ||||||
| @@ -241,9 +253,6 @@ class GenericInlineModelAdminTest(TestCase): | |||||||
|         used in conjunction with `GenericInlineModelAdmin.readonly_fields` |         used in conjunction with `GenericInlineModelAdmin.readonly_fields` | ||||||
|         and when no `ModelAdmin.exclude` is defined. |         and when no `ModelAdmin.exclude` is defined. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         request = None |  | ||||||
|  |  | ||||||
|         class MediaForm(ModelForm): |         class MediaForm(ModelForm): | ||||||
|  |  | ||||||
|             class Meta: |             class Meta: | ||||||
| @@ -272,9 +281,6 @@ class GenericInlineModelAdminTest(TestCase): | |||||||
|         `ModelAdmin.exclude` or `GenericInlineModelAdmin.exclude` are defined. |         `ModelAdmin.exclude` or `GenericInlineModelAdmin.exclude` are defined. | ||||||
|         Refs #15907. |         Refs #15907. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         request = None |  | ||||||
|  |  | ||||||
|         # First with `GenericInlineModelAdmin`  ----------------- |         # First with `GenericInlineModelAdmin`  ----------------- | ||||||
|  |  | ||||||
|         class MediaForm(ModelForm): |         class MediaForm(ModelForm): | ||||||
|   | |||||||
| @@ -19,9 +19,15 @@ from models import (Band, Concert, ValidationTestModel, | |||||||
|     ValidationTestInlineModel) |     ValidationTestInlineModel) | ||||||
|  |  | ||||||
|  |  | ||||||
| # None of the following tests really depend on the content of the request, | class MockRequest(object): | ||||||
| # so we'll just pass in None. |     pass | ||||||
| request = None |  | ||||||
|  | class MockSuperUser(object): | ||||||
|  |     def has_perm(self, perm): | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  | request = MockRequest() | ||||||
|  | request.user = MockSuperUser() | ||||||
|  |  | ||||||
|  |  | ||||||
| class ModelAdminTests(TestCase): | class ModelAdminTests(TestCase): | ||||||
| @@ -357,9 +363,10 @@ class ModelAdminTests(TestCase): | |||||||
|  |  | ||||||
|         concert = Concert.objects.create(main_band=self.band, opening_band=self.band, day=1) |         concert = Concert.objects.create(main_band=self.band, opening_band=self.band, day=1) | ||||||
|         ma = BandAdmin(Band, self.site) |         ma = BandAdmin(Band, self.site) | ||||||
|         fieldsets = list(ma.inline_instances[0].get_fieldsets(request)) |         inline_instances = ma.get_inline_instances(request) | ||||||
|  |         fieldsets = list(inline_instances[0].get_fieldsets(request)) | ||||||
|         self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport']) |         self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport']) | ||||||
|         fieldsets = list(ma.inline_instances[0].get_fieldsets(request, ma.inline_instances[0].model)) |         fieldsets = list(inline_instances[0].get_fieldsets(request, inline_instances[0].model)) | ||||||
|         self.assertEqual(fieldsets[0][1]['fields'], ['day']) |         self.assertEqual(fieldsets[0][1]['fields'], ['day']) | ||||||
|  |  | ||||||
|     # radio_fields behavior ########################################### |     # radio_fields behavior ########################################### | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user