From d1286a8a689e31435c07534fee7b61f41cea37f8 Mon Sep 17 00:00:00 2001 From: Paulo Date: Tue, 22 Aug 2017 19:48:55 -0400 Subject: [PATCH] Fixed #28517 -- Fixed admin delete confirmation view crash when related models don't have a delete permission. --- django/contrib/admin/utils.py | 8 ++++---- tests/admin_views/admin.py | 24 +++++++++++++++++++----- tests/admin_views/models.py | 8 ++++++++ tests/admin_views/tests.py | 23 ++++++++++++++++++++--- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index a2518a78a1..545954af54 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -149,10 +149,10 @@ def get_deleted_objects(objs, opts, user, admin_site, using): # Change url doesn't exist -- don't display link to edit return no_edit_link - p = '%s.%s' % (opts.app_label, - get_permission_codename('delete', opts)) - if not user.has_perm(p): - perms_needed.add(opts.verbose_name) + if 'delete' in opts.default_permissions: + p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) + if not user.has_perm(p): + perms_needed.add(opts.verbose_name) # Display a link to the admin page. return format_html('{}: {}', capfirst(opts.verbose_name), diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 1e4124fca5..12087865b4 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -36,11 +36,11 @@ from .models import ( Person, Persona, Picture, Pizza, Plot, PlotDetails, PlotProxy, PluggableSearchPerson, Podcast, Post, PrePopulatedPost, PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, - ReadablePizza, Recipe, Recommendation, Recommender, ReferencedByGenRel, - ReferencedByInline, ReferencedByParent, RelatedPrepopulated, - RelatedWithUUIDPKModel, Report, Reservation, Restaurant, - RowLevelChangePermissionModel, Section, ShortMessage, Simple, Sketch, - State, Story, StumpJoke, Subscriber, SuperVillain, Telegram, Thing, + ReadablePizza, ReadOnlyPizza, Recipe, Recommendation, Recommender, + ReferencedByGenRel, ReferencedByInline, ReferencedByParent, + RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Reservation, + Restaurant, RowLevelChangePermissionModel, Section, ShortMessage, Simple, + Sketch, State, Story, StumpJoke, Subscriber, SuperVillain, Telegram, Thing, Topping, UnchangeableObject, UndeletableObject, UnorderedObject, UserMessenger, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour, ) @@ -502,6 +502,19 @@ class StudentAdmin(admin.ModelAdmin): search_fields = ('name',) +class ReadOnlyPizzaAdmin(admin.ModelAdmin): + readonly_fields = ('name', 'toppings') + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return True + + def has_delete_permission(self, request, obj=None): + return True + + class WorkHourAdmin(admin.ModelAdmin): list_display = ('datum', 'employee') list_filter = ('employee',) @@ -1001,6 +1014,7 @@ site.register(Book, inlines=[ChapterInline]) site.register(Promo) site.register(ChapterXtra1, ChapterXtra1Admin) site.register(Pizza, PizzaAdmin) +site.register(ReadOnlyPizza, ReadOnlyPizzaAdmin) site.register(ReadablePizza) site.register(Topping, ToppingAdmin) site.register(Album, AlbumAdmin) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index fc229cf3bd..38565b0a7f 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -582,6 +582,14 @@ class ReadablePizza(Pizza): proxy = True +# No default permissions are created for this model and both name and toppings +# are readonly for this model's admin. +class ReadOnlyPizza(Pizza): + class Meta: + proxy = True + default_permissions = () + + class Album(models.Model): owner = models.ForeignKey(User, models.SET_NULL, null=True, blank=True) title = models.CharField(max_length=30) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 5bcc1942a5..b8093c2971 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -48,9 +48,9 @@ from .models import ( MainPrepopulated, Media, ModelWithStringPrimaryKey, OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post, - PrePopulatedPost, Promo, Question, ReadablePizza, Recommendation, - Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, Report, - Restaurant, RowLevelChangePermissionModel, SecretHideout, Section, + PrePopulatedPost, Promo, Question, ReadablePizza, ReadOnlyPizza, + Recommendation, Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, + Report, Restaurant, RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage, Simple, State, Story, SuperSecretHideout, SuperVillain, Telegram, TitleTranslation, Topping, UnchangeableObject, UndeletableObject, UnorderedObject, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour, @@ -1820,6 +1820,23 @@ class AdminViewPermissionsTest(TestCase): logged = LogEntry.objects.get(content_type=article_ct, action_flag=DELETION) self.assertEqual(logged.object_id, str(self.a1.pk)) + def test_delete_view_with_no_default_permissions(self): + """ + The delete view allows users to delete collected objects without a + 'delete' permission (ReadOnlyPizza.Meta.default_permissions is empty). + """ + pizza = ReadOnlyPizza.objects.create(name='Double Cheese') + delete_url = reverse('admin:admin_views_readonlypizza_delete', args=(pizza.pk,)) + self.client.force_login(self.adduser) + response = self.client.get(delete_url) + self.assertContains(response, 'admin_views/readonlypizza/%s/' % pizza.pk) + self.assertContains(response, '

Summary

') + self.assertContains(response, '
  • Read only pizzas: 1
  • ') + self.assertEqual(response.status_code, 200) + post = self.client.post(delete_url, {'post': 'yes'}) + self.assertRedirects(post, reverse('admin:admin_views_readonlypizza_changelist')) + self.assertEqual(ReadOnlyPizza.objects.count(), 0) + def test_delete_view_nonexistent_obj(self): self.client.force_login(self.deleteuser) url = reverse('admin:admin_views_article_delete', args=('nonexistent',))