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 /docs | |
| parent | 83fcfc9ec8610540948815e127101f1206562ead (diff) | |
Fixed #16117 -- Added decorators for admin action and display functions.
Refs #25134, #32099.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/intro/tutorial07.txt | 16 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/actions.txt | 87 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/index.txt | 212 | ||||
| -rw-r--r-- | docs/releases/3.2.txt | 22 | ||||
| -rw-r--r-- | docs/topics/i18n/translation.txt | 10 |
5 files changed, 261 insertions, 86 deletions
diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index 9bcc2b40a3..545e40baa3 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -228,22 +228,26 @@ of an arbitrary method is not supported. Also note that the column header for underscores replaced with spaces), and that each line contains the string representation of the output. -You can improve that by giving that method (in :file:`polls/models.py`) a few -attributes, as follows: +You can improve that by using the :func:`~django.contrib.admin.display` +decorator on that method (in :file:`polls/models.py`), as follows: .. code-block:: python :caption: polls/models.py + from django.contrib import admin + class Question(models.Model): # ... + @admin.display( + boolean=True, + ordering='pub_date', + description='Published recently?', + ) def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now - was_published_recently.admin_order_field = 'pub_date' - was_published_recently.boolean = True - was_published_recently.short_description = 'Published recently?' -For more information on these method properties, see +For more information on the properties configurable via the decorator, see :attr:`~django.contrib.admin.ModelAdmin.list_display`. Edit your :file:`polls/admin.py` file again and add an improvement to the diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index e75aa86afa..650eda9b4f 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -99,18 +99,32 @@ That's actually all there is to writing an action! However, we'll take one more optional-but-useful step and give the action a "nice" title in the admin. By default, this action would appear in the action list as "Make published" -- the function name, with underscores replaced by spaces. That's fine, but we -can provide a better, more human-friendly name by giving the -``make_published`` function a ``short_description`` attribute:: +can provide a better, more human-friendly name by using the +:func:`~django.contrib.admin.action` decorator on the ``make_published`` +function:: + from django.contrib import admin + + ... + + @admin.action(description='Mark selected stories as published') def make_published(modeladmin, request, queryset): queryset.update(status='p') - make_published.short_description = "Mark selected stories as published" .. note:: - This might look familiar; the admin's ``list_display`` option uses the - same technique to provide human-readable descriptions for callback - functions registered there, too. + This might look familiar; the admin's + :attr:`~django.contrib.admin.ModelAdmin.list_display` option uses a similar + technique with the :func:`~django.contrib.admin.display` decorator to + provide human-readable descriptions for callback functions registered + there, too. + +.. versionchanged:: 3.2 + + The ``description`` argument to the :func:`~django.contrib.admin.action` + decorator is equivalent to setting the ``short_description`` attribute on + the action function directly in previous versions. Setting the attribute + directly is still supported for backward compatibility. Adding actions to the :class:`ModelAdmin` ----------------------------------------- @@ -122,9 +136,9 @@ the action and its registration would look like:: from django.contrib import admin from myapp.models import Article + @admin.action(description='Mark selected stories as published') def make_published(modeladmin, request, queryset): queryset.update(status='p') - make_published.short_description = "Mark selected stories as published" class ArticleAdmin(admin.ModelAdmin): list_display = ['title', 'status'] @@ -171,9 +185,9 @@ You can do it like this:: actions = ['make_published'] + @admin.action(description='Mark selected stories as published') def make_published(self, request, queryset): queryset.update(status='p') - make_published.short_description = "Mark selected stories as published" Notice first that we've moved ``make_published`` into a method and renamed the ``modeladmin`` parameter to ``self``, and second that we've now put the string @@ -364,20 +378,20 @@ Setting permissions for actions ------------------------------- Actions may limit their availability to users with specific permissions by -setting an ``allowed_permissions`` attribute on the action function:: +wrapping the action function with the :func:`~django.contrib.admin.action` +decorator and passing the ``permissions`` argument:: + @admin.action(permissions=['change']) def make_published(modeladmin, request, queryset): queryset.update(status='p') - make_published.allowed_permissions = ('change',) The ``make_published()`` action will only be available to users that pass the :meth:`.ModelAdmin.has_change_permission` check. -If ``allowed_permissions`` has more than one permission, the action will be -available as long as the user passes at least one of the checks. +If ``permissions`` has more than one permission, the action will be available +as long as the user passes at least one of the checks. -Available values for ``allowed_permissions`` and the corresponding method -checks are: +Available values for ``permissions`` and the corresponding method checks are: - ``'add'``: :meth:`.ModelAdmin.has_add_permission` - ``'change'``: :meth:`.ModelAdmin.has_change_permission` @@ -395,12 +409,55 @@ For example:: class ArticleAdmin(admin.ModelAdmin): actions = ['make_published'] + @admin.action(permissions=['publish']) def make_published(self, request, queryset): queryset.update(status='p') - make_published.allowed_permissions = ('publish',) def has_publish_permission(self, request): """Does the user have the publish permission?""" opts = self.opts codename = get_permission_codename('publish', opts) return request.user.has_perm('%s.%s' % (opts.app_label, codename)) + +.. versionchanged:: 3.2 + + The ``permissions`` argument to the :func:`~django.contrib.admin.action` + decorator is equivalent to setting the ``allowed_permissions`` attribute on + the action function directly in previous versions. Setting the attribute + directly is still supported for backward compatibility. + +The ``action`` decorator +======================== + +.. function:: action(*, permissions=None, description=None) + + .. versionadded:: 3.2 + + This decorator can be used for setting specific attributes on custom action + functions that can be used with + :attr:`~django.contrib.admin.ModelAdmin.actions`:: + + @admin.action( + permissions=['publish'], + description='Mark selected stories as published', + ) + def make_published(self, request, queryset): + queryset.update(status='p') + + This is equivalent to setting some attributes (with the original, longer + names) on the function directly:: + + def make_published(self, request, queryset): + queryset.update(status='p') + make_published.allowed_permissions = ['publish'] + make_published.short_description = 'Mark selected stories as published' + + Use of this decorator is not compulsory to make an action function, but it + can be useful to use it without arguments as a marker in your source to + identify the purpose of the function:: + + @admin.action + def make_inactive(self, request, queryset): + queryset.update(is_active=False) + + In this case it will add no attributes to the function. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 040b2ada85..aaf5134395 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -256,10 +256,17 @@ subclass:: class AuthorAdmin(admin.ModelAdmin): fields = ('name', 'title', 'view_birth_date') + @admin.display(empty_value='???') def view_birth_date(self, obj): return obj.birth_date - view_birth_date.empty_value_display = '???' + .. versionchanged:: 3.2 + + The ``empty_value`` argument to the + :func:`~django.contrib.admin.display` decorator is equivalent to + setting the ``empty_value_display`` attribute on the display function + directly in previous versions. Setting the attribute directly is still + supported for backward compatibility. .. attribute:: ModelAdmin.exclude @@ -551,7 +558,9 @@ subclass:: If you don't set ``list_display``, the admin site will display a single column that displays the ``__str__()`` representation of each object. - There are four types of values that can be used in ``list_display``: + There are four types of values that can be used in ``list_display``. All + but the simplest may use the :func:`~django.contrib.admin.display` + decorator is used to customize how the field is presented: * The name of a model field. For example:: @@ -560,9 +569,9 @@ subclass:: * A callable that accepts one argument, the model instance. For example:: + @admin.display(description='Name') def upper_case_name(obj): return ("%s %s" % (obj.first_name, obj.last_name)).upper() - upper_case_name.short_description = 'Name' class PersonAdmin(admin.ModelAdmin): list_display = (upper_case_name,) @@ -573,9 +582,9 @@ subclass:: class PersonAdmin(admin.ModelAdmin): list_display = ('upper_case_name',) + @admin.display(description='Name') def upper_case_name(self, obj): return ("%s %s" % (obj.first_name, obj.last_name)).upper() - upper_case_name.short_description = 'Name' * A string representing a model attribute or method (without any required arguments). For example:: @@ -587,9 +596,9 @@ subclass:: name = models.CharField(max_length=50) birthday = models.DateField() + @admin.display(description='Birth decade') def decade_born_in(self): return '%d’s' % (self.birthday.year // 10 * 10) - decade_born_in.short_description = 'Birth decade' class PersonAdmin(admin.ModelAdmin): list_display = ('name', 'decade_born_in') @@ -624,6 +633,7 @@ subclass:: last_name = models.CharField(max_length=50) color_code = models.CharField(max_length=6) + @admin.display def colored_name(self): return format_html( '<span style="color: #{};">{} {}</span>', @@ -637,7 +647,17 @@ subclass:: * As some examples have already demonstrated, when using a callable, a model method, or a ``ModelAdmin`` method, you can customize the column's - title by adding a ``short_description`` attribute to the callable. + title by wrapping the callable with the + :func:`~django.contrib.admin.display` decorator and passing the + ``description`` argument. + + .. versionchanged:: 3.2 + + The ``description`` argument to the + :func:`~django.contrib.admin.display` decorator is equivalent to + setting the ``short_description`` attribute on the display function + directly in previous versions. Setting the attribute directly is + still supported for backward compatibility. * If the value of a field is ``None``, an empty string, or an iterable without elements, Django will display ``-`` (a dash). You can override @@ -657,17 +677,23 @@ subclass:: class PersonAdmin(admin.ModelAdmin): list_display = ('name', 'birth_date_view') + @admin.display(empty_value='unknown') def birth_date_view(self, obj): return obj.birth_date - birth_date_view.empty_value_display = 'unknown' + .. versionchanged:: 3.2 + + The ``empty_value`` argument to the + :func:`~django.contrib.admin.display` decorator is equivalent to + setting the ``empty_value_display`` attribute on the display function + directly in previous versions. Setting the attribute directly is + still supported for backward compatibility. * If the string given is a method of the model, ``ModelAdmin`` or a callable that returns ``True``, ``False``, or ``None``, Django will - display a pretty "yes", "no", or "unknown" icon if you give the method a - ``boolean`` attribute whose value is ``True``. - - Here's a full example model:: + display a pretty "yes", "no", or "unknown" icon if you wrap the method + with the :func:`~django.contrib.admin.display` decorator passing the + ``boolean`` argument with the value set to ``True``:: from django.contrib import admin from django.db import models @@ -676,13 +702,21 @@ subclass:: first_name = models.CharField(max_length=50) birthday = models.DateField() + @admin.display(boolean=True) def born_in_fifties(self): return 1950 <= self.birthday.year < 1960 - born_in_fifties.boolean = True class PersonAdmin(admin.ModelAdmin): list_display = ('name', 'born_in_fifties') + .. versionchanged:: 3.2 + + The ``boolean`` argument to the + :func:`~django.contrib.admin.display` decorator is equivalent to + setting the ``boolean`` attribute on the display function directly in + previous versions. Setting the attribute directly is still supported + for backward compatibility. + * The ``__str__()`` method is just as valid in ``list_display`` as any other model method, so it's perfectly OK to do this:: @@ -692,44 +726,42 @@ subclass:: fields can't be used in sorting (because Django does all the sorting at the database level). - However, if an element of ``list_display`` represents a certain - database field, you can indicate this fact by setting the - ``admin_order_field`` attribute of the item. + However, if an element of ``list_display`` represents a certain database + field, you can indicate this fact by using the + :func:`~django.contrib.admin.display` decorator on the method, passing + the ``ordering`` argument:: - For example:: - - from django.contrib import admin - from django.db import models - from django.utils.html import format_html - - class Person(models.Model): - first_name = models.CharField(max_length=50) - color_code = models.CharField(max_length=6) + from django.contrib import admin + from django.db import models + from django.utils.html import format_html - def colored_first_name(self): - return format_html( - '<span style="color: #{};">{}</span>', - self.color_code, - self.first_name, - ) + class Person(models.Model): + first_name = models.CharField(max_length=50) + color_code = models.CharField(max_length=6) - colored_first_name.admin_order_field = 'first_name' + @admin.display(ordering='first_name') + def colored_first_name(self): + return format_html( + '<span style="color: #{};">{}</span>', + self.color_code, + self.first_name, + ) - class PersonAdmin(admin.ModelAdmin): - list_display = ('first_name', 'colored_first_name') + class PersonAdmin(admin.ModelAdmin): + list_display = ('first_name', 'colored_first_name') The above will tell Django to order by the ``first_name`` field when trying to sort by ``colored_first_name`` in the admin. - To indicate descending order with ``admin_order_field`` you can use a - hyphen prefix on the field name. Using the above example, this would - look like:: + To indicate descending order with the ``ordering`` argument you can use a + hyphen prefix on the field name. Using the above example, this would look + like:: - colored_first_name.admin_order_field = '-first_name' + @admin.display(ordering='-first_name') - ``admin_order_field`` supports query lookups to sort by values on related - models. This example includes an "author first name" column in the list - display and allows sorting it by first name:: + The ``ordering`` argument supports query lookups to sort by values on + related models. This example includes an "author first name" column in + the list display and allows sorting it by first name:: class Blog(models.Model): title = models.CharField(max_length=255) @@ -738,13 +770,12 @@ subclass:: class BlogAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'author_first_name') + @admin.display(ordering='author__first_name') def author_first_name(self, obj): return obj.author.first_name - author_first_name.admin_order_field = 'author__first_name' - - :doc:`Query expressions </ref/models/expressions>` may be used in - ``admin_order_field``. For example:: + :doc:`Query expressions </ref/models/expressions>` may be used with the + ``ordering`` argument:: from django.db.models import Value from django.db.models.functions import Concat @@ -753,32 +784,47 @@ subclass:: first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) + @admin.display(ordering=Concat('first_name', Value(' '), 'last_name')) def full_name(self): return self.first_name + ' ' + self.last_name - full_name.admin_order_field = Concat('first_name', Value(' '), 'last_name') - * Elements of ``list_display`` can also be properties. Please note however, - that due to the way properties work in Python, setting - ``short_description`` or ``admin_order_field`` on a property is only - possible when using the ``property()`` function and **not** with the - ``@property`` decorator. + .. versionchanged:: 3.2 + + The ``ordering`` argument to the + :func:`~django.contrib.admin.display` decorator is equivalent to + setting the ``admin_order_field`` attribute on the display function + directly in previous versions. Setting the attribute directly is + still supported for backward compatibility. - For example:: + * Elements of ``list_display`` can also be properties:: class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) - def my_property(self): + @property + @admin.display( + ordering='last_name', + description='Full name of the person', + ) + def full_name(self): return self.first_name + ' ' + self.last_name - my_property.short_description = "Full name of the person" - my_property.admin_order_field = 'last_name' - - full_name = property(my_property) class PersonAdmin(admin.ModelAdmin): list_display = ('full_name',) + Note that ``@property`` must be above ``@display``. If you're using the + old way -- setting the display-related attributes directly rather than + using the :func:`~django.contrib.admin.display` decorator -- be aware + that the ``property()`` function and **not** the ``@property`` decorator + must be used:: + + def my_property(self): + return self.first_name + ' ' + self.last_name + my_property.short_description = "Full name of the person" + my_property.admin_order_field = 'last_name' + + full_name = property(my_property) * The field names in ``list_display`` will also appear as CSS classes in the HTML output, in the form of ``column-<field_name>`` on each ``<th>`` @@ -1239,6 +1285,8 @@ subclass:: class PersonAdmin(admin.ModelAdmin): readonly_fields = ('address_report',) + # description functions like a model field's verbose_name + @admin.display(description='Address') def address_report(self, instance): # assuming get_full_address() returns a list of strings # for each line of the address and you want to separate each @@ -1249,9 +1297,6 @@ subclass:: ((line,) for line in instance.get_full_address()), ) or mark_safe("<span class='errors'>I can't determine this address.</span>") - # short_description functions like a model field's verbose_name - address_report.short_description = "Address" - .. attribute:: ModelAdmin.save_as Set ``save_as`` to enable a "save as new" feature on admin change forms. @@ -1360,8 +1405,9 @@ subclass:: .. attribute:: ModelAdmin.sortable_by By default, the change list page allows sorting by all model fields (and - callables that have the ``admin_order_field`` property) specified in - :attr:`list_display`. + callables that use the ``ordering`` argument to the + :func:`~django.contrib.admin.display` decorator or have the + ``admin_order_field`` attribute) specified in :attr:`list_display`. If you want to disable sorting for some columns, set ``sortable_by`` to a collection (e.g. ``list``, ``tuple``, or ``set``) of the subset of @@ -3337,6 +3383,50 @@ The action in the examples above match the last part of the URL names for object which has an ``app_label`` and ``model_name`` attributes and is usually supplied by the admin views for the current model. +The ``display`` decorator +========================= + +.. function:: display(*, boolean=None, ordering=None, description=None, empty_value=None) + + .. versionadded:: 3.2 + + This decorator can be used for setting specific attributes on custom + display functions that can be used with + :attr:`~django.contrib.admin.ModelAdmin.list_display` or + :attr:`~django.contrib.admin.ModelAdmin.readonly_fields`:: + + @admin.display( + boolean=True, + ordering='-publish_date', + description='Is Published?', + ) + def is_published(self, obj): + return obj.publish_date is not None + + This is equivalent to setting some attributes (with the original, longer + names) on the function directly:: + + def is_published(self, obj): + return obj.publish_date is not None + is_published.boolean = True + is_published.admin_order_field = '-publish_date' + is_published.short_description = 'Is Published?' + + Also note that the ``empty_value`` decorator parameter maps to the + ``empty_value_display`` attribute assigned directly to the function. It + cannot be used in conjunction with ``boolean`` -- they are mutually + exclusive. + + Use of this decorator is not compulsory to make a display function, but it + can be useful to use it without arguments as a marker in your source to + identify the purpose of the function:: + + @admin.display + def published_year(self, obj): + return obj.publish_date.year + + In this case it will add no attributes to the function. + .. currentmodule:: django.contrib.admin.views.decorators The ``staff_member_required`` decorator diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 57ab1baf34..66607916e0 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -141,6 +141,28 @@ Django </topics/cache>`. .. _pymemcache: https://pypi.org/project/pymemcache/ +New decorators for the admin site +--------------------------------- + +The new :func:`~django.contrib.admin.display` decorator allows for easily +adding options to custom display functions that can be used with +:attr:`~django.contrib.admin.ModelAdmin.list_display` or +:attr:`~django.contrib.admin.ModelAdmin.readonly_fields`. + +Likewise, the new :func:`~django.contrib.admin.action` decorator allows for +easily adding options to action functions that can be used with +:attr:`~django.contrib.admin.ModelAdmin.actions`. + +Using the ``@display`` decorator has the advantage that it is now +possible to use the ``@property`` decorator when needing to specify attributes +on the custom method. Prior to this it was necessary to use the ``property()`` +function instead after assigning the required attributes to the method. + +Using decorators has the advantage that these options are more discoverable as +they can be suggested by completion utilities in code editors. They are merely +a convenience and still set the same attributes on the functions under the +hood. + Minor features -------------- diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 7a7bf85ebc..1819359725 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -389,12 +389,14 @@ verbose names Django performs by looking at the model's class name:: verbose_name = _('my thing') verbose_name_plural = _('my things') -Model methods ``short_description`` attribute values -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Model methods ``description`` argument to the ``@display`` decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For model methods, you can provide translations to Django and the admin site -with the ``short_description`` attribute:: +with the ``description`` argument to the :func:`~django.contrib.admin.display` +decorator:: + from django.contrib import admin from django.db import models from django.utils.translation import gettext_lazy as _ @@ -406,9 +408,9 @@ with the ``short_description`` attribute:: verbose_name=_('kind'), ) + @admin.display(description=_('Is it a mouse?')) def is_mouse(self): return self.kind.type == MOUSE_TYPE - is_mouse.short_description = _('Is it a mouse?') Working with lazy translation objects ------------------------------------- |
