瀏覽代碼

Fixed #28517 -- Fixed admin delete confirmation view crash when related models don't have a delete permission.

Paulo 7 年之前
父節點
當前提交
d1286a8a68
共有 4 個文件被更改,包括 51 次插入12 次删除
  1. 4 4
      django/contrib/admin/utils.py
  2. 19 5
      tests/admin_views/admin.py
  3. 8 0
      tests/admin_views/models.py
  4. 20 3
      tests/admin_views/tests.py

+ 4 - 4
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('{}: <a href="{}">{}</a>',
                                capfirst(opts.verbose_name),

+ 19 - 5
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)

+ 8 - 0
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)

+ 20 - 3
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, '<h2>Summary</h2>')
+        self.assertContains(response, '<li>Read only pizzas: 1</li>')
+        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',))