mirror of https://github.com/django/django.git
Fixed #29930 -- Allowed editing in admin with view-only inlines.
Co-authored-by: Tim Graham <timograham@gmail.com>
This commit is contained in:
parent
950112548e
commit
8245c99ee6
|
@ -1945,7 +1945,24 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'files': request.FILES,
|
||||
'save_as_new': '_saveasnew' in request.POST
|
||||
})
|
||||
formsets.append(FormSet(**formset_params))
|
||||
formset = FormSet(**formset_params)
|
||||
|
||||
def user_deleted_form(request, obj, formset, index):
|
||||
"""Return whether or not the user deleted the form."""
|
||||
return (
|
||||
inline.has_delete_permission(request, obj) and
|
||||
'{}-{}-DELETE'.format(formset.prefix, index) in request.POST
|
||||
)
|
||||
|
||||
# Bypass validation of each view-only inline form (since the form's
|
||||
# data won't be in request.POST), unless the form was deleted.
|
||||
if not inline.has_change_permission(request, obj):
|
||||
for index, form in enumerate(formset.initial_forms):
|
||||
if user_deleted_form(request, obj, formset, index):
|
||||
continue
|
||||
form._errors = {}
|
||||
form.cleaned_data = form.initial
|
||||
formsets.append(formset)
|
||||
inline_instances.append(inline)
|
||||
return formsets, inline_instances
|
||||
|
||||
|
|
|
@ -22,3 +22,7 @@ Bugfixes
|
|||
|
||||
* Fixed admin view-only change form crash when using
|
||||
``ModelAdmin.prepopulated_fields`` (:ticket:`29929`).
|
||||
|
||||
* Fixed "Please correct the errors below" error message when editing an object
|
||||
in the admin if the user only has the "view" permission on inlines
|
||||
(:ticket:`29930`).
|
||||
|
|
|
@ -1912,6 +1912,96 @@ class AdminViewPermissionsTest(TestCase):
|
|||
new_article = Article.objects.latest('id')
|
||||
self.assertRedirects(post, reverse('admin:admin_views_article_change', args=(new_article.pk,)))
|
||||
|
||||
def test_change_view_with_view_only_inlines(self):
|
||||
"""
|
||||
User with change permission to a section but view-only for inlines.
|
||||
"""
|
||||
self.viewuser.user_permissions.add(get_perm(Section, get_permission_codename('change', Section._meta)))
|
||||
self.client.force_login(self.viewuser)
|
||||
# GET shows inlines.
|
||||
response = self.client.get(reverse('admin:admin_views_section_change', args=(self.s1.pk,)))
|
||||
self.assertEqual(len(response.context['inline_admin_formsets']), 1)
|
||||
formset = response.context['inline_admin_formsets'][0]
|
||||
self.assertEqual(len(formset.forms), 3)
|
||||
# Valid POST changes the name.
|
||||
data = {
|
||||
'name': 'Can edit name with view-only inlines',
|
||||
'article_set-TOTAL_FORMS': 3,
|
||||
'article_set-INITIAL_FORMS': 3
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertRedirects(response, reverse('admin:admin_views_section_changelist'))
|
||||
self.assertEqual(Section.objects.get(pk=self.s1.pk).name, data['name'])
|
||||
# Invalid POST reshows inlines.
|
||||
del data['name']
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context['inline_admin_formsets']), 1)
|
||||
formset = response.context['inline_admin_formsets'][0]
|
||||
self.assertEqual(len(formset.forms), 3)
|
||||
|
||||
def test_change_view_with_view_and_add_inlines(self):
|
||||
"""User has view and add permissions on the inline model."""
|
||||
self.viewuser.user_permissions.add(get_perm(Section, get_permission_codename('change', Section._meta)))
|
||||
self.viewuser.user_permissions.add(get_perm(Article, get_permission_codename('add', Article._meta)))
|
||||
self.client.force_login(self.viewuser)
|
||||
# GET shows inlines.
|
||||
response = self.client.get(reverse('admin:admin_views_section_change', args=(self.s1.pk,)))
|
||||
self.assertEqual(len(response.context['inline_admin_formsets']), 1)
|
||||
formset = response.context['inline_admin_formsets'][0]
|
||||
self.assertEqual(len(formset.forms), 6)
|
||||
# Valid POST creates a new article.
|
||||
data = {
|
||||
'name': 'Can edit name with view-only inlines',
|
||||
'article_set-TOTAL_FORMS': 6,
|
||||
'article_set-INITIAL_FORMS': 3,
|
||||
'article_set-3-id': [''],
|
||||
'article_set-3-title': ['A title'],
|
||||
'article_set-3-content': ['Added content'],
|
||||
'article_set-3-date_0': ['2008-3-18'],
|
||||
'article_set-3-date_1': ['11:54:58'],
|
||||
'article_set-3-section': [str(self.s1.pk)],
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertRedirects(response, reverse('admin:admin_views_section_changelist'))
|
||||
self.assertEqual(Section.objects.get(pk=self.s1.pk).name, data['name'])
|
||||
self.assertEqual(Article.objects.count(), 4)
|
||||
# Invalid POST reshows inlines.
|
||||
del data['name']
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context['inline_admin_formsets']), 1)
|
||||
formset = response.context['inline_admin_formsets'][0]
|
||||
self.assertEqual(len(formset.forms), 6)
|
||||
|
||||
def test_change_view_with_view_and_delete_inlines(self):
|
||||
"""User has view and delete permissions on the inline model."""
|
||||
self.viewuser.user_permissions.add(get_perm(Section, get_permission_codename('change', Section._meta)))
|
||||
self.client.force_login(self.viewuser)
|
||||
data = {
|
||||
'name': 'Name is required.',
|
||||
'article_set-TOTAL_FORMS': 6,
|
||||
'article_set-INITIAL_FORMS': 3,
|
||||
'article_set-0-id': [str(self.a1.pk)],
|
||||
'article_set-0-DELETE': ['on'],
|
||||
}
|
||||
# Inline POST details are ignored without delete permission.
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertRedirects(response, reverse('admin:admin_views_section_changelist'))
|
||||
self.assertEqual(Article.objects.count(), 3)
|
||||
# Deletion successful when delete permission is added.
|
||||
self.viewuser.user_permissions.add(get_perm(Article, get_permission_codename('delete', Article._meta)))
|
||||
data = {
|
||||
'name': 'Name is required.',
|
||||
'article_set-TOTAL_FORMS': 6,
|
||||
'article_set-INITIAL_FORMS': 3,
|
||||
'article_set-0-id': [str(self.a1.pk)],
|
||||
'article_set-0-DELETE': ['on'],
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_section_change', args=(self.s1.pk,)), data)
|
||||
self.assertRedirects(response, reverse('admin:admin_views_section_changelist'))
|
||||
self.assertEqual(Article.objects.count(), 2)
|
||||
|
||||
def test_delete_view(self):
|
||||
"""Delete view should restrict access and actually delete items."""
|
||||
delete_dict = {'post': 'yes'}
|
||||
|
|
|
@ -89,6 +89,10 @@ class ModelAdminTests(TestCase):
|
|||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
self.request = MockRequest()
|
||||
self.request.POST = {
|
||||
'song_set-TOTAL_FORMS': 4,
|
||||
'song_set-INITIAL_FORMS': 1,
|
||||
}
|
||||
self.request.user = self.MockAddUser()
|
||||
self.ma = BandAdmin(Band, self.site)
|
||||
|
||||
|
|
Loading…
Reference in New Issue