summaryrefslogtreecommitdiff
path: root/docs/ref/contrib/admin
diff options
context:
space:
mode:
authordjango-bot <ops@djangoproject.com>2023-03-01 13:35:43 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-03-01 13:39:03 +0100
commit62510f01e76ad0526c94ea6d1bc6399c1ddf3df4 (patch)
tree79844be246eba809a4ca09c6f4c3448f2276321a /docs/ref/contrib/admin
parent32f224e359c68e70e3f9a230be0265dcd6677079 (diff)
[4.2.x] Fixed #34140 -- Reformatted code blocks in docs with blacken-docs.
Diffstat (limited to 'docs/ref/contrib/admin')
-rw-r--r--docs/ref/contrib/admin/actions.txt111
-rw-r--r--docs/ref/contrib/admin/admindocs.txt9
-rw-r--r--docs/ref/contrib/admin/filters.txt34
-rw-r--r--docs/ref/contrib/admin/index.txt333
4 files changed, 294 insertions, 193 deletions
diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt
index 56e8d7feae..37c5be7ace 100644
--- a/docs/ref/contrib/admin/actions.txt
+++ b/docs/ref/contrib/admin/actions.txt
@@ -48,11 +48,12 @@ news application with an ``Article`` model::
from django.db import models
STATUS_CHOICES = [
- ('d', 'Draft'),
- ('p', 'Published'),
- ('w', 'Withdrawn'),
+ ("d", "Draft"),
+ ("p", "Published"),
+ ("w", "Withdrawn"),
]
+
class Article(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
@@ -83,7 +84,7 @@ Our publish-these-articles function won't need the :class:`ModelAdmin` or the
request object, but we will use the queryset::
def make_published(modeladmin, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
.. note::
@@ -107,9 +108,10 @@ function::
...
- @admin.action(description='Mark selected stories as published')
+
+ @admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
.. note::
@@ -129,15 +131,18 @@ 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')
+
+ @admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
+
class ArticleAdmin(admin.ModelAdmin):
- list_display = ['title', 'status']
- ordering = ['title']
+ list_display = ["title", "status"]
+ ordering = ["title"]
actions = [make_published]
+
admin.site.register(Article, ArticleAdmin)
That code will give us an admin change list that looks something like this:
@@ -176,11 +181,11 @@ You can do it like this::
class ArticleAdmin(admin.ModelAdmin):
...
- actions = ['make_published']
+ actions = ["make_published"]
- @admin.action(description='Mark selected stories as published')
+ @admin.action(description="Mark selected stories as published")
def make_published(self, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
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
@@ -199,16 +204,22 @@ that the action was successful::
from django.contrib import messages
from django.utils.translation import ngettext
+
class ArticleAdmin(admin.ModelAdmin):
...
def make_published(self, request, queryset):
- updated = queryset.update(status='p')
- self.message_user(request, ngettext(
- '%d story was successfully marked as published.',
- '%d stories were successfully marked as published.',
- updated,
- ) % updated, messages.SUCCESS)
+ updated = queryset.update(status="p")
+ self.message_user(
+ request,
+ ngettext(
+ "%d story was successfully marked as published.",
+ "%d stories were successfully marked as published.",
+ updated,
+ )
+ % updated,
+ messages.SUCCESS,
+ )
This make the action match what the admin itself does after successfully
performing an action:
@@ -231,6 +242,7 @@ dump some selected objects as JSON::
from django.core import serializers
from django.http import HttpResponse
+
def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response)
@@ -249,13 +261,17 @@ that redirects to your custom export view::
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
+
def export_selected_objects(modeladmin, request, queryset):
- selected = queryset.values_list('pk', flat=True)
+ selected = queryset.values_list("pk", flat=True)
ct = ContentType.objects.get_for_model(queryset.model)
- return HttpResponseRedirect('/export/?ct=%s&ids=%s' % (
- ct.pk,
- ','.join(str(pk) for pk in selected),
- ))
+ return HttpResponseRedirect(
+ "/export/?ct=%s&ids=%s"
+ % (
+ ct.pk,
+ ",".join(str(pk) for pk in selected),
+ )
+ )
As you can see, the action is rather short; all the complex logic would belong
in your export view. This would need to deal with objects of any type, hence
@@ -285,7 +301,7 @@ Making actions available site-wide
<disabling-admin-actions>` -- by passing a second argument to
:meth:`AdminSite.add_action()`::
- admin.site.add_action(export_selected_objects, 'export_selected')
+ admin.site.add_action(export_selected_objects, "export_selected")
.. _disabling-admin-actions:
@@ -307,7 +323,7 @@ Disabling a site-wide action
For example, you can use this method to remove the built-in "delete selected
objects" action::
- admin.site.disable_action('delete_selected')
+ admin.site.disable_action("delete_selected")
Once you've done the above, that action will no longer be available
site-wide.
@@ -316,16 +332,18 @@ Disabling a site-wide action
particular model, list it explicitly in your ``ModelAdmin.actions`` list::
# Globally disable delete selected
- admin.site.disable_action('delete_selected')
+ admin.site.disable_action("delete_selected")
+
# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
- actions = ['some_other_action']
+ actions = ["some_other_action"]
...
+
# This one will
class AnotherModelAdmin(admin.ModelAdmin):
- actions = ['delete_selected', 'a_third_action']
+ actions = ["delete_selected", "a_third_action"]
...
@@ -360,9 +378,9 @@ Conditionally enabling or disabling actions
def get_actions(self, request):
actions = super().get_actions(request)
- if request.user.username[0].upper() != 'J':
- if 'delete_selected' in actions:
- del actions['delete_selected']
+ if request.user.username[0].upper() != "J":
+ if "delete_selected" in actions:
+ del actions["delete_selected"]
return actions
.. _admin-action-permissions:
@@ -374,9 +392,9 @@ Actions may limit their availability to users with specific permissions by
wrapping the action function with the :func:`~django.contrib.admin.action`
decorator and passing the ``permissions`` argument::
- @admin.action(permissions=['change'])
+ @admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
The ``make_published()`` action will only be available to users that pass the
:meth:`.ModelAdmin.has_change_permission` check.
@@ -399,18 +417,19 @@ For example::
from django.contrib import admin
from django.contrib.auth import get_permission_codename
+
class ArticleAdmin(admin.ModelAdmin):
- actions = ['make_published']
+ actions = ["make_published"]
- @admin.action(permissions=['publish'])
+ @admin.action(permissions=["publish"])
def make_published(self, request, queryset):
- queryset.update(status='p')
+ queryset.update(status="p")
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))
+ codename = get_permission_codename("publish", opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
The ``action`` decorator
========================
@@ -422,19 +441,21 @@ The ``action`` decorator
:attr:`~django.contrib.admin.ModelAdmin.actions`::
@admin.action(
- permissions=['publish'],
- description='Mark selected stories as published',
+ permissions=["publish"],
+ description="Mark selected stories as published",
)
def make_published(self, request, queryset):
- queryset.update(status='p')
+ 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'
+ 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
diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt
index 5a95e101ed..cc121a7bed 100644
--- a/docs/ref/contrib/admin/admindocs.txt
+++ b/docs/ref/contrib/admin/admindocs.txt
@@ -62,11 +62,13 @@ A model with useful documentation might look like this::
Stores a single blog entry, related to :model:`blog.Blog` and
:model:`auth.User`.
"""
+
slug = models.SlugField(help_text="A short label, generally used in URLs.")
author = models.ForeignKey(
User,
models.SET_NULL,
- blank=True, null=True,
+ blank=True,
+ null=True,
)
blog = models.ForeignKey(Blog, models.CASCADE)
...
@@ -92,6 +94,7 @@ For example::
from myapp.models import MyModel
+
def my_view(request, slug):
"""
Display an individual :model:`myapp.MyModel`.
@@ -105,8 +108,8 @@ For example::
:template:`myapp/my_template.html`
"""
- context = {'mymodel': MyModel.objects.get(slug=slug)}
- return render(request, 'myapp/my_template.html', context)
+ context = {"mymodel": MyModel.objects.get(slug=slug)}
+ return render(request, "myapp/my_template.html", context)
Template tags and filters reference
===================================
diff --git a/docs/ref/contrib/admin/filters.txt b/docs/ref/contrib/admin/filters.txt
index a3d7bfc9cd..ca8881a645 100644
--- a/docs/ref/contrib/admin/filters.txt
+++ b/docs/ref/contrib/admin/filters.txt
@@ -33,13 +33,13 @@ Each specified field should be either a ``BooleanField``, ``CharField``,
``ManyToManyField``, for example::
class PersonAdmin(admin.ModelAdmin):
- list_filter = ['is_staff', 'company']
+ list_filter = ["is_staff", "company"]
Field names in ``list_filter`` can also span relations
using the ``__`` lookup, for example::
class PersonAdmin(admin.UserAdmin):
- list_filter = ['company__name']
+ list_filter = ["company__name"]
Using a ``SimpleListFilter``
============================
@@ -54,13 +54,14 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
+
class DecadeBornListFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
- title = _('decade born')
+ title = _("decade born")
# Parameter for the filter that will be used in the URL query.
- parameter_name = 'decade'
+ parameter_name = "decade"
def lookups(self, request, model_admin):
"""
@@ -71,8 +72,8 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
in the right sidebar.
"""
return [
- ('80s', _('in the eighties')),
- ('90s', _('in the nineties')),
+ ("80s", _("in the eighties")),
+ ("90s", _("in the nineties")),
]
def queryset(self, request, queryset):
@@ -83,17 +84,18 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
- if self.value() == '80s':
+ if self.value() == "80s":
return queryset.filter(
birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31),
)
- if self.value() == '90s':
+ if self.value() == "90s":
return queryset.filter(
birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31),
)
+
class PersonAdmin(admin.ModelAdmin):
list_filter = [DecadeBornListFilter]
@@ -103,7 +105,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
and ``queryset`` methods, for example::
class AuthDecadeBornListFilter(DecadeBornListFilter):
-
def lookups(self, request, model_admin):
if request.user.is_superuser:
return super().lookups(request, model_admin)
@@ -117,7 +118,6 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
available data::
class AdvancedDecadeBornListFilter(DecadeBornListFilter):
-
def lookups(self, request, model_admin):
"""
Only show the lookups if there actually is
@@ -128,12 +128,12 @@ and ``parameter_name`` attributes, and override the ``lookups`` and
birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31),
).exists():
- yield ('80s', _('in the eighties'))
+ yield ("80s", _("in the eighties"))
if qs.filter(
birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31),
).exists():
- yield ('90s', _('in the nineties'))
+ yield ("90s", _("in the nineties"))
Using a field name and an explicit ``FieldListFilter``
======================================================
@@ -145,7 +145,7 @@ field name and the second element is a class inheriting from
class PersonAdmin(admin.ModelAdmin):
list_filter = [
- ('is_staff', admin.BooleanFieldListFilter),
+ ("is_staff", admin.BooleanFieldListFilter),
]
Here the ``is_staff`` field will use the ``BooleanFieldListFilter``. Specifying
@@ -160,7 +160,7 @@ that relation using ``RelatedOnlyFieldListFilter``::
class BookAdmin(admin.ModelAdmin):
list_filter = [
- ('author', admin.RelatedOnlyFieldListFilter),
+ ("author", admin.RelatedOnlyFieldListFilter),
]
Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will
@@ -173,7 +173,7 @@ allows to store::
class BookAdmin(admin.ModelAdmin):
list_filter = [
- ('title', admin.EmptyFieldListFilter),
+ ("title", admin.EmptyFieldListFilter),
]
By defining a filter using the ``__in`` lookup, it is possible to filter for
@@ -186,10 +186,10 @@ the separator::
class FilterWithCustomSeparator(admin.FieldListFilter):
# custom list separator that should be used to separate values.
- list_separator = '|'
+ list_separator = "|"
def __init__(self, field, request, params, model, model_admin, field_path):
- self.lookup_kwarg = '%s__in' % field_path
+ self.lookup_kwarg = "%s__in" % field_path
super().__init__(field, request, params, model, model_admin, field_path)
def expected_parameters(self):
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 22082812c0..4574e00937 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -89,8 +89,11 @@ Other topics
from django.contrib import admin
from myapp.models import Author
+
class AuthorAdmin(admin.ModelAdmin):
pass
+
+
admin.site.register(Author, AuthorAdmin)
.. admonition:: Do you need a ``ModelAdmin`` object at all?
@@ -117,6 +120,7 @@ The ``register`` decorator
from django.contrib import admin
from .models import Author
+
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
pass
@@ -129,6 +133,7 @@ The ``register`` decorator
from .models import Author, Editor, Reader
from myproject.admin_site import custom_admin_site
+
@admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin):
pass
@@ -185,8 +190,9 @@ subclass::
from django.contrib import admin
+
class AuthorAdmin(admin.ModelAdmin):
- date_hierarchy = 'pub_date'
+ date_hierarchy = "pub_date"
.. attribute:: ModelAdmin.actions
@@ -214,12 +220,12 @@ subclass::
Example::
- date_hierarchy = 'pub_date'
+ date_hierarchy = "pub_date"
You can also specify a field on a related model using the ``__`` lookup,
for example::
- date_hierarchy = 'author__pub_date'
+ date_hierarchy = "author__pub_date"
This will intelligently populate itself based on available data,
e.g. if all the dates are in one month, it'll show the day-level
@@ -240,18 +246,20 @@ subclass::
from django.contrib import admin
+
class AuthorAdmin(admin.ModelAdmin):
- empty_value_display = '-empty-'
+ empty_value_display = "-empty-"
You can also override ``empty_value_display`` for all admin pages with
:attr:`AdminSite.empty_value_display`, or for specific fields like this::
from django.contrib import admin
+
class AuthorAdmin(admin.ModelAdmin):
- list_display = ['name', 'title', 'view_birth_date']
+ list_display = ["name", "title", "view_birth_date"]
- @admin.display(empty_value='???')
+ @admin.display(empty_value="???")
def view_birth_date(self, obj):
return obj.birth_date
@@ -264,6 +272,7 @@ subclass::
from django.db import models
+
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3)
@@ -275,11 +284,13 @@ subclass::
from django.contrib import admin
+
class AuthorAdmin(admin.ModelAdmin):
- fields = ['name', 'title']
+ fields = ["name", "title"]
+
class AuthorAdmin(admin.ModelAdmin):
- exclude = ['birth_date']
+ exclude = ["birth_date"]
Since the Author model only has three fields, ``name``, ``title``, and
``birth_date``, the forms resulting from the above declarations will
@@ -294,7 +305,7 @@ subclass::
:class:`django.contrib.flatpages.models.FlatPage` model as follows::
class FlatPageAdmin(admin.ModelAdmin):
- fields = ['url', 'title', 'content']
+ fields = ["url", "title", "content"]
In the above example, only the fields ``url``, ``title`` and ``content``
will be displayed, sequentially, in the form. ``fields`` can contain
@@ -314,7 +325,7 @@ subclass::
own line::
class FlatPageAdmin(admin.ModelAdmin):
- fields = [('url', 'title'), 'content']
+ fields = [("url", "title"), "content"]
.. admonition:: Note
@@ -345,15 +356,22 @@ subclass::
from django.contrib import admin
+
class FlatPageAdmin(admin.ModelAdmin):
fieldsets = [
- (None, {
- 'fields': ['url', 'title', 'content', 'sites'],
- }),
- ('Advanced options', {
- 'classes': ['collapse'],
- 'fields': ['registration_required', 'template_name'],
- }),
+ (
+ None,
+ {
+ "fields": ["url", "title", "content", "sites"],
+ },
+ ),
+ (
+ "Advanced options",
+ {
+ "classes": ["collapse"],
+ "fields": ["registration_required", "template_name"],
+ },
+ ),
]
This results in an admin page that looks like:
@@ -374,7 +392,7 @@ subclass::
Example::
{
- 'fields': ['first_name', 'last_name', 'address', 'city', 'state'],
+ "fields": ["first_name", "last_name", "address", "city", "state"],
}
As with the :attr:`~ModelAdmin.fields` option, to display multiple
@@ -383,7 +401,7 @@ subclass::
the same line::
{
- 'fields': [('first_name', 'last_name'), 'address', 'city', 'state'],
+ "fields": [("first_name", "last_name"), "address", "city", "state"],
}
``fields`` can contain values defined in
@@ -399,7 +417,7 @@ subclass::
Example::
{
- 'classes': ['wide', 'extrapretty'],
+ "classes": ["wide", "extrapretty"],
}
Two useful classes defined by the default admin site stylesheet are
@@ -471,14 +489,15 @@ subclass::
from django.contrib import admin
from myapp.models import Person
- class PersonForm(forms.ModelForm):
+ class PersonForm(forms.ModelForm):
class Meta:
model = Person
- exclude = ['name']
+ exclude = ["name"]
+
class PersonAdmin(admin.ModelAdmin):
- exclude = ['age']
+ exclude = ["age"]
form = PersonForm
In the above example, the "age" field will be excluded but the "name"
@@ -504,9 +523,10 @@ subclass::
from myapp.models import MyModel
from myapp.widgets import RichTextEditorWidget
+
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
- models.TextField: {'widget': RichTextEditorWidget},
+ models.TextField: {"widget": RichTextEditorWidget},
}
Note that the key in the dictionary is the actual field class, *not* a
@@ -540,7 +560,7 @@ subclass::
Example::
- list_display = ['first_name', 'last_name']
+ list_display = ["first_name", "last_name"]
If you don't set ``list_display``, the admin site will display a single
column that displays the ``__str__()`` representation of each object.
@@ -552,14 +572,15 @@ subclass::
* The name of a model field. For example::
class PersonAdmin(admin.ModelAdmin):
- list_display = ['first_name', 'last_name']
+ list_display = ["first_name", "last_name"]
* A callable that accepts one argument, the model instance. For example::
- @admin.display(description='Name')
+ @admin.display(description="Name")
def upper_case_name(obj):
return f"{obj.first_name} {obj.last_name}".upper()
+
class PersonAdmin(admin.ModelAdmin):
list_display = [upper_case_name]
@@ -567,9 +588,9 @@ subclass::
the model instance. For example::
class PersonAdmin(admin.ModelAdmin):
- list_display = ['upper_case_name']
+ list_display = ["upper_case_name"]
- @admin.display(description='Name')
+ @admin.display(description="Name")
def upper_case_name(self, obj):
return f"{obj.first_name} {obj.last_name}".upper()
@@ -579,17 +600,19 @@ subclass::
from django.contrib import admin
from django.db import models
+
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
- @admin.display(description='Birth decade')
+ @admin.display(description="Birth decade")
def decade_born_in(self):
decade = self.birthday.year // 10 * 10
- return f'{decade}’s'
+ return f"{decade}’s"
+
class PersonAdmin(admin.ModelAdmin):
- list_display = ['name', 'decade_born_in']
+ list_display = ["name", "decade_born_in"]
A few special cases to note about ``list_display``:
@@ -616,6 +639,7 @@ subclass::
from django.db import models
from django.utils.html import format_html
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -630,8 +654,9 @@ subclass::
self.last_name,
)
+
class PersonAdmin(admin.ModelAdmin):
- list_display = ['first_name', 'last_name', 'colored_name']
+ list_display = ["first_name", "last_name", "colored_name"]
* As some examples have already demonstrated, when using a callable, a
model method, or a ``ModelAdmin`` method, you can customize the column's
@@ -645,21 +670,21 @@ subclass::
from django.contrib import admin
- admin.site.empty_value_display = '(None)'
+ admin.site.empty_value_display = "(None)"
You can also use :attr:`ModelAdmin.empty_value_display`::
class PersonAdmin(admin.ModelAdmin):
- empty_value_display = 'unknown'
+ empty_value_display = "unknown"
Or on a field level::
class PersonAdmin(admin.ModelAdmin):
- list_display = ['name', 'birth_date_view']
+ list_display = ["name", "birth_date_view"]
- @admin.display(empty_value='unknown')
+ @admin.display(empty_value="unknown")
def birth_date_view(self, obj):
- return obj.birth_date
+ return obj.birth_date
* If the string given is a method of the model, ``ModelAdmin`` or a
callable that returns ``True``, ``False``, or ``None``, Django will
@@ -670,6 +695,7 @@ subclass::
from django.contrib import admin
from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
birthday = models.DateField()
@@ -678,13 +704,14 @@ subclass::
def born_in_fifties(self):
return 1950 <= self.birthday.year < 1960
+
class PersonAdmin(admin.ModelAdmin):
- list_display = ['name', 'born_in_fifties']
+ list_display = ["name", "born_in_fifties"]
* The ``__str__()`` method is just as valid in ``list_display`` as any
other model method, so it's perfectly OK to do this::
- list_display = ['__str__', 'some_other_field']
+ list_display = ["__str__", "some_other_field"]
* Usually, elements of ``list_display`` that aren't actual database
fields can't be used in sorting (because Django does all the sorting
@@ -699,11 +726,12 @@ subclass::
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)
- @admin.display(ordering='first_name')
+ @admin.display(ordering="first_name")
def colored_first_name(self):
return format_html(
'<span style="color: #{};">{}</span>',
@@ -711,8 +739,9 @@ subclass::
self.first_name,
)
+
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
trying to sort by ``colored_first_name`` in the admin.
@@ -721,7 +750,7 @@ subclass::
hyphen prefix on the field name. Using the above example, this would look
like::
- @admin.display(ordering='-first_name')
+ @admin.display(ordering="-first_name")
def colored_first_name(self):
...
@@ -733,10 +762,11 @@ subclass::
title = models.CharField(max_length=255)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
+
class BlogAdmin(admin.ModelAdmin):
- list_display = ['title', 'author', 'author_first_name']
+ list_display = ["title", "author", "author_first_name"]
- @admin.display(ordering='author__first_name')
+ @admin.display(ordering="author__first_name")
def author_first_name(self, obj):
return obj.author.first_name
@@ -746,13 +776,14 @@ subclass::
from django.db.models import Value
from django.db.models.functions import Concat
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
- @admin.display(ordering=Concat('first_name', Value(' '), 'last_name'))
+ @admin.display(ordering=Concat("first_name", Value(" "), "last_name"))
def full_name(self):
- return self.first_name + ' ' + self.last_name
+ return self.first_name + " " + self.last_name
* Elements of ``list_display`` can also be properties
::
@@ -763,14 +794,15 @@ subclass::
@property
@admin.display(
- ordering='last_name',
- description='Full name of the person',
+ ordering="last_name",
+ description="Full name of the person",
)
def full_name(self):
- return self.first_name + ' ' + self.last_name
+ return self.first_name + " " + self.last_name
+
class PersonAdmin(admin.ModelAdmin):
- list_display = ['full_name']
+ 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
@@ -779,9 +811,11 @@ subclass::
must be used::
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.admin_order_field = 'last_name'
+ my_property.admin_order_field = "last_name"
full_name = property(my_property)
@@ -823,13 +857,13 @@ subclass::
linked on the change list page::
class PersonAdmin(admin.ModelAdmin):
- list_display = ['first_name', 'last_name', 'birthday']
- list_display_links = ['first_name', 'last_name']
+ list_display = ["first_name", "last_name", "birthday"]
+ list_display_links = ["first_name", "last_name"]
In this example, the change list page grid will have no links::
class AuditEntryAdmin(admin.ModelAdmin):
- list_display = ['timestamp', 'message']
+ list_display = ["timestamp", "message"]
list_display_links = None
.. _admin-list-editable:
@@ -896,7 +930,7 @@ subclass::
``select_related`` as parameters. For example::
class ArticleAdmin(admin.ModelAdmin):
- list_select_related = ['author', 'category']
+ list_select_related = ["author", "category"]
will call ``select_related('author', 'category')``.
@@ -1013,11 +1047,12 @@ subclass::
``question_text`` field and ordered by the ``date_created`` field::
class QuestionAdmin(admin.ModelAdmin):
- ordering = ['date_created']
- search_fields = ['question_text']
+ ordering = ["date_created"]
+ search_fields = ["question_text"]
+
class ChoiceAdmin(admin.ModelAdmin):
- autocomplete_fields = ['question']
+ autocomplete_fields = ["question"]
.. admonition:: Performance considerations for large datasets
@@ -1084,18 +1119,19 @@ subclass::
from django.utils.html import format_html_join
from django.utils.safestring import mark_safe
+
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')
+ @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
# line by a linebreak
return format_html_join(
- mark_safe('<br>'),
- '{}',
+ mark_safe("<br>"),
+ "{}",
((line,) for line in instance.get_full_address()),
) or mark_safe("<span class='errors'>I can't determine this address.</span>")
@@ -1139,13 +1175,13 @@ subclass::
``TextField``. You can also perform a related lookup on a ``ForeignKey`` or
``ManyToManyField`` with the lookup API "follow" notation::
- search_fields = ['foreign_key__related_fieldname']
+ search_fields = ["foreign_key__related_fieldname"]
For example, if you have a blog entry with an author, the following
definition would enable searching blog entries by the email address of the
author::
- search_fields = ['user__email']
+ search_fields = ["user__email"]
When somebody does a search in the admin search box, Django splits the
search query into words and returns all objects that contain each of the
@@ -1251,6 +1287,7 @@ subclass::
from django.contrib import admin
+
class PersonAdmin(admin.ModelAdmin):
view_on_site = False
@@ -1260,10 +1297,11 @@ subclass::
from django.contrib import admin
from django.urls import reverse
+
class PersonAdmin(admin.ModelAdmin):
def view_on_site(self, obj):
- url = reverse('person-detail', kwargs={'slug': obj.slug})
- return 'https://example.com' + url
+ url = reverse("person-detail", kwargs={"slug": obj.slug})
+ return "https://example.com" + url
Custom template options
~~~~~~~~~~~~~~~~~~~~~~~
@@ -1328,6 +1366,7 @@ templates used by the :class:`ModelAdmin` views:
from django.contrib import admin
+
class ArticleAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
@@ -1375,12 +1414,11 @@ templates used by the :class:`ModelAdmin` views:
to the :attr:`ordering` attribute. For example::
class PersonAdmin(admin.ModelAdmin):
-
def get_ordering(self, request):
if request.user.is_superuser:
- return ['name', 'rank']
+ return ["name", "rank"]
else:
- return ['name']
+ return ["name"]
.. method:: ModelAdmin.get_search_results(request, queryset, search_term)
@@ -1401,12 +1439,14 @@ templates used by the :class:`ModelAdmin` views:
For example, to search by ``name`` and ``age``, you could use::
class PersonAdmin(admin.ModelAdmin):
- list_display = ['name', 'age']
- search_fields = ['name']
+ list_display = ["name", "age"]
+ search_fields = ["name"]
def get_search_results(self, request, queryset, search_term):
queryset, may_have_duplicates = super().get_search_results(
- request, queryset, search_term,
+ request,
+ queryset,
+ search_term,
)
try:
search_term_as_int = int(search_term)
@@ -1533,9 +1573,8 @@ templates used by the :class:`ModelAdmin` views:
For example, to prevent one or more columns from being sortable::
class PersonAdmin(admin.ModelAdmin):
-
def get_sortable_by(self, request):
- return {*self.get_list_display(request)} - {'rank'}
+ return {*self.get_list_display(request)} - {"rank"}
.. method:: ModelAdmin.get_inline_instances(request, obj=None)
@@ -1575,21 +1614,20 @@ templates used by the :class:`ModelAdmin` views:
from django.template.response import TemplateResponse
from django.urls import path
+
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
- my_urls = [
- path('my_view/', self.admin_site.admin_view(self.my_view))
- ]
+ my_urls = [path("my_view/", self.admin_site.admin_view(self.my_view))]
return my_urls + urls
def my_view(self, request):
# ...
context = dict(
- # Include common variables for rendering the admin template.
- self.admin_site.each_context(request),
- # Anything else you want in the context...
- key=value,
+ # Include common variables for rendering the admin template.
+ self.admin_site.each_context(request),
+ # Anything else you want in the context...
+ key=value,
)
return TemplateResponse(request, "sometemplate.html", context)
@@ -1629,7 +1667,7 @@ templates used by the :class:`ModelAdmin` views:
performed, you can pass a ``cacheable=True`` argument to
``AdminSite.admin_view()``::
- path('my_view/', self.admin_site.admin_view(self.my_view, cacheable=True))
+ path("my_view/", self.admin_site.admin_view(self.my_view, cacheable=True))
``ModelAdmin`` views have ``model_admin`` attributes. Other
``AdminSite`` views have ``admin_site`` attributes.
@@ -1647,7 +1685,7 @@ templates used by the :class:`ModelAdmin` views:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if request.user.is_superuser:
- kwargs['form'] = MySuperuserForm
+ kwargs["form"] = MySuperuserForm
return super().get_form(request, obj, **kwargs)
You may also return a custom :class:`~django.forms.ModelForm` class
@@ -1692,7 +1730,8 @@ templates used by the :class:`ModelAdmin` views:
class CountryAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields['capital'].queryset = self.instance.cities.all()
+ self.fields["capital"].queryset = self.instance.cities.all()
+
class CountryAdmin(admin.ModelAdmin):
form = CountryAdminForm
@@ -1723,12 +1762,12 @@ templates used by the :class:`ModelAdmin` views:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_choice_field(self, db_field, request, **kwargs):
if db_field.name == "status":
- kwargs['choices'] = [
- ('accepted', 'Accepted'),
- ('denied', 'Denied'),
+ kwargs["choices"] = [
+ ("accepted", "Accepted"),
+ ("denied", "Denied"),
]
if request.user.is_superuser:
- kwargs['choices'].append(('ready', 'Ready for deployment'))
+ kwargs["choices"].append(("ready", "Ready for deployment"))
return super().formfield_for_choice_field(db_field, request, **kwargs)
.. admonition:: Note
@@ -1753,9 +1792,11 @@ templates used by the :class:`ModelAdmin` views:
from django import forms
+
class MyForm(forms.ModelForm):
pass
+
class MyModelAdmin(admin.ModelAdmin):
def get_changelist_form(self, request, **kwargs):
return MyForm
@@ -1778,12 +1819,14 @@ templates used by the :class:`ModelAdmin` views:
from django.forms import BaseModelFormSet
+
class MyAdminFormSet(BaseModelFormSet):
pass
+
class MyModelAdmin(admin.ModelAdmin):
def get_changelist_formset(self, request, **kwargs):
- kwargs['formset'] = MyAdminFormSet
+ kwargs["formset"] = MyAdminFormSet
return super().get_changelist_formset(request, **kwargs)
.. method:: ModelAdmin.lookup_allowed(lookup, value)
@@ -1930,7 +1973,7 @@ templates used by the :class:`ModelAdmin` views:
def get_formset_kwargs(self, request, obj, inline, prefix):
return {
**super().get_formset_kwargs(request, obj, inline, prefix),
- 'form_kwargs': {'request': request},
+ "form_kwargs": {"request": request},
}
You can also use it to set ``initial`` for formset forms.
@@ -1946,7 +1989,7 @@ templates used by the :class:`ModelAdmin` views:
``{'fieldname': 'fieldval'}``::
def get_changeform_initial_data(self, request):
- return {'name': 'custom_initial_value'}
+ return {"name": "custom_initial_value"}
.. method:: ModelAdmin.get_deleted_objects(objs, request)
@@ -2018,19 +2061,21 @@ example, the change view is overridden so that the rendered template is
provided some extra mapping data that would not otherwise be available::
class MyModelAdmin(admin.ModelAdmin):
-
# A template for a very customized change view:
- change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html'
+ change_form_template = "admin/myapp/extras/openstreetmap_change_form.html"
def get_osm_info(self):
# ...
pass
- def change_view(self, request, object_id, form_url='', extra_context=None):
+ def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
- extra_context['osm_data'] = self.get_osm_info()
+ extra_context["osm_data"] = self.get_osm_info()
return super().change_view(
- request, object_id, form_url, extra_context=extra_context,
+ request,
+ object_id,
+ form_url,
+ extra_context=extra_context,
)
These views return :class:`~django.template.response.TemplateResponse`
@@ -2136,21 +2181,25 @@ information.
from django.db import models
+
class Author(models.Model):
- name = models.CharField(max_length=100)
+ name = models.CharField(max_length=100)
+
class Book(models.Model):
- author = models.ForeignKey(Author, on_delete=models.CASCADE)
- title = models.CharField(max_length=100)
+ author = models.ForeignKey(Author, on_delete=models.CASCADE)
+ title = models.CharField(max_length=100)
You can edit the books authored by an author on the author page. You add
inlines to a model by specifying them in a ``ModelAdmin.inlines``::
from django.contrib import admin
+
class BookInline(admin.TabularInline):
model = Book
+
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
@@ -2383,9 +2432,14 @@ Take this model for instance::
from django.db import models
+
class Friendship(models.Model):
- to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="friends")
- from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="from_friends")
+ to_person = models.ForeignKey(
+ Person, on_delete=models.CASCADE, related_name="friends"
+ )
+ from_person = models.ForeignKey(
+ Person, on_delete=models.CASCADE, related_name="from_friends"
+ )
If you wanted to display an inline on the ``Person`` admin add/change pages
you need to explicitly define the foreign key since it is unable to do so
@@ -2394,10 +2448,12 @@ automatically::
from django.contrib import admin
from myapp.models import Friendship
+
class FriendshipInline(admin.TabularInline):
model = Friendship
fk_name = "to_person"
+
class PersonAdmin(admin.ModelAdmin):
inlines = [
FriendshipInline,
@@ -2418,31 +2474,36 @@ Suppose we have the following models::
from django.db import models
+
class Person(models.Model):
name = models.CharField(max_length=128)
+
class Group(models.Model):
name = models.CharField(max_length=128)
- members = models.ManyToManyField(Person, related_name='groups')
+ members = models.ManyToManyField(Person, related_name="groups")
If you want to display many-to-many relations using an inline, you can do
so by defining an ``InlineModelAdmin`` object for the relationship::
from django.contrib import admin
+
class MembershipInline(admin.TabularInline):
model = Group.members.through
+
class PersonAdmin(admin.ModelAdmin):
inlines = [
MembershipInline,
]
+
class GroupAdmin(admin.ModelAdmin):
inlines = [
MembershipInline,
]
- exclude = ['members']
+ exclude = ["members"]
There are two features worth noting in this example.
@@ -2482,12 +2543,15 @@ we can do this with inline admin models. Suppose we have the following models::
from django.db import models
+
class Person(models.Model):
name = models.CharField(max_length=128)
+
class Group(models.Model):
name = models.CharField(max_length=128)
- members = models.ManyToManyField(Person, through='Membership')
+ members = models.ManyToManyField(Person, through="Membership")
+
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
@@ -2511,6 +2575,7 @@ Now create admin views for the ``Person`` and ``Group`` models::
class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline]
+
class GroupAdmin(admin.ModelAdmin):
inlines = [MembershipInline]
@@ -2533,12 +2598,14 @@ you have the following models::
from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models
+
class Image(models.Model):
image = models.ImageField(upload_to="images")
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
+
class Product(models.Model):
name = models.CharField(max_length=100)
@@ -2557,14 +2624,17 @@ any other inline. In your ``admin.py`` for this example app::
from myapp.models import Image, Product
+
class ImageInline(GenericTabularInline):
model = Image
+
class ProductAdmin(admin.ModelAdmin):
inlines = [
ImageInline,
]
+
admin.site.register(Product, ProductAdmin)
See the :doc:`contenttypes documentation </ref/contrib/contenttypes>` for more
@@ -2955,7 +3025,7 @@ In this example, we register the default ``AdminSite`` instance
from django.urls import path
urlpatterns = [
- path('admin/', admin.site.urls),
+ path("admin/", admin.site.urls),
]
.. _customizing-adminsite:
@@ -2977,10 +3047,12 @@ to reference your :class:`AdminSite` subclass.
from .models import MyModel
+
class MyAdminSite(admin.AdminSite):
- site_header = 'Monty Python administration'
+ site_header = "Monty Python administration"
- admin_site = MyAdminSite(name='myadmin')
+
+ admin_site = MyAdminSite(name="myadmin")
admin_site.register(MyModel)
@@ -2992,7 +3064,7 @@ to reference your :class:`AdminSite` subclass.
from myapp.admin import admin_site
urlpatterns = [
- path('myadmin/', admin_site.urls),
+ path("myadmin/", admin_site.urls),
]
Note that you may not want autodiscovery of ``admin`` modules when using your
@@ -3016,6 +3088,7 @@ returns a site instance.
from django.contrib import admin
+
class MyAdminSite(admin.AdminSite):
...
@@ -3024,15 +3097,16 @@ returns a site instance.
from django.contrib.admin.apps import AdminConfig
+
class MyAdminConfig(AdminConfig):
- default_site = 'myproject.admin.MyAdminSite'
+ default_site = "myproject.admin.MyAdminSite"
.. code-block:: python
:caption: ``myproject/settings.py``
INSTALLED_APPS = [
# ...
- 'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
+ "myproject.apps.MyAdminConfig", # replaces 'django.contrib.admin'
# ...
]
@@ -3055,8 +3129,8 @@ respectively::
from myproject.admin import advanced_site, basic_site
urlpatterns = [
- path('basic-admin/', basic_site.urls),
- path('advanced-admin/', advanced_site.urls),
+ path("basic-admin/", basic_site.urls),
+ path("advanced-admin/", advanced_site.urls),
]
``AdminSite`` instances take a single argument to their constructor, their
@@ -3093,24 +3167,24 @@ your URLconf. Specifically, add these four patterns::
from django.contrib.auth import views as auth_views
path(
- 'admin/password_reset/',
+ "admin/password_reset/",
auth_views.PasswordResetView.as_view(),
- name='admin_password_reset',
+ name="admin_password_reset",
),
path(
- 'admin/password_reset/done/',
+ "admin/password_reset/done/",
auth_views.PasswordResetDoneView.as_view(),
- name='password_reset_done',
+ name="password_reset_done",
),
path(
- 'reset/<uidb64>/<token>/',
+ "reset/<uidb64>/<token>/",
auth_views.PasswordResetConfirmView.as_view(),
- name='password_reset_confirm',
+ name="password_reset_confirm",
),
path(
- 'reset/done/',
+ "reset/done/",
auth_views.PasswordResetCompleteView.as_view(),
- name='password_reset_complete',
+ name="password_reset_complete",
),
(This assumes you've added the admin at ``admin/`` and requires that you put
@@ -3245,7 +3319,7 @@ call:
>>> from django.urls import reverse
>>> c = Choice.objects.get(...)
- >>> change_url = reverse('admin:polls_choice_change', args=(c.id,))
+ >>> change_url = reverse("admin:polls_choice_change", args=(c.id,))
This will find the first registered instance of the admin application
(whatever the instance name), and resolve to the view for changing
@@ -3258,7 +3332,7 @@ if you specifically wanted the admin view from the admin instance named
.. code-block:: pycon
- >>> change_url = reverse('admin:polls_choice_change', args=(c.id,), current_app='custom')
+ >>> change_url = reverse("admin:polls_choice_change", args=(c.id,), current_app="custom")
For more details, see the documentation on :ref:`reversing namespaced URLs
<topics-http-reversing-url-namespaces>`.
@@ -3289,8 +3363,8 @@ The ``display`` decorator
@admin.display(
boolean=True,
- ordering='-publish_date',
- description='Is Published?',
+ ordering="-publish_date",
+ description="Is Published?",
)
def is_published(self, obj):
return obj.publish_date is not None
@@ -3300,9 +3374,11 @@ The ``display`` decorator
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?'
+ 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
@@ -3341,6 +3417,7 @@ The ``staff_member_required`` decorator
from django.contrib.admin.views.decorators import staff_member_required
+
@staff_member_required
def my_view(request):
...