Fixed #16117 -- Added decorators for admin action and display functions.
Refs #25134, #32099.
This commit is contained in:
parent
83fcfc9ec8
commit
9204485396
|
@ -1,4 +1,4 @@
|
||||||
from django.contrib.admin.decorators import register
|
from django.contrib.admin.decorators import action, display, register
|
||||||
from django.contrib.admin.filters import (
|
from django.contrib.admin.filters import (
|
||||||
AllValuesFieldListFilter, BooleanFieldListFilter, ChoicesFieldListFilter,
|
AllValuesFieldListFilter, BooleanFieldListFilter, ChoicesFieldListFilter,
|
||||||
DateFieldListFilter, EmptyFieldListFilter, FieldListFilter, ListFilter,
|
DateFieldListFilter, EmptyFieldListFilter, FieldListFilter, ListFilter,
|
||||||
|
@ -11,10 +11,10 @@ from django.contrib.admin.sites import AdminSite, site
|
||||||
from django.utils.module_loading import autodiscover_modules
|
from django.utils.module_loading import autodiscover_modules
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"register", "ModelAdmin", "HORIZONTAL", "VERTICAL", "StackedInline",
|
"action", "display", "register", "ModelAdmin", "HORIZONTAL", "VERTICAL",
|
||||||
"TabularInline", "AdminSite", "site", "ListFilter", "SimpleListFilter",
|
"StackedInline", "TabularInline", "AdminSite", "site", "ListFilter",
|
||||||
"FieldListFilter", "BooleanFieldListFilter", "RelatedFieldListFilter",
|
"SimpleListFilter", "FieldListFilter", "BooleanFieldListFilter",
|
||||||
"ChoicesFieldListFilter", "DateFieldListFilter",
|
"RelatedFieldListFilter", "ChoicesFieldListFilter", "DateFieldListFilter",
|
||||||
"AllValuesFieldListFilter", "EmptyFieldListFilter",
|
"AllValuesFieldListFilter", "EmptyFieldListFilter",
|
||||||
"RelatedOnlyFieldListFilter", "autodiscover",
|
"RelatedOnlyFieldListFilter", "autodiscover",
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,12 +4,17 @@ Built-in, globally-available admin actions.
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
|
from django.contrib.admin.decorators import action
|
||||||
from django.contrib.admin.utils import model_ngettext
|
from django.contrib.admin.utils import model_ngettext
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
|
|
||||||
|
|
||||||
|
@action(
|
||||||
|
permissions=['delete'],
|
||||||
|
description=gettext_lazy('Delete selected %(verbose_name_plural)s'),
|
||||||
|
)
|
||||||
def delete_selected(modeladmin, request, queryset):
|
def delete_selected(modeladmin, request, queryset):
|
||||||
"""
|
"""
|
||||||
Default action which deletes the selected objects.
|
Default action which deletes the selected objects.
|
||||||
|
@ -73,7 +78,3 @@ def delete_selected(modeladmin, request, queryset):
|
||||||
"admin/%s/delete_selected_confirmation.html" % app_label,
|
"admin/%s/delete_selected_confirmation.html" % app_label,
|
||||||
"admin/delete_selected_confirmation.html"
|
"admin/delete_selected_confirmation.html"
|
||||||
], context)
|
], context)
|
||||||
|
|
||||||
|
|
||||||
delete_selected.allowed_permissions = ('delete',)
|
|
||||||
delete_selected.short_description = gettext_lazy("Delete selected %(verbose_name_plural)s")
|
|
||||||
|
|
|
@ -1,3 +1,76 @@
|
||||||
|
def action(function=None, *, permissions=None, description=None):
|
||||||
|
"""
|
||||||
|
Conveniently add attributes to an action function::
|
||||||
|
|
||||||
|
@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'
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
if permissions is not None:
|
||||||
|
func.allowed_permissions = permissions
|
||||||
|
if description is not None:
|
||||||
|
func.short_description = description
|
||||||
|
return func
|
||||||
|
if function is None:
|
||||||
|
return decorator
|
||||||
|
else:
|
||||||
|
return decorator(function)
|
||||||
|
|
||||||
|
|
||||||
|
def display(function=None, *, boolean=None, ordering=None, description=None, empty_value=None):
|
||||||
|
"""
|
||||||
|
Conveniently add attributes to a display function::
|
||||||
|
|
||||||
|
@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?'
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
if boolean is not None and empty_value is not None:
|
||||||
|
raise ValueError(
|
||||||
|
'The boolean and empty_value arguments to the @display '
|
||||||
|
'decorator are mutually exclusive.'
|
||||||
|
)
|
||||||
|
if boolean is not None:
|
||||||
|
func.boolean = boolean
|
||||||
|
if ordering is not None:
|
||||||
|
func.admin_order_field = ordering
|
||||||
|
if description is not None:
|
||||||
|
func.short_description = description
|
||||||
|
if empty_value is not None:
|
||||||
|
func.empty_value_display = empty_value
|
||||||
|
return func
|
||||||
|
if function is None:
|
||||||
|
return decorator
|
||||||
|
else:
|
||||||
|
return decorator(function)
|
||||||
|
|
||||||
|
|
||||||
def register(*models, site=None):
|
def register(*models, site=None):
|
||||||
"""
|
"""
|
||||||
Register the given model(s) classes and wrapped ModelAdmin class with
|
Register the given model(s) classes and wrapped ModelAdmin class with
|
||||||
|
|
|
@ -12,6 +12,7 @@ from django.contrib.admin import helpers, widgets
|
||||||
from django.contrib.admin.checks import (
|
from django.contrib.admin.checks import (
|
||||||
BaseModelAdminChecks, InlineModelAdminChecks, ModelAdminChecks,
|
BaseModelAdminChecks, InlineModelAdminChecks, ModelAdminChecks,
|
||||||
)
|
)
|
||||||
|
from django.contrib.admin.decorators import display
|
||||||
from django.contrib.admin.exceptions import DisallowedModelAdminToField
|
from django.contrib.admin.exceptions import DisallowedModelAdminToField
|
||||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
from django.contrib.admin.utils import (
|
from django.contrib.admin.utils import (
|
||||||
|
@ -848,12 +849,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
action_flag=DELETION,
|
action_flag=DELETION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@display(description=mark_safe('<input type="checkbox" id="action-toggle">'))
|
||||||
def action_checkbox(self, obj):
|
def action_checkbox(self, obj):
|
||||||
"""
|
"""
|
||||||
A list_display column containing a checkbox widget.
|
A list_display column containing a checkbox widget.
|
||||||
"""
|
"""
|
||||||
return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))
|
return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))
|
||||||
action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle">')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_action_description(func, name):
|
def _get_action_description(func, name):
|
||||||
|
|
|
@ -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
|
underscores replaced with spaces), and that each line contains the string
|
||||||
representation of the output.
|
representation of the output.
|
||||||
|
|
||||||
You can improve that by giving that method (in :file:`polls/models.py`) a few
|
You can improve that by using the :func:`~django.contrib.admin.display`
|
||||||
attributes, as follows:
|
decorator on that method (in :file:`polls/models.py`), as follows:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:caption: polls/models.py
|
:caption: polls/models.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
class Question(models.Model):
|
class Question(models.Model):
|
||||||
# ...
|
# ...
|
||||||
|
@admin.display(
|
||||||
|
boolean=True,
|
||||||
|
ordering='pub_date',
|
||||||
|
description='Published recently?',
|
||||||
|
)
|
||||||
def was_published_recently(self):
|
def was_published_recently(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
return now - datetime.timedelta(days=1) <= self.pub_date <= 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`.
|
:attr:`~django.contrib.admin.ModelAdmin.list_display`.
|
||||||
|
|
||||||
Edit your :file:`polls/admin.py` file again and add an improvement to the
|
Edit your :file:`polls/admin.py` file again and add an improvement to the
|
||||||
|
|
|
@ -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.
|
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" --
|
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
|
the function name, with underscores replaced by spaces. That's fine, but we
|
||||||
can provide a better, more human-friendly name by giving the
|
can provide a better, more human-friendly name by using the
|
||||||
``make_published`` function a ``short_description`` attribute::
|
: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):
|
def make_published(modeladmin, request, queryset):
|
||||||
queryset.update(status='p')
|
queryset.update(status='p')
|
||||||
make_published.short_description = "Mark selected stories as published"
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This might look familiar; the admin's ``list_display`` option uses the
|
This might look familiar; the admin's
|
||||||
same technique to provide human-readable descriptions for callback
|
:attr:`~django.contrib.admin.ModelAdmin.list_display` option uses a similar
|
||||||
functions registered there, too.
|
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`
|
Adding actions to the :class:`ModelAdmin`
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
@ -122,9 +136,9 @@ the action and its registration would look like::
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from myapp.models import Article
|
from myapp.models import Article
|
||||||
|
|
||||||
|
@admin.action(description='Mark selected stories as published')
|
||||||
def make_published(modeladmin, request, queryset):
|
def make_published(modeladmin, request, queryset):
|
||||||
queryset.update(status='p')
|
queryset.update(status='p')
|
||||||
make_published.short_description = "Mark selected stories as published"
|
|
||||||
|
|
||||||
class ArticleAdmin(admin.ModelAdmin):
|
class ArticleAdmin(admin.ModelAdmin):
|
||||||
list_display = ['title', 'status']
|
list_display = ['title', 'status']
|
||||||
|
@ -171,9 +185,9 @@ You can do it like this::
|
||||||
|
|
||||||
actions = ['make_published']
|
actions = ['make_published']
|
||||||
|
|
||||||
|
@admin.action(description='Mark selected stories as published')
|
||||||
def make_published(self, request, queryset):
|
def make_published(self, request, queryset):
|
||||||
queryset.update(status='p')
|
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
|
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
|
``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
|
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):
|
def make_published(modeladmin, request, queryset):
|
||||||
queryset.update(status='p')
|
queryset.update(status='p')
|
||||||
make_published.allowed_permissions = ('change',)
|
|
||||||
|
|
||||||
The ``make_published()`` action will only be available to users that pass the
|
The ``make_published()`` action will only be available to users that pass the
|
||||||
:meth:`.ModelAdmin.has_change_permission` check.
|
:meth:`.ModelAdmin.has_change_permission` check.
|
||||||
|
|
||||||
If ``allowed_permissions`` has more than one permission, the action will be
|
If ``permissions`` has more than one permission, the action will be available
|
||||||
available as long as the user passes at least one of the checks.
|
as long as the user passes at least one of the checks.
|
||||||
|
|
||||||
Available values for ``allowed_permissions`` and the corresponding method
|
Available values for ``permissions`` and the corresponding method checks are:
|
||||||
checks are:
|
|
||||||
|
|
||||||
- ``'add'``: :meth:`.ModelAdmin.has_add_permission`
|
- ``'add'``: :meth:`.ModelAdmin.has_add_permission`
|
||||||
- ``'change'``: :meth:`.ModelAdmin.has_change_permission`
|
- ``'change'``: :meth:`.ModelAdmin.has_change_permission`
|
||||||
|
@ -395,12 +409,55 @@ For example::
|
||||||
class ArticleAdmin(admin.ModelAdmin):
|
class ArticleAdmin(admin.ModelAdmin):
|
||||||
actions = ['make_published']
|
actions = ['make_published']
|
||||||
|
|
||||||
|
@admin.action(permissions=['publish'])
|
||||||
def make_published(self, request, queryset):
|
def make_published(self, request, queryset):
|
||||||
queryset.update(status='p')
|
queryset.update(status='p')
|
||||||
make_published.allowed_permissions = ('publish',)
|
|
||||||
|
|
||||||
def has_publish_permission(self, request):
|
def has_publish_permission(self, request):
|
||||||
"""Does the user have the publish permission?"""
|
"""Does the user have the publish permission?"""
|
||||||
opts = self.opts
|
opts = self.opts
|
||||||
codename = get_permission_codename('publish', opts)
|
codename = get_permission_codename('publish', opts)
|
||||||
return request.user.has_perm('%s.%s' % (opts.app_label, codename))
|
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.
|
||||||
|
|
|
@ -256,10 +256,17 @@ subclass::
|
||||||
class AuthorAdmin(admin.ModelAdmin):
|
class AuthorAdmin(admin.ModelAdmin):
|
||||||
fields = ('name', 'title', 'view_birth_date')
|
fields = ('name', 'title', 'view_birth_date')
|
||||||
|
|
||||||
|
@admin.display(empty_value='???')
|
||||||
def view_birth_date(self, obj):
|
def view_birth_date(self, obj):
|
||||||
return obj.birth_date
|
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
|
.. attribute:: ModelAdmin.exclude
|
||||||
|
|
||||||
|
@ -551,7 +558,9 @@ subclass::
|
||||||
If you don't set ``list_display``, the admin site will display a single
|
If you don't set ``list_display``, the admin site will display a single
|
||||||
column that displays the ``__str__()`` representation of each object.
|
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::
|
* The name of a model field. For example::
|
||||||
|
|
||||||
|
@ -560,9 +569,9 @@ subclass::
|
||||||
|
|
||||||
* A callable that accepts one argument, the model instance. For example::
|
* A callable that accepts one argument, the model instance. For example::
|
||||||
|
|
||||||
|
@admin.display(description='Name')
|
||||||
def upper_case_name(obj):
|
def upper_case_name(obj):
|
||||||
return ("%s %s" % (obj.first_name, obj.last_name)).upper()
|
return ("%s %s" % (obj.first_name, obj.last_name)).upper()
|
||||||
upper_case_name.short_description = 'Name'
|
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = (upper_case_name,)
|
list_display = (upper_case_name,)
|
||||||
|
@ -573,9 +582,9 @@ subclass::
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('upper_case_name',)
|
list_display = ('upper_case_name',)
|
||||||
|
|
||||||
|
@admin.display(description='Name')
|
||||||
def upper_case_name(self, obj):
|
def upper_case_name(self, obj):
|
||||||
return ("%s %s" % (obj.first_name, obj.last_name)).upper()
|
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
|
* A string representing a model attribute or method (without any required
|
||||||
arguments). For example::
|
arguments). For example::
|
||||||
|
@ -587,9 +596,9 @@ subclass::
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
birthday = models.DateField()
|
birthday = models.DateField()
|
||||||
|
|
||||||
|
@admin.display(description='Birth decade')
|
||||||
def decade_born_in(self):
|
def decade_born_in(self):
|
||||||
return '%d’s' % (self.birthday.year // 10 * 10)
|
return '%d’s' % (self.birthday.year // 10 * 10)
|
||||||
decade_born_in.short_description = 'Birth decade'
|
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'decade_born_in')
|
list_display = ('name', 'decade_born_in')
|
||||||
|
@ -624,6 +633,7 @@ subclass::
|
||||||
last_name = models.CharField(max_length=50)
|
last_name = models.CharField(max_length=50)
|
||||||
color_code = models.CharField(max_length=6)
|
color_code = models.CharField(max_length=6)
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def colored_name(self):
|
def colored_name(self):
|
||||||
return format_html(
|
return format_html(
|
||||||
'<span style="color: #{};">{} {}</span>',
|
'<span style="color: #{};">{} {}</span>',
|
||||||
|
@ -637,7 +647,17 @@ subclass::
|
||||||
|
|
||||||
* As some examples have already demonstrated, when using a callable, a
|
* As some examples have already demonstrated, when using a callable, a
|
||||||
model method, or a ``ModelAdmin`` method, you can customize the column's
|
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
|
* If the value of a field is ``None``, an empty string, or an iterable
|
||||||
without elements, Django will display ``-`` (a dash). You can override
|
without elements, Django will display ``-`` (a dash). You can override
|
||||||
|
@ -657,17 +677,23 @@ subclass::
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'birth_date_view')
|
list_display = ('name', 'birth_date_view')
|
||||||
|
|
||||||
|
@admin.display(empty_value='unknown')
|
||||||
def birth_date_view(self, obj):
|
def birth_date_view(self, obj):
|
||||||
return obj.birth_date
|
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
|
* If the string given is a method of the model, ``ModelAdmin`` or a
|
||||||
callable that returns ``True``, ``False``, or ``None``, Django will
|
callable that returns ``True``, ``False``, or ``None``, Django will
|
||||||
display a pretty "yes", "no", or "unknown" icon if you give the method a
|
display a pretty "yes", "no", or "unknown" icon if you wrap the method
|
||||||
``boolean`` attribute whose value is ``True``.
|
with the :func:`~django.contrib.admin.display` decorator passing the
|
||||||
|
``boolean`` argument with the value set to ``True``::
|
||||||
Here's a full example model::
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -676,13 +702,21 @@ subclass::
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
birthday = models.DateField()
|
birthday = models.DateField()
|
||||||
|
|
||||||
|
@admin.display(boolean=True)
|
||||||
def born_in_fifties(self):
|
def born_in_fifties(self):
|
||||||
return 1950 <= self.birthday.year < 1960
|
return 1950 <= self.birthday.year < 1960
|
||||||
born_in_fifties.boolean = True
|
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'born_in_fifties')
|
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
|
* The ``__str__()`` method is just as valid in ``list_display`` as any
|
||||||
other model method, so it's perfectly OK to do this::
|
other model method, so it's perfectly OK to do this::
|
||||||
|
|
||||||
|
@ -692,11 +726,10 @@ subclass::
|
||||||
fields can't be used in sorting (because Django does all the sorting
|
fields can't be used in sorting (because Django does all the sorting
|
||||||
at the database level).
|
at the database level).
|
||||||
|
|
||||||
However, if an element of ``list_display`` represents a certain
|
However, if an element of ``list_display`` represents a certain database
|
||||||
database field, you can indicate this fact by setting the
|
field, you can indicate this fact by using the
|
||||||
``admin_order_field`` attribute of the item.
|
:func:`~django.contrib.admin.display` decorator on the method, passing
|
||||||
|
the ``ordering`` argument::
|
||||||
For example::
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -706,6 +739,7 @@ subclass::
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
color_code = models.CharField(max_length=6)
|
color_code = models.CharField(max_length=6)
|
||||||
|
|
||||||
|
@admin.display(ordering='first_name')
|
||||||
def colored_first_name(self):
|
def colored_first_name(self):
|
||||||
return format_html(
|
return format_html(
|
||||||
'<span style="color: #{};">{}</span>',
|
'<span style="color: #{};">{}</span>',
|
||||||
|
@ -713,23 +747,21 @@ subclass::
|
||||||
self.first_name,
|
self.first_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
colored_first_name.admin_order_field = 'first_name'
|
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('first_name', 'colored_first_name')
|
list_display = ('first_name', 'colored_first_name')
|
||||||
|
|
||||||
The above will tell Django to order by the ``first_name`` field when
|
The above will tell Django to order by the ``first_name`` field when
|
||||||
trying to sort by ``colored_first_name`` in the admin.
|
trying to sort by ``colored_first_name`` in the admin.
|
||||||
|
|
||||||
To indicate descending order with ``admin_order_field`` you can use a
|
To indicate descending order with the ``ordering`` argument you can use a
|
||||||
hyphen prefix on the field name. Using the above example, this would
|
hyphen prefix on the field name. Using the above example, this would look
|
||||||
look like::
|
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
|
The ``ordering`` argument supports query lookups to sort by values on
|
||||||
models. This example includes an "author first name" column in the list
|
related models. This example includes an "author first name" column in
|
||||||
display and allows sorting it by first name::
|
the list display and allows sorting it by first name::
|
||||||
|
|
||||||
class Blog(models.Model):
|
class Blog(models.Model):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
|
@ -738,13 +770,12 @@ subclass::
|
||||||
class BlogAdmin(admin.ModelAdmin):
|
class BlogAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'author', 'author_first_name')
|
list_display = ('title', 'author', 'author_first_name')
|
||||||
|
|
||||||
|
@admin.display(ordering='author__first_name')
|
||||||
def author_first_name(self, obj):
|
def author_first_name(self, obj):
|
||||||
return obj.author.first_name
|
return obj.author.first_name
|
||||||
|
|
||||||
author_first_name.admin_order_field = 'author__first_name'
|
:doc:`Query expressions </ref/models/expressions>` may be used with the
|
||||||
|
``ordering`` argument::
|
||||||
:doc:`Query expressions </ref/models/expressions>` may be used in
|
|
||||||
``admin_order_field``. For example::
|
|
||||||
|
|
||||||
from django.db.models import Value
|
from django.db.models import Value
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat
|
||||||
|
@ -753,22 +784,41 @@ subclass::
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
last_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):
|
def full_name(self):
|
||||||
return self.first_name + ' ' + self.last_name
|
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,
|
.. versionchanged:: 3.2
|
||||||
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.
|
|
||||||
|
|
||||||
For example::
|
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.
|
||||||
|
|
||||||
|
* Elements of ``list_display`` can also be properties::
|
||||||
|
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
last_name = models.CharField(max_length=50)
|
last_name = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@admin.display(
|
||||||
|
ordering='last_name',
|
||||||
|
description='Full name of the person',
|
||||||
|
)
|
||||||
|
def full_name(self):
|
||||||
|
return self.first_name + ' ' + self.last_name
|
||||||
|
|
||||||
|
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):
|
def my_property(self):
|
||||||
return self.first_name + ' ' + self.last_name
|
return self.first_name + ' ' + self.last_name
|
||||||
my_property.short_description = "Full name of the person"
|
my_property.short_description = "Full name of the person"
|
||||||
|
@ -776,10 +826,6 @@ subclass::
|
||||||
|
|
||||||
full_name = property(my_property)
|
full_name = property(my_property)
|
||||||
|
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('full_name',)
|
|
||||||
|
|
||||||
|
|
||||||
* The field names in ``list_display`` will also appear as CSS classes in
|
* 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>``
|
the HTML output, in the form of ``column-<field_name>`` on each ``<th>``
|
||||||
element. This can be used to set column widths in a CSS file for example.
|
element. This can be used to set column widths in a CSS file for example.
|
||||||
|
@ -1239,6 +1285,8 @@ subclass::
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
readonly_fields = ('address_report',)
|
readonly_fields = ('address_report',)
|
||||||
|
|
||||||
|
# description functions like a model field's verbose_name
|
||||||
|
@admin.display(description='Address')
|
||||||
def address_report(self, instance):
|
def address_report(self, instance):
|
||||||
# assuming get_full_address() returns a list of strings
|
# assuming get_full_address() returns a list of strings
|
||||||
# for each line of the address and you want to separate each
|
# 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()),
|
((line,) for line in instance.get_full_address()),
|
||||||
) or mark_safe("<span class='errors'>I can't determine this address.</span>")
|
) 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
|
.. attribute:: ModelAdmin.save_as
|
||||||
|
|
||||||
Set ``save_as`` to enable a "save as new" feature on admin change forms.
|
Set ``save_as`` to enable a "save as new" feature on admin change forms.
|
||||||
|
@ -1360,8 +1405,9 @@ subclass::
|
||||||
.. attribute:: ModelAdmin.sortable_by
|
.. attribute:: ModelAdmin.sortable_by
|
||||||
|
|
||||||
By default, the change list page allows sorting by all model fields (and
|
By default, the change list page allows sorting by all model fields (and
|
||||||
callables that have the ``admin_order_field`` property) specified in
|
callables that use the ``ordering`` argument to the
|
||||||
:attr:`list_display`.
|
: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
|
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
|
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
|
object which has an ``app_label`` and ``model_name`` attributes and is usually
|
||||||
supplied by the admin views for the current model.
|
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
|
.. currentmodule:: django.contrib.admin.views.decorators
|
||||||
|
|
||||||
The ``staff_member_required`` decorator
|
The ``staff_member_required`` decorator
|
||||||
|
|
|
@ -141,6 +141,28 @@ Django </topics/cache>`.
|
||||||
|
|
||||||
.. _pymemcache: https://pypi.org/project/pymemcache/
|
.. _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
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -389,12 +389,14 @@ verbose names Django performs by looking at the model's class name::
|
||||||
verbose_name = _('my thing')
|
verbose_name = _('my thing')
|
||||||
verbose_name_plural = _('my things')
|
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
|
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.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
@ -406,9 +408,9 @@ with the ``short_description`` attribute::
|
||||||
verbose_name=_('kind'),
|
verbose_name=_('kind'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@admin.display(description=_('Is it a mouse?'))
|
||||||
def is_mouse(self):
|
def is_mouse(self):
|
||||||
return self.kind.type == MOUSE_TYPE
|
return self.kind.type == MOUSE_TYPE
|
||||||
is_mouse.short_description = _('Is it a mouse?')
|
|
||||||
|
|
||||||
Working with lazy translation objects
|
Working with lazy translation objects
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
|
@ -19,6 +19,7 @@ class EventAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = 'date'
|
date_hierarchy = 'date'
|
||||||
list_display = ['event_date_func']
|
list_display = ['event_date_func']
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def event_date_func(self, event):
|
def event_date_func(self, event):
|
||||||
return event.date
|
return event.date
|
||||||
|
|
||||||
|
@ -171,6 +172,6 @@ class EmptyValueChildAdmin(admin.ModelAdmin):
|
||||||
empty_value_display = '-empty-'
|
empty_value_display = '-empty-'
|
||||||
list_display = ('name', 'age_display', 'age')
|
list_display = ('name', 'age_display', 'age')
|
||||||
|
|
||||||
|
@admin.display(empty_value='†')
|
||||||
def age_display(self, obj):
|
def age_display(self, obj):
|
||||||
return obj.age
|
return obj.age
|
||||||
age_display.empty_value_display = '†'
|
|
||||||
|
|
|
@ -692,6 +692,7 @@ class SystemChecksTestCase(SimpleTestCase):
|
||||||
self.assertEqual(errors, [])
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
def test_readonly_on_method(self):
|
def test_readonly_on_method(self):
|
||||||
|
@admin.display
|
||||||
def my_function(obj):
|
def my_function(obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -705,6 +706,7 @@ class SystemChecksTestCase(SimpleTestCase):
|
||||||
class SongAdmin(admin.ModelAdmin):
|
class SongAdmin(admin.ModelAdmin):
|
||||||
readonly_fields = ("readonly_method_on_modeladmin",)
|
readonly_fields = ("readonly_method_on_modeladmin",)
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def readonly_method_on_modeladmin(self, obj):
|
def readonly_method_on_modeladmin(self, obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -717,6 +719,7 @@ class SystemChecksTestCase(SimpleTestCase):
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
if item == "dynamic_method":
|
if item == "dynamic_method":
|
||||||
|
@admin.display
|
||||||
def method(obj):
|
def method(obj):
|
||||||
pass
|
pass
|
||||||
return method
|
return method
|
||||||
|
@ -777,6 +780,7 @@ class SystemChecksTestCase(SimpleTestCase):
|
||||||
|
|
||||||
def test_extra(self):
|
def test_extra(self):
|
||||||
class SongAdmin(admin.ModelAdmin):
|
class SongAdmin(admin.ModelAdmin):
|
||||||
|
@admin.display
|
||||||
def awesome_song(self, instance):
|
def awesome_song(self, instance):
|
||||||
if instance.title == "Born to Run":
|
if instance.title == "Born to Run":
|
||||||
return "Best Ever!"
|
return "Best Ever!"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
@ -28,9 +29,9 @@ class Article(models.Model):
|
||||||
def test_from_model(self):
|
def test_from_model(self):
|
||||||
return "nothing"
|
return "nothing"
|
||||||
|
|
||||||
|
@admin.display(description='not What you Expect')
|
||||||
def test_from_model_with_override(self):
|
def test_from_model_with_override(self):
|
||||||
return "nothing"
|
return "nothing"
|
||||||
test_from_model_with_override.short_description = "not What you Expect"
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleProxy(Article):
|
class ArticleProxy(Article):
|
||||||
|
|
|
@ -3,6 +3,7 @@ from decimal import Decimal
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import admin
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
from django.contrib.admin.utils import (
|
from django.contrib.admin.utils import (
|
||||||
NestedObjects, display_for_field, display_for_value, flatten,
|
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')
|
self.assertEqual(label_for_field('site_id', Article), 'Site id')
|
||||||
|
|
||||||
class MockModelAdmin:
|
class MockModelAdmin:
|
||||||
|
@admin.display(description='not Really the Model')
|
||||||
def test_from_model(self, obj):
|
def test_from_model(self, obj):
|
||||||
return "nothing"
|
return "nothing"
|
||||||
test_from_model.short_description = "not Really the Model"
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
|
label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
|
||||||
|
@ -323,13 +324,11 @@ class UtilsTests(SimpleTestCase):
|
||||||
label_for_field('nonexistent', Article, form=ArticleForm()),
|
label_for_field('nonexistent', Article, form=ArticleForm()),
|
||||||
|
|
||||||
def test_label_for_property(self):
|
def test_label_for_property(self):
|
||||||
# NOTE: cannot use @property decorator, because of
|
|
||||||
# AttributeError: 'property' object has no attribute 'short_description'
|
|
||||||
class MockModelAdmin:
|
class MockModelAdmin:
|
||||||
def my_property(self):
|
@property
|
||||||
|
@admin.display(description='property short description')
|
||||||
|
def test_from_property(self):
|
||||||
return "this if from property"
|
return "this if from property"
|
||||||
my_property.short_description = 'property short description'
|
|
||||||
test_from_property = property(my_property)
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
label_for_field("test_from_property", Article, model_admin=MockModelAdmin),
|
label_for_field("test_from_property", Article, model_admin=MockModelAdmin),
|
||||||
|
|
|
@ -49,6 +49,7 @@ from .models import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.display(ordering='date')
|
||||||
def callable_year(dt_value):
|
def callable_year(dt_value):
|
||||||
try:
|
try:
|
||||||
return dt_value.year
|
return dt_value.year
|
||||||
|
@ -56,9 +57,6 @@ def callable_year(dt_value):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
callable_year.admin_order_field = 'date'
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleInline(admin.TabularInline):
|
class ArticleInline(admin.TabularInline):
|
||||||
model = Article
|
model = Article
|
||||||
fk_name = 'section'
|
fk_name = 'section'
|
||||||
|
@ -138,25 +136,24 @@ class ArticleAdmin(ArticleAdminWithExtraUrl):
|
||||||
|
|
||||||
# These orderings aren't particularly useful but show that expressions can
|
# These orderings aren't particularly useful but show that expressions can
|
||||||
# be used for admin_order_field.
|
# be used for admin_order_field.
|
||||||
|
@admin.display(ordering=models.F('date') + datetime.timedelta(days=3))
|
||||||
def order_by_expression(self, obj):
|
def order_by_expression(self, obj):
|
||||||
return obj.model_year
|
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):
|
def order_by_f_expression(self, obj):
|
||||||
return obj.model_year
|
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):
|
def order_by_orderby_expression(self, obj):
|
||||||
return obj.model_year
|
return obj.model_year
|
||||||
order_by_orderby_expression.admin_order_field = models.F('date').asc(nulls_last=True)
|
|
||||||
|
|
||||||
def changelist_view(self, request):
|
def changelist_view(self, request):
|
||||||
return super().changelist_view(request, extra_context={'extra_var': 'Hello!'})
|
return super().changelist_view(request, extra_context={'extra_var': 'Hello!'})
|
||||||
|
|
||||||
|
@admin.display(ordering='date', description=None)
|
||||||
def modeladmin_year(self, obj):
|
def modeladmin_year(self, obj):
|
||||||
return obj.date.year
|
return obj.date.year
|
||||||
modeladmin_year.admin_order_field = 'date'
|
|
||||||
modeladmin_year.short_description = None
|
|
||||||
|
|
||||||
def delete_model(self, request, obj):
|
def delete_model(self, request, obj):
|
||||||
EmailMessage(
|
EmailMessage(
|
||||||
|
@ -216,6 +213,7 @@ class ThingAdmin(admin.ModelAdmin):
|
||||||
class InquisitionAdmin(admin.ModelAdmin):
|
class InquisitionAdmin(admin.ModelAdmin):
|
||||||
list_display = ('leader', 'country', 'expected', 'sketch')
|
list_display = ('leader', 'country', 'expected', 'sketch')
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def sketch(self, obj):
|
def sketch(self, obj):
|
||||||
# A method with the same name as a reverse accessor.
|
# A method with the same name as a reverse accessor.
|
||||||
return 'list-display-sketch'
|
return 'list-display-sketch'
|
||||||
|
@ -280,6 +278,7 @@ class SubscriberAdmin(admin.ModelAdmin):
|
||||||
SubscriberAdmin.overridden = True
|
SubscriberAdmin.overridden = True
|
||||||
super().delete_queryset(request, queryset)
|
super().delete_queryset(request, queryset)
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def mail_admin(self, request, selected):
|
def mail_admin(self, request, selected):
|
||||||
EmailMessage(
|
EmailMessage(
|
||||||
'Greetings from a ModelAdmin action',
|
'Greetings from a ModelAdmin action',
|
||||||
|
@ -289,6 +288,7 @@ class SubscriberAdmin(admin.ModelAdmin):
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
|
|
||||||
|
@admin.action(description='External mail (Another awesome action)')
|
||||||
def external_mail(modeladmin, request, selected):
|
def external_mail(modeladmin, request, selected):
|
||||||
EmailMessage(
|
EmailMessage(
|
||||||
'Greetings from a function action',
|
'Greetings from a function action',
|
||||||
|
@ -298,32 +298,23 @@ def external_mail(modeladmin, request, selected):
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
|
|
||||||
external_mail.short_description = 'External mail (Another awesome action)'
|
@admin.action(description='Redirect to (Awesome action)')
|
||||||
|
|
||||||
|
|
||||||
def redirect_to(modeladmin, request, selected):
|
def redirect_to(modeladmin, request, selected):
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
return HttpResponseRedirect('/some-where-else/')
|
return HttpResponseRedirect('/some-where-else/')
|
||||||
|
|
||||||
|
|
||||||
redirect_to.short_description = 'Redirect to (Awesome action)'
|
@admin.action(description='Download subscription')
|
||||||
|
|
||||||
|
|
||||||
def download(modeladmin, request, selected):
|
def download(modeladmin, request, selected):
|
||||||
buf = StringIO('This is the content of the file')
|
buf = StringIO('This is the content of the file')
|
||||||
return StreamingHttpResponse(FileWrapper(buf))
|
return StreamingHttpResponse(FileWrapper(buf))
|
||||||
|
|
||||||
|
|
||||||
download.short_description = 'Download subscription'
|
@admin.action(description='No permission to run')
|
||||||
|
|
||||||
|
|
||||||
def no_perm(modeladmin, request, selected):
|
def no_perm(modeladmin, request, selected):
|
||||||
return HttpResponse(content='No permission to perform this action', status=403)
|
return HttpResponse(content='No permission to perform this action', status=403)
|
||||||
|
|
||||||
|
|
||||||
no_perm.short_description = 'No permission to run'
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalSubscriberAdmin(admin.ModelAdmin):
|
class ExternalSubscriberAdmin(admin.ModelAdmin):
|
||||||
actions = [redirect_to, external_mail, download, no_perm]
|
actions = [redirect_to, external_mail, download, no_perm]
|
||||||
|
|
||||||
|
@ -441,6 +432,7 @@ class LinkInline(admin.TabularInline):
|
||||||
|
|
||||||
readonly_fields = ("posted", "multiline", "readonly_link_content")
|
readonly_fields = ("posted", "multiline", "readonly_link_content")
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def multiline(self, instance):
|
def multiline(self, instance):
|
||||||
return "InlineMultiline\ntest\nstring"
|
return "InlineMultiline\ntest\nstring"
|
||||||
|
|
||||||
|
@ -501,19 +493,22 @@ class PostAdmin(admin.ModelAdmin):
|
||||||
LinkInline
|
LinkInline
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def coolness(self, instance):
|
def coolness(self, instance):
|
||||||
if instance.pk:
|
if instance.pk:
|
||||||
return "%d amount of cool." % instance.pk
|
return "%d amount of cool." % instance.pk
|
||||||
else:
|
else:
|
||||||
return "Unknown coolness."
|
return "Unknown coolness."
|
||||||
|
|
||||||
|
@admin.display(description='Value in $US')
|
||||||
def value(self, instance):
|
def value(self, instance):
|
||||||
return 1000
|
return 1000
|
||||||
value.short_description = 'Value in $US'
|
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def multiline(self, instance):
|
def multiline(self, instance):
|
||||||
return "Multiline\ntest\nstring"
|
return "Multiline\ntest\nstring"
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def multiline_html(self, instance):
|
def multiline_html(self, instance):
|
||||||
return mark_safe("Multiline<br>\nhtml<br>\ncontent")
|
return mark_safe("Multiline<br>\nhtml<br>\ncontent")
|
||||||
|
|
||||||
|
@ -655,9 +650,9 @@ class ComplexSortedPersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'age', 'is_employee', 'colored_name')
|
list_display = ('name', 'age', 'is_employee', 'colored_name')
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
|
|
||||||
|
@admin.display(ordering='name')
|
||||||
def colored_name(self, obj):
|
def colored_name(self, obj):
|
||||||
return format_html('<span style="color: #ff00ff;">{}</span>', obj.name)
|
return format_html('<span style="color: #ff00ff;">{}</span>', obj.name)
|
||||||
colored_name.admin_order_field = 'name'
|
|
||||||
|
|
||||||
|
|
||||||
class PluggableSearchPersonAdmin(admin.ModelAdmin):
|
class PluggableSearchPersonAdmin(admin.ModelAdmin):
|
||||||
|
@ -706,20 +701,18 @@ class AdminOrderedModelMethodAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderedAdminMethodAdmin(admin.ModelAdmin):
|
class AdminOrderedAdminMethodAdmin(admin.ModelAdmin):
|
||||||
|
@admin.display(ordering='order')
|
||||||
def some_admin_order(self, obj):
|
def some_admin_order(self, obj):
|
||||||
return obj.order
|
return obj.order
|
||||||
some_admin_order.admin_order_field = 'order'
|
|
||||||
ordering = ('order',)
|
ordering = ('order',)
|
||||||
list_display = ('stuff', 'some_admin_order')
|
list_display = ('stuff', 'some_admin_order')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.display(ordering='order')
|
||||||
def admin_ordered_callable(obj):
|
def admin_ordered_callable(obj):
|
||||||
return obj.order
|
return obj.order
|
||||||
|
|
||||||
|
|
||||||
admin_ordered_callable.admin_order_field = 'order'
|
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderedCallableAdmin(admin.ModelAdmin):
|
class AdminOrderedCallableAdmin(admin.ModelAdmin):
|
||||||
ordering = ('order',)
|
ordering = ('order',)
|
||||||
list_display = ('stuff', admin_ordered_callable)
|
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")]
|
return [p for p in urlpatterns if p.name and not p.name.endswith("_change")]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.display
|
||||||
def callable_on_unknown(obj):
|
def callable_on_unknown(obj):
|
||||||
return obj.unknown
|
return obj.unknown
|
||||||
|
|
||||||
|
@ -831,21 +825,27 @@ class MessageTestingAdmin(admin.ModelAdmin):
|
||||||
actions = ["message_debug", "message_info", "message_success",
|
actions = ["message_debug", "message_info", "message_success",
|
||||||
"message_warning", "message_error", "message_extra_tags"]
|
"message_warning", "message_error", "message_extra_tags"]
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_debug(self, request, selected):
|
def message_debug(self, request, selected):
|
||||||
self.message_user(request, "Test debug", level="debug")
|
self.message_user(request, "Test debug", level="debug")
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_info(self, request, selected):
|
def message_info(self, request, selected):
|
||||||
self.message_user(request, "Test info", level="info")
|
self.message_user(request, "Test info", level="info")
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_success(self, request, selected):
|
def message_success(self, request, selected):
|
||||||
self.message_user(request, "Test success", level="success")
|
self.message_user(request, "Test success", level="success")
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_warning(self, request, selected):
|
def message_warning(self, request, selected):
|
||||||
self.message_user(request, "Test warning", level="warning")
|
self.message_user(request, "Test warning", level="warning")
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_error(self, request, selected):
|
def message_error(self, request, selected):
|
||||||
self.message_user(request, "Test error", level="error")
|
self.message_user(request, "Test error", level="error")
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def message_extra_tags(self, request, selected):
|
def message_extra_tags(self, request, selected):
|
||||||
self.message_user(request, "Test tags", extra_tags="extra_tag")
|
self.message_user(request, "Test tags", extra_tags="extra_tag")
|
||||||
|
|
||||||
|
@ -1156,9 +1156,9 @@ class ArticleAdmin6(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
sortable_by = ('date', callable_year)
|
sortable_by = ('date', callable_year)
|
||||||
|
|
||||||
|
@admin.display(ordering='date')
|
||||||
def modeladmin_year(self, obj):
|
def modeladmin_year(self, obj):
|
||||||
return obj.date.year
|
return obj.date.year
|
||||||
modeladmin_year.admin_order_field = 'date'
|
|
||||||
|
|
||||||
|
|
||||||
class ActorAdmin6(admin.ModelAdmin):
|
class ActorAdmin6(admin.ModelAdmin):
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import (
|
from django.contrib.contenttypes.fields import (
|
||||||
GenericForeignKey, GenericRelation,
|
GenericForeignKey, GenericRelation,
|
||||||
|
@ -45,20 +46,18 @@ class Article(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
@admin.display(ordering='date', description='')
|
||||||
def model_year(self):
|
def model_year(self):
|
||||||
return self.date.year
|
return self.date.year
|
||||||
model_year.admin_order_field = 'date'
|
|
||||||
model_year.short_description = ''
|
|
||||||
|
|
||||||
|
@admin.display(ordering='-date', description='')
|
||||||
def model_year_reversed(self):
|
def model_year_reversed(self):
|
||||||
return self.date.year
|
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
|
return self.date.year
|
||||||
property_year.admin_order_field = 'date'
|
|
||||||
model_property_year = property(property_year)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_month(self):
|
def model_month(self):
|
||||||
|
@ -746,9 +745,9 @@ class AdminOrderedModelMethod(models.Model):
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
stuff = models.CharField(max_length=200)
|
stuff = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
@admin.display(ordering='order')
|
||||||
def some_order(self):
|
def some_order(self):
|
||||||
return self.order
|
return self.order
|
||||||
some_order.admin_order_field = 'order'
|
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderedAdminMethod(models.Model):
|
class AdminOrderedAdminMethod(models.Model):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from urllib.parse import parse_qsl, urljoin, urlparse
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
from django.contrib.admin import AdminSite, ModelAdmin
|
from django.contrib.admin import AdminSite, ModelAdmin
|
||||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||||
from django.contrib.admin.models import ADDITION, DELETION, LogEntry
|
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'))
|
response = self.client.get(reverse('admin:admin_views_post_changelist'))
|
||||||
self.assertContains(response, 'icon-unknown.svg')
|
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):
|
def test_i18n_language_non_english_default(self):
|
||||||
"""
|
"""
|
||||||
Check if the JavaScript i18n view returns an empty language catalog
|
Check if the JavaScript i18n view returns an empty language catalog
|
||||||
|
|
|
@ -27,6 +27,7 @@ class AdminActionsTests(TestCase):
|
||||||
class BandAdmin(admin.ModelAdmin):
|
class BandAdmin(admin.ModelAdmin):
|
||||||
actions = ['custom_action']
|
actions = ['custom_action']
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def custom_action(modeladmin, request, queryset):
|
def custom_action(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ class AdminActionsTests(TestCase):
|
||||||
class AdminBase(admin.ModelAdmin):
|
class AdminBase(admin.ModelAdmin):
|
||||||
actions = ['custom_action']
|
actions = ['custom_action']
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def custom_action(modeladmin, request, queryset):
|
def custom_action(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -78,13 +80,14 @@ class AdminActionsTests(TestCase):
|
||||||
self.assertEqual(action_names, ['delete_selected'])
|
self.assertEqual(action_names, ['delete_selected'])
|
||||||
|
|
||||||
def test_global_actions_description(self):
|
def test_global_actions_description(self):
|
||||||
|
@admin.action(description='Site-wide admin action 1.')
|
||||||
def global_action_1(modeladmin, request, queryset):
|
def global_action_1(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def global_action_2(modeladmin, request, queryset):
|
def global_action_2(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
global_action_1.short_description = 'Site-wide admin action 1.'
|
|
||||||
admin_site = admin.AdminSite()
|
admin_site = admin.AdminSite()
|
||||||
admin_site.add_action(global_action_1)
|
admin_site.add_action(global_action_1)
|
||||||
admin_site.add_action(global_action_2)
|
admin_site.add_action(global_action_2)
|
||||||
|
@ -103,30 +106,28 @@ class AdminActionsTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_actions_replace_global_action(self):
|
def test_actions_replace_global_action(self):
|
||||||
|
@admin.action(description='Site-wide admin action 1.')
|
||||||
def global_action_1(modeladmin, request, queryset):
|
def global_action_1(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@admin.action(description='Site-wide admin action 2.')
|
||||||
def global_action_2(modeladmin, request, queryset):
|
def global_action_2(modeladmin, request, queryset):
|
||||||
pass
|
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_1, name='custom_action_1')
|
||||||
admin.site.add_action(global_action_2, name='custom_action_2')
|
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):
|
def custom_action_1(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
custom_action_1.short_description = 'Local admin action 1.'
|
|
||||||
|
|
||||||
class BandAdmin(admin.ModelAdmin):
|
class BandAdmin(admin.ModelAdmin):
|
||||||
actions = [custom_action_1, 'custom_action_2']
|
actions = [custom_action_1, 'custom_action_2']
|
||||||
|
|
||||||
|
@admin.action(description='Local admin action 2.')
|
||||||
def custom_action_2(self, request, queryset):
|
def custom_action_2(self, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
custom_action_2.short_description = 'Local admin action 2.'
|
|
||||||
|
|
||||||
ma = BandAdmin(Band, admin.site)
|
ma = BandAdmin(Band, admin.site)
|
||||||
self.assertEqual(ma.check(), [])
|
self.assertEqual(ma.check(), [])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib import admin
|
||||||
from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter
|
from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter
|
||||||
from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline
|
from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
@ -499,10 +500,12 @@ class ListDisplayTests(CheckTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_valid_case(self):
|
def test_valid_case(self):
|
||||||
|
@admin.display
|
||||||
def a_callable(obj):
|
def a_callable(obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TestModelAdmin(ModelAdmin):
|
class TestModelAdmin(ModelAdmin):
|
||||||
|
@admin.display
|
||||||
def a_method(self, obj):
|
def a_method(self, obj):
|
||||||
pass
|
pass
|
||||||
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
|
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
|
||||||
|
@ -563,10 +566,12 @@ class ListDisplayLinksCheckTests(CheckTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_valid_case(self):
|
def test_valid_case(self):
|
||||||
|
@admin.display
|
||||||
def a_callable(obj):
|
def a_callable(obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TestModelAdmin(ModelAdmin):
|
class TestModelAdmin(ModelAdmin):
|
||||||
|
@admin.display
|
||||||
def a_method(self, obj):
|
def a_method(self, obj):
|
||||||
pass
|
pass
|
||||||
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
|
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
|
||||||
|
@ -1417,11 +1422,10 @@ class AutocompleteFieldsTests(CheckTestCase):
|
||||||
class ActionsCheckTests(CheckTestCase):
|
class ActionsCheckTests(CheckTestCase):
|
||||||
|
|
||||||
def test_custom_permissions_require_matching_has_method(self):
|
def test_custom_permissions_require_matching_has_method(self):
|
||||||
|
@admin.action(permissions=['custom'])
|
||||||
def custom_permission_action(modeladmin, request, queryset):
|
def custom_permission_action(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
custom_permission_action.allowed_permissions = ('custom',)
|
|
||||||
|
|
||||||
class BandAdmin(ModelAdmin):
|
class BandAdmin(ModelAdmin):
|
||||||
actions = (custom_permission_action,)
|
actions = (custom_permission_action,)
|
||||||
|
|
||||||
|
@ -1433,6 +1437,7 @@ class ActionsCheckTests(CheckTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_actions_not_unique(self):
|
def test_actions_not_unique(self):
|
||||||
|
@admin.action
|
||||||
def action(modeladmin, request, queryset):
|
def action(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1447,9 +1452,11 @@ class ActionsCheckTests(CheckTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_actions_unique(self):
|
def test_actions_unique(self):
|
||||||
|
@admin.action
|
||||||
def action1(modeladmin, request, queryset):
|
def action1(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@admin.action
|
||||||
def action2(modeladmin, request, queryset):
|
def action2(modeladmin, request, queryset):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue