diff options
| author | django-bot <ops@djangoproject.com> | 2023-03-01 13:35:43 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-03-01 13:39:03 +0100 |
| commit | 62510f01e76ad0526c94ea6d1bc6399c1ddf3df4 (patch) | |
| tree | 79844be246eba809a4ca09c6f4c3448f2276321a /docs/ref/contrib/admin | |
| parent | 32f224e359c68e70e3f9a230be0265dcd6677079 (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.txt | 111 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/admindocs.txt | 9 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/filters.txt | 34 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/index.txt | 333 |
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): ... |
