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
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -2097,6 +2091,14 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
self.hand_clean_DELETE()
|
||||
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
|
||||
|
||||
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
|
||||
(: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',)
|
||||
|
||||
|
||||
class InnerInline2Tabular(admin.TabularInline):
|
||||
model = Inner2
|
||||
|
||||
|
||||
class CustomNumberWidget(forms.NumberInput):
|
||||
class Media:
|
||||
js = ('custom_number.js',)
|
||||
|
@ -236,7 +240,7 @@ site.register(TitleCollection, inlines=[TitleInline])
|
|||
# only ModelAdmin media
|
||||
site.register(Holder, HolderAdmin, inlines=[InnerInline])
|
||||
# ModelAdmin and Inline media
|
||||
site.register(Holder2, HolderAdmin, inlines=[InnerInline2])
|
||||
site.register(Holder2, HolderAdmin, inlines=[InnerInline2, InnerInline2Tabular])
|
||||
# only Inline media
|
||||
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
|
||||
|
||||
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.inner2_id = inner2.id
|
||||
|
||||
self.client.force_login(self.user)
|
||||
|
||||
|
@ -684,7 +683,7 @@ class TestInlinePermissions(TestCase):
|
|||
)
|
||||
self.assertNotContains(
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -693,7 +692,7 @@ class TestInlinePermissions(TestCase):
|
|||
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>')
|
||||
self.assertContains(response, '<h2>Inner2s</h2>', count=2)
|
||||
# Just the one form for existing instances
|
||||
self.assertContains(
|
||||
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(
|
||||
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
|
||||
)
|
||||
# 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">',
|
||||
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):
|
||||
permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct)
|
||||
|
@ -726,7 +733,7 @@ class TestInlinePermissions(TestCase):
|
|||
)
|
||||
self.assertContains(
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -746,7 +753,7 @@ class TestInlinePermissions(TestCase):
|
|||
)
|
||||
self.assertContains(
|
||||
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
|
||||
)
|
||||
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
|
||||
|
@ -760,7 +767,7 @@ class TestInlinePermissions(TestCase):
|
|||
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>')
|
||||
self.assertContains(response, '<h2>Inner2s</h2>', count=2)
|
||||
# One form for existing instance only, three for new
|
||||
self.assertContains(
|
||||
response,
|
||||
|
@ -769,10 +776,18 @@ class TestInlinePermissions(TestCase):
|
|||
)
|
||||
self.assertContains(
|
||||
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
|
||||
)
|
||||
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')
|
||||
|
|
Loading…
Reference in New Issue