mirror of https://github.com/django/django.git
Fixed #29637 -- Fixed admin change form crash if the user doesn’t have the add permission to a TabularInline.
Regression in 825f0beda8
.
This commit is contained in:
parent
d0928d6454
commit
64e1a271f5
|
@ -2055,12 +2055,6 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
can_add = self.has_add_permission(request, obj) if request else True
|
can_add = self.has_add_permission(request, obj) if request else True
|
||||||
|
|
||||||
class DeleteProtectedModelForm(base_model_form):
|
class DeleteProtectedModelForm(base_model_form):
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(DeleteProtectedModelForm, self).__init__(*args, **kwargs)
|
|
||||||
if not can_change and not self.instance._state.adding:
|
|
||||||
self.fields = {}
|
|
||||||
if not can_add and self.instance._state.adding:
|
|
||||||
self.fields = {}
|
|
||||||
|
|
||||||
def hand_clean_DELETE(self):
|
def hand_clean_DELETE(self):
|
||||||
"""
|
"""
|
||||||
|
@ -2097,6 +2091,14 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
self.hand_clean_DELETE()
|
self.hand_clean_DELETE()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def has_changed(self):
|
||||||
|
# Protect against unauthorized edits.
|
||||||
|
if not can_change and not self.instance._state.adding:
|
||||||
|
return False
|
||||||
|
if not can_add and self.instance._state.adding:
|
||||||
|
return False
|
||||||
|
return super().has_changed()
|
||||||
|
|
||||||
defaults['form'] = DeleteProtectedModelForm
|
defaults['form'] = DeleteProtectedModelForm
|
||||||
|
|
||||||
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
|
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
|
||||||
|
|
|
@ -24,3 +24,7 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed translation failure of ``DurationField``'s "overflow" error message
|
* Fixed translation failure of ``DurationField``'s "overflow" error message
|
||||||
(:ticket:`29623`).
|
(:ticket:`29623`).
|
||||||
|
|
||||||
|
* Fixed a regression where the admin change form crashed if the user doesn't
|
||||||
|
have the 'add' permission to a model that uses ``TabularInline``
|
||||||
|
(:ticket:`29637`).
|
||||||
|
|
|
@ -74,6 +74,10 @@ class InnerInline2(admin.StackedInline):
|
||||||
js = ('my_awesome_inline_scripts.js',)
|
js = ('my_awesome_inline_scripts.js',)
|
||||||
|
|
||||||
|
|
||||||
|
class InnerInline2Tabular(admin.TabularInline):
|
||||||
|
model = Inner2
|
||||||
|
|
||||||
|
|
||||||
class CustomNumberWidget(forms.NumberInput):
|
class CustomNumberWidget(forms.NumberInput):
|
||||||
class Media:
|
class Media:
|
||||||
js = ('custom_number.js',)
|
js = ('custom_number.js',)
|
||||||
|
@ -236,7 +240,7 @@ site.register(TitleCollection, inlines=[TitleInline])
|
||||||
# only ModelAdmin media
|
# only ModelAdmin media
|
||||||
site.register(Holder, HolderAdmin, inlines=[InnerInline])
|
site.register(Holder, HolderAdmin, inlines=[InnerInline])
|
||||||
# ModelAdmin and Inline media
|
# ModelAdmin and Inline media
|
||||||
site.register(Holder2, HolderAdmin, inlines=[InnerInline2])
|
site.register(Holder2, HolderAdmin, inlines=[InnerInline2, InnerInline2Tabular])
|
||||||
# only Inline media
|
# only Inline media
|
||||||
site.register(Holder3, inlines=[InnerInline3])
|
site.register(Holder3, inlines=[InnerInline3])
|
||||||
|
|
||||||
|
|
|
@ -588,9 +588,8 @@ class TestInlinePermissions(TestCase):
|
||||||
self.author_book_auto_m2m_intermediate_id = author_book_auto_m2m_intermediate.pk
|
self.author_book_auto_m2m_intermediate_id = author_book_auto_m2m_intermediate.pk
|
||||||
|
|
||||||
holder = Holder2.objects.create(dummy=13)
|
holder = Holder2.objects.create(dummy=13)
|
||||||
inner2 = Inner2.objects.create(dummy=42, holder=holder)
|
self.inner2 = Inner2.objects.create(dummy=42, holder=holder)
|
||||||
self.holder_change_url = reverse('admin:admin_inlines_holder2_change', args=(holder.id,))
|
self.holder_change_url = reverse('admin:admin_inlines_holder2_change', args=(holder.id,))
|
||||||
self.inner2_id = inner2.id
|
|
||||||
|
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
@ -684,7 +683,7 @@ class TestInlinePermissions(TestCase):
|
||||||
)
|
)
|
||||||
self.assertNotContains(
|
self.assertNotContains(
|
||||||
response,
|
response,
|
||||||
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2_id,
|
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2.id,
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -693,7 +692,7 @@ class TestInlinePermissions(TestCase):
|
||||||
self.user.user_permissions.add(permission)
|
self.user.user_permissions.add(permission)
|
||||||
response = self.client.get(self.holder_change_url)
|
response = self.client.get(self.holder_change_url)
|
||||||
# Change permission on inner2s, so we can change existing but not add new
|
# Change permission on inner2s, so we can change existing but not add new
|
||||||
self.assertContains(response, '<h2>Inner2s</h2>')
|
self.assertContains(response, '<h2>Inner2s</h2>', count=2)
|
||||||
# Just the one form for existing instances
|
# Just the one form for existing instances
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" value="1" name="inner2_set-TOTAL_FORMS">',
|
response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" value="1" name="inner2_set-TOTAL_FORMS">',
|
||||||
|
@ -701,7 +700,7 @@ class TestInlinePermissions(TestCase):
|
||||||
)
|
)
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2_id,
|
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2.id,
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
# max-num 0 means we can't add new ones
|
# max-num 0 means we can't add new ones
|
||||||
|
@ -710,6 +709,14 @@ class TestInlinePermissions(TestCase):
|
||||||
'<input type="hidden" id="id_inner2_set-MAX_NUM_FORMS" value="0" name="inner2_set-MAX_NUM_FORMS">',
|
'<input type="hidden" id="id_inner2_set-MAX_NUM_FORMS" value="0" name="inner2_set-MAX_NUM_FORMS">',
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
|
# TabularInline
|
||||||
|
self.assertContains(response, '<th class="required">Dummy</th>', html=True)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<input type="number" name="inner2_set-2-0-dummy" value="%s" '
|
||||||
|
'class="vIntegerField" id="id_inner2_set-2-0-dummy">' % self.inner2.dummy,
|
||||||
|
html=True,
|
||||||
|
)
|
||||||
|
|
||||||
def test_inline_change_fk_add_change_perm(self):
|
def test_inline_change_fk_add_change_perm(self):
|
||||||
permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
|
permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
|
||||||
|
@ -726,7 +733,7 @@ class TestInlinePermissions(TestCase):
|
||||||
)
|
)
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2_id,
|
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2.id,
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -746,7 +753,7 @@ class TestInlinePermissions(TestCase):
|
||||||
)
|
)
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2_id,
|
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2.id,
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
|
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
|
||||||
|
@ -760,7 +767,7 @@ class TestInlinePermissions(TestCase):
|
||||||
self.user.user_permissions.add(permission)
|
self.user.user_permissions.add(permission)
|
||||||
response = self.client.get(self.holder_change_url)
|
response = self.client.get(self.holder_change_url)
|
||||||
# All perms on inner2s, so we can add/change/delete
|
# All perms on inner2s, so we can add/change/delete
|
||||||
self.assertContains(response, '<h2>Inner2s</h2>')
|
self.assertContains(response, '<h2>Inner2s</h2>', count=2)
|
||||||
# One form for existing instance only, three for new
|
# One form for existing instance only, three for new
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
|
@ -769,10 +776,18 @@ class TestInlinePermissions(TestCase):
|
||||||
)
|
)
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2_id,
|
'<input type="hidden" id="id_inner2_set-0-id" value="%i" name="inner2_set-0-id">' % self.inner2.id,
|
||||||
html=True
|
html=True
|
||||||
)
|
)
|
||||||
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
|
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
|
||||||
|
# TabularInline
|
||||||
|
self.assertContains(response, '<th class="required">Dummy</th>', html=True)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<input type="number" name="inner2_set-2-0-dummy" value="%s" '
|
||||||
|
'class="vIntegerField" id="id_inner2_set-2-0-dummy">' % self.inner2.dummy,
|
||||||
|
html=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='admin_inlines.urls')
|
@override_settings(ROOT_URLCONF='admin_inlines.urls')
|
||||||
|
|
Loading…
Reference in New Issue