diff options
| author | Nick Pope <nick.pope@flightdataservices.com> | 2021-01-13 16:19:22 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-13 17:19:22 +0100 |
| commit | 920448539631b52dcee53bd32a880abbc9de18bd (patch) | |
| tree | 03dd52fd206088302de11e0b485b420726718a4a /tests | |
| parent | 83fcfc9ec8610540948815e127101f1206562ead (diff) | |
Fixed #16117 -- Added decorators for admin action and display functions.
Refs #25134, #32099.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/admin_changelist/admin.py | 3 | ||||
| -rw-r--r-- | tests/admin_checks/tests.py | 4 | ||||
| -rw-r--r-- | tests/admin_utils/models.py | 3 | ||||
| -rw-r--r-- | tests/admin_utils/tests.py | 11 | ||||
| -rw-r--r-- | tests/admin_views/admin.py | 54 | ||||
| -rw-r--r-- | tests/admin_views/models.py | 15 | ||||
| -rw-r--r-- | tests/admin_views/tests.py | 12 | ||||
| -rw-r--r-- | tests/modeladmin/test_actions.py | 15 | ||||
| -rw-r--r-- | tests/modeladmin/test_checks.py | 11 |
9 files changed, 76 insertions, 52 deletions
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 890919557e..4f23296eca 100644 --- a/tests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -19,6 +19,7 @@ class EventAdmin(admin.ModelAdmin): date_hierarchy = 'date' list_display = ['event_date_func'] + @admin.display def event_date_func(self, event): return event.date @@ -171,6 +172,6 @@ class EmptyValueChildAdmin(admin.ModelAdmin): empty_value_display = '-empty-' list_display = ('name', 'age_display', 'age') + @admin.display(empty_value='†') def age_display(self, obj): return obj.age - age_display.empty_value_display = '†' diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py index 5744dc1b39..139b6dec59 100644 --- a/tests/admin_checks/tests.py +++ b/tests/admin_checks/tests.py @@ -692,6 +692,7 @@ class SystemChecksTestCase(SimpleTestCase): self.assertEqual(errors, []) def test_readonly_on_method(self): + @admin.display def my_function(obj): pass @@ -705,6 +706,7 @@ class SystemChecksTestCase(SimpleTestCase): class SongAdmin(admin.ModelAdmin): readonly_fields = ("readonly_method_on_modeladmin",) + @admin.display def readonly_method_on_modeladmin(self, obj): pass @@ -717,6 +719,7 @@ class SystemChecksTestCase(SimpleTestCase): def __getattr__(self, item): if item == "dynamic_method": + @admin.display def method(obj): pass return method @@ -777,6 +780,7 @@ class SystemChecksTestCase(SimpleTestCase): def test_extra(self): class SongAdmin(admin.ModelAdmin): + @admin.display def awesome_song(self, instance): if instance.title == "Born to Run": return "Best Ever!" diff --git a/tests/admin_utils/models.py b/tests/admin_utils/models.py index fda1380b23..e57c3926b5 100644 --- a/tests/admin_utils/models.py +++ b/tests/admin_utils/models.py @@ -1,3 +1,4 @@ +from django.contrib import admin from django.db import models from django.utils.translation import gettext_lazy as _ @@ -28,9 +29,9 @@ class Article(models.Model): def test_from_model(self): return "nothing" + @admin.display(description='not What you Expect') def test_from_model_with_override(self): return "nothing" - test_from_model_with_override.short_description = "not What you Expect" class ArticleProxy(Article): diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py index ce9f94dbb9..a74449bdc0 100644 --- a/tests/admin_utils/tests.py +++ b/tests/admin_utils/tests.py @@ -3,6 +3,7 @@ from decimal import Decimal from django import forms from django.conf import settings +from django.contrib import admin from django.contrib.admin import helpers from django.contrib.admin.utils import ( NestedObjects, display_for_field, display_for_value, flatten, @@ -293,9 +294,9 @@ class UtilsTests(SimpleTestCase): self.assertEqual(label_for_field('site_id', Article), 'Site id') class MockModelAdmin: + @admin.display(description='not Really the Model') def test_from_model(self, obj): return "nothing" - test_from_model.short_description = "not Really the Model" self.assertEqual( label_for_field("test_from_model", Article, model_admin=MockModelAdmin), @@ -323,13 +324,11 @@ class UtilsTests(SimpleTestCase): label_for_field('nonexistent', Article, form=ArticleForm()), def test_label_for_property(self): - # NOTE: cannot use @property decorator, because of - # AttributeError: 'property' object has no attribute 'short_description' class MockModelAdmin: - def my_property(self): + @property + @admin.display(description='property short description') + def test_from_property(self): return "this if from property" - my_property.short_description = 'property short description' - test_from_property = property(my_property) self.assertEqual( label_for_field("test_from_property", Article, model_admin=MockModelAdmin), diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 1140f03496..925da71982 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -49,6 +49,7 @@ from .models import ( ) +@admin.display(ordering='date') def callable_year(dt_value): try: return dt_value.year @@ -56,9 +57,6 @@ def callable_year(dt_value): return None -callable_year.admin_order_field = 'date' - - class ArticleInline(admin.TabularInline): model = Article fk_name = 'section' @@ -138,25 +136,24 @@ class ArticleAdmin(ArticleAdminWithExtraUrl): # These orderings aren't particularly useful but show that expressions can # be used for admin_order_field. + @admin.display(ordering=models.F('date') + datetime.timedelta(days=3)) def order_by_expression(self, obj): return obj.model_year - order_by_expression.admin_order_field = models.F('date') + datetime.timedelta(days=3) + @admin.display(ordering=models.F('date')) def order_by_f_expression(self, obj): return obj.model_year - order_by_f_expression.admin_order_field = models.F('date') + @admin.display(ordering=models.F('date').asc(nulls_last=True)) def order_by_orderby_expression(self, obj): return obj.model_year - order_by_orderby_expression.admin_order_field = models.F('date').asc(nulls_last=True) def changelist_view(self, request): return super().changelist_view(request, extra_context={'extra_var': 'Hello!'}) + @admin.display(ordering='date', description=None) def modeladmin_year(self, obj): return obj.date.year - modeladmin_year.admin_order_field = 'date' - modeladmin_year.short_description = None def delete_model(self, request, obj): EmailMessage( @@ -216,6 +213,7 @@ class ThingAdmin(admin.ModelAdmin): class InquisitionAdmin(admin.ModelAdmin): list_display = ('leader', 'country', 'expected', 'sketch') + @admin.display def sketch(self, obj): # A method with the same name as a reverse accessor. return 'list-display-sketch' @@ -280,6 +278,7 @@ class SubscriberAdmin(admin.ModelAdmin): SubscriberAdmin.overridden = True super().delete_queryset(request, queryset) + @admin.action def mail_admin(self, request, selected): EmailMessage( 'Greetings from a ModelAdmin action', @@ -289,6 +288,7 @@ class SubscriberAdmin(admin.ModelAdmin): ).send() +@admin.action(description='External mail (Another awesome action)') def external_mail(modeladmin, request, selected): EmailMessage( 'Greetings from a function action', @@ -298,32 +298,23 @@ def external_mail(modeladmin, request, selected): ).send() -external_mail.short_description = 'External mail (Another awesome action)' - - +@admin.action(description='Redirect to (Awesome action)') def redirect_to(modeladmin, request, selected): from django.http import HttpResponseRedirect return HttpResponseRedirect('/some-where-else/') -redirect_to.short_description = 'Redirect to (Awesome action)' - - +@admin.action(description='Download subscription') def download(modeladmin, request, selected): buf = StringIO('This is the content of the file') return StreamingHttpResponse(FileWrapper(buf)) -download.short_description = 'Download subscription' - - +@admin.action(description='No permission to run') def no_perm(modeladmin, request, selected): return HttpResponse(content='No permission to perform this action', status=403) -no_perm.short_description = 'No permission to run' - - class ExternalSubscriberAdmin(admin.ModelAdmin): actions = [redirect_to, external_mail, download, no_perm] @@ -441,6 +432,7 @@ class LinkInline(admin.TabularInline): readonly_fields = ("posted", "multiline", "readonly_link_content") + @admin.display def multiline(self, instance): return "InlineMultiline\ntest\nstring" @@ -501,19 +493,22 @@ class PostAdmin(admin.ModelAdmin): LinkInline ] + @admin.display def coolness(self, instance): if instance.pk: return "%d amount of cool." % instance.pk else: return "Unknown coolness." + @admin.display(description='Value in $US') def value(self, instance): return 1000 - value.short_description = 'Value in $US' + @admin.display def multiline(self, instance): return "Multiline\ntest\nstring" + @admin.display def multiline_html(self, instance): return mark_safe("Multiline<br>\nhtml<br>\ncontent") @@ -655,9 +650,9 @@ class ComplexSortedPersonAdmin(admin.ModelAdmin): list_display = ('name', 'age', 'is_employee', 'colored_name') ordering = ('name',) + @admin.display(ordering='name') def colored_name(self, obj): return format_html('<span style="color: #ff00ff;">{}</span>', obj.name) - colored_name.admin_order_field = 'name' class PluggableSearchPersonAdmin(admin.ModelAdmin): @@ -706,20 +701,18 @@ class AdminOrderedModelMethodAdmin(admin.ModelAdmin): class AdminOrderedAdminMethodAdmin(admin.ModelAdmin): + @admin.display(ordering='order') def some_admin_order(self, obj): return obj.order - some_admin_order.admin_order_field = 'order' ordering = ('order',) list_display = ('stuff', 'some_admin_order') +@admin.display(ordering='order') def admin_ordered_callable(obj): return obj.order -admin_ordered_callable.admin_order_field = 'order' - - class AdminOrderedCallableAdmin(admin.ModelAdmin): ordering = ('order',) list_display = ('stuff', admin_ordered_callable) @@ -814,6 +807,7 @@ class UnchangeableObjectAdmin(admin.ModelAdmin): return [p for p in urlpatterns if p.name and not p.name.endswith("_change")] +@admin.display def callable_on_unknown(obj): return obj.unknown @@ -831,21 +825,27 @@ class MessageTestingAdmin(admin.ModelAdmin): actions = ["message_debug", "message_info", "message_success", "message_warning", "message_error", "message_extra_tags"] + @admin.action def message_debug(self, request, selected): self.message_user(request, "Test debug", level="debug") + @admin.action def message_info(self, request, selected): self.message_user(request, "Test info", level="info") + @admin.action def message_success(self, request, selected): self.message_user(request, "Test success", level="success") + @admin.action def message_warning(self, request, selected): self.message_user(request, "Test warning", level="warning") + @admin.action def message_error(self, request, selected): self.message_user(request, "Test error", level="error") + @admin.action def message_extra_tags(self, request, selected): self.message_user(request, "Test tags", extra_tags="extra_tag") @@ -1156,9 +1156,9 @@ class ArticleAdmin6(admin.ModelAdmin): ) sortable_by = ('date', callable_year) + @admin.display(ordering='date') def modeladmin_year(self, obj): return obj.date.year - modeladmin_year.admin_order_field = 'date' class ActorAdmin6(admin.ModelAdmin): diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 5224f5f91a..f449ad792b 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -3,6 +3,7 @@ import os import tempfile import uuid +from django.contrib import admin from django.contrib.auth.models import User from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation, @@ -45,20 +46,18 @@ class Article(models.Model): def __str__(self): return self.title + @admin.display(ordering='date', description='') def model_year(self): return self.date.year - model_year.admin_order_field = 'date' - model_year.short_description = '' + @admin.display(ordering='-date', description='') def model_year_reversed(self): return self.date.year - model_year_reversed.admin_order_field = '-date' - model_year_reversed.short_description = '' - def property_year(self): + @property + @admin.display(ordering='date') + def model_property_year(self): return self.date.year - property_year.admin_order_field = 'date' - model_property_year = property(property_year) @property def model_month(self): @@ -746,9 +745,9 @@ class AdminOrderedModelMethod(models.Model): order = models.IntegerField() stuff = models.CharField(max_length=200) + @admin.display(ordering='order') def some_order(self): return self.order - some_order.admin_order_field = 'order' class AdminOrderedAdminMethod(models.Model): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 297760f807..0b2415cdb8 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -7,6 +7,7 @@ from urllib.parse import parse_qsl, urljoin, urlparse import pytz +from django.contrib import admin from django.contrib.admin import AdminSite, ModelAdmin from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.models import ADDITION, DELETION, LogEntry @@ -751,6 +752,17 @@ class AdminViewBasicTest(AdminViewBasicTestCase): response = self.client.get(reverse('admin:admin_views_post_changelist')) self.assertContains(response, 'icon-unknown.svg') + def test_display_decorator_with_boolean_and_empty_value(self): + msg = ( + 'The boolean and empty_value arguments to the @display decorator ' + 'are mutually exclusive.' + ) + with self.assertRaisesMessage(ValueError, msg): + class BookAdmin(admin.ModelAdmin): + @admin.display(boolean=True, empty_value='(Missing)') + def is_published(self, obj): + return obj.publish_date is not None + def test_i18n_language_non_english_default(self): """ Check if the JavaScript i18n view returns an empty language catalog diff --git a/tests/modeladmin/test_actions.py b/tests/modeladmin/test_actions.py index f7de725ffc..b61641c0c9 100644 --- a/tests/modeladmin/test_actions.py +++ b/tests/modeladmin/test_actions.py @@ -27,6 +27,7 @@ class AdminActionsTests(TestCase): class BandAdmin(admin.ModelAdmin): actions = ['custom_action'] + @admin.action def custom_action(modeladmin, request, queryset): pass @@ -60,6 +61,7 @@ class AdminActionsTests(TestCase): class AdminBase(admin.ModelAdmin): actions = ['custom_action'] + @admin.action def custom_action(modeladmin, request, queryset): pass @@ -78,13 +80,14 @@ class AdminActionsTests(TestCase): self.assertEqual(action_names, ['delete_selected']) def test_global_actions_description(self): + @admin.action(description='Site-wide admin action 1.') def global_action_1(modeladmin, request, queryset): pass + @admin.action def global_action_2(modeladmin, request, queryset): pass - global_action_1.short_description = 'Site-wide admin action 1.' admin_site = admin.AdminSite() admin_site.add_action(global_action_1) admin_site.add_action(global_action_2) @@ -103,30 +106,28 @@ class AdminActionsTests(TestCase): ) def test_actions_replace_global_action(self): + @admin.action(description='Site-wide admin action 1.') def global_action_1(modeladmin, request, queryset): pass + @admin.action(description='Site-wide admin action 2.') def global_action_2(modeladmin, request, queryset): pass - global_action_1.short_description = 'Site-wide admin action 1.' - global_action_2.short_description = 'Site-wide admin action 2.' admin.site.add_action(global_action_1, name='custom_action_1') admin.site.add_action(global_action_2, name='custom_action_2') + @admin.action(description='Local admin action 1.') def custom_action_1(modeladmin, request, queryset): pass - custom_action_1.short_description = 'Local admin action 1.' - class BandAdmin(admin.ModelAdmin): actions = [custom_action_1, 'custom_action_2'] + @admin.action(description='Local admin action 2.') def custom_action_2(self, request, queryset): pass - custom_action_2.short_description = 'Local admin action 2.' - ma = BandAdmin(Band, admin.site) self.assertEqual(ma.check(), []) self.assertEqual( diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py index 308f4a19eb..d81cc3dd32 100644 --- a/tests/modeladmin/test_checks.py +++ b/tests/modeladmin/test_checks.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib import admin from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline from django.contrib.admin.sites import AdminSite @@ -499,10 +500,12 @@ class ListDisplayTests(CheckTestCase): ) def test_valid_case(self): + @admin.display def a_callable(obj): pass class TestModelAdmin(ModelAdmin): + @admin.display def a_method(self, obj): pass list_display = ('name', 'decade_published_in', 'a_method', a_callable) @@ -563,10 +566,12 @@ class ListDisplayLinksCheckTests(CheckTestCase): ) def test_valid_case(self): + @admin.display def a_callable(obj): pass class TestModelAdmin(ModelAdmin): + @admin.display def a_method(self, obj): pass list_display = ('name', 'decade_published_in', 'a_method', a_callable) @@ -1417,11 +1422,10 @@ class AutocompleteFieldsTests(CheckTestCase): class ActionsCheckTests(CheckTestCase): def test_custom_permissions_require_matching_has_method(self): + @admin.action(permissions=['custom']) def custom_permission_action(modeladmin, request, queryset): pass - custom_permission_action.allowed_permissions = ('custom',) - class BandAdmin(ModelAdmin): actions = (custom_permission_action,) @@ -1433,6 +1437,7 @@ class ActionsCheckTests(CheckTestCase): ) def test_actions_not_unique(self): + @admin.action def action(modeladmin, request, queryset): pass @@ -1447,9 +1452,11 @@ class ActionsCheckTests(CheckTestCase): ) def test_actions_unique(self): + @admin.action def action1(modeladmin, request, queryset): pass + @admin.action def action2(modeladmin, request, queryset): pass |
