summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorNick Pope <nick.pope@flightdataservices.com>2021-01-13 16:19:22 +0000
committerGitHub <noreply@github.com>2021-01-13 17:19:22 +0100
commit920448539631b52dcee53bd32a880abbc9de18bd (patch)
tree03dd52fd206088302de11e0b485b420726718a4a /docs
parent83fcfc9ec8610540948815e127101f1206562ead (diff)
Fixed #16117 -- Added decorators for admin action and display functions.
Refs #25134, #32099.
Diffstat (limited to 'docs')
-rw-r--r--docs/intro/tutorial07.txt16
-rw-r--r--docs/ref/contrib/admin/actions.txt87
-rw-r--r--docs/ref/contrib/admin/index.txt212
-rw-r--r--docs/releases/3.2.txt22
-rw-r--r--docs/topics/i18n/translation.txt10
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
-------------------------------------