0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-11-28 00:17:06 +01:00
wagtail/docs/reference/contrib/modeladmin/indexview.md
LB Johnston 8908c38dcd Documentation - fix up ModelAdmin items
- Link to ModelAdmin showed incorrectly as `Index app` not `ModelAdmin app`
- Many code snippets were nested, causing double nesting to appear in published docs
2023-02-06 10:26:46 +00:00

22 KiB
Raw Blame History

Customising IndexView - the listing view

For the sake of consistency, this section of the docs will refer to the listing view as IndexView, because that is the view class that does all the heavy lifting.

You can use the following attributes and methods on the ModelAdmin class to alter how your model data is treated and represented by the IndexView.

---
local:
depth: 1
---

(modeladmin_list_display)=

ModelAdmin.list_display

Expected value: A list or tuple, where each item is the name of a field or single-argument callable on your model, or a similarly simple method defined on the ModelAdmin class itself.

Default value: ('__str__',)

Set list_display to control which fields are displayed in the IndexView for your model.

You have three possible values that can be used in list_display:

  • A field of the model. For example:

    from wagtail.contrib.modeladmin.options import ModelAdmin
    from .models import Person
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('first_name', 'last_name')
    
  • The name of a custom method on your ModelAdmin class, that accepts a single parameter for the model instance. For example:

    from wagtail.contrib.modeladmin.options import ModelAdmin
    from .models import Person
    
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('upper_case_name',)
    
        def upper_case_name(self, obj):
            return ("%s %s" % (obj.first_name, obj.last_name)).upper()
        upper_case_name.short_description = 'Name'
    
  • The name of a method on your Model class that accepts only self as an argument. For example:

    from django.db import models
    from wagtail.contrib.modeladmin.options import ModelAdmin
    
    class Person(models.Model):
        name = models.CharField(max_length=50)
        birthday = models.DateField()
    
        def decade_born_in(self):
            return self.birthday.strftime('%Y')[:3] + "0's"
        decade_born_in.short_description = 'Birth decade'
    
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('name', 'decade_born_in')
    

A few special cases to note about list_display:

  • If the field is a ForeignKey, Django will display the output of __str__() of the related object.

  • If the string provided is a method of the model or ModelAdmin class, Django will HTML-escape the output by default. To escape user input and allow your own unescaped tags, use format_html(). For example:

    from django.db import models
    from django.utils.html import format_html
    from wagtail.contrib.modeladmin.options import ModelAdmin
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
        color_code = models.CharField(max_length=6)
    
        def styled_name(self):
            return format_html(
                '<span style="color: #{};">{} {}</span>',
                self.color_code,
                self.first_name,
                self.last_name,
            )
    
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('first_name', 'last_name', 'styled_name')
    
  • If the value of a field is None, an empty string, or an iterable without elements, Wagtail will display a dash (-) for that column. You can override this by setting empty_value_display on your ModelAdmin class. For example:

        from wagtail.contrib.modeladmin.options import ModelAdmin
    
        class PersonAdmin(ModelAdmin):
            empty_value_display = 'N/A'
            ...
    

    Or, if you'd like to change the value used depending on the field, you can override ModelAdmin's get_empty_value_display() method, like so:

    from django.db import models
    from wagtail.contrib.modeladmin.options import ModelAdmin
    
    
    class Person(models.Model):
        name = models.CharField(max_length=100)
        nickname = models.CharField(blank=True, max_length=100)
        likes_cat_gifs = models.NullBooleanField()
    
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('name', 'nickname', 'likes_cat_gifs')
    
        def get_empty_value_display(self, field_name=None):
            if field_name == 'nickname':
                return 'None given'
            if field_name == 'likes_cat_gifs':
                return 'Unanswered'
            return super().get_empty_value_display(field_name)
    

    The __str__() method is just as valid in list_display as any other model method, so its perfectly OK to do this:

    list_display = ('__str__', 'some_other_field')
    

    By default, the ability to sort results by an item in list_display is only offered when it's a field that has an actual database value (because sorting is done at the database level). However, if the output of the method is representative of a database field, you can indicate this fact by setting the admin_order_field attribute on that method, like so:

    from django.db import models
    from django.utils.html import format_html
    from wagtail.contrib.modeladmin.options import ModelAdmin
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
        color_code = models.CharField(max_length=6)
    
        def styled_first_name(self):
            return format_html(
                '<span style="color: #{};">{}</span>',
                self.color_code,
                self.first_name,
            )
        styled_first_name.admin_order_field = 'first_name'
    
    
    class PersonAdmin(ModelAdmin):
        model = Person
        list_display = ('styled_first_name', 'last_name')
    

    The above will tell Wagtail to order by the first_name field when trying to sort by styled_first_name in the index view.

    The above will tell Wagtail to order by the first_name field when trying to sort by styled_first_name in the index view.

    To indicate descending order with admin_order_field you can use a hyphen prefix on the field name. Using the above example, this would look like:

    .. code-block:: python

    styled_first_name.admin_order_field = '-first_name'
    

    admin_order_field supports query lookups to sort by values on related models, too. This example includes an “author first name” column in the list display and allows sorting it by first name:

    from django.db import models
    
    
    class Blog(models.Model):
        title = models.CharField(max_length=255)
        author = models.ForeignKey(Person, on_delete=models.CASCADE)
    
        def author_first_name(self, obj):
            return obj.author.first_name
    
        author_first_name.admin_order_field = 'author__first_name'
    
  • Elements of list_display can also be properties. Please note however, that due to the way properties work in Python, setting short_description on a property is only possible when using the property() function and not with the @property decorator.

    For example:

    from django.db import models
    from wagtail.contrib.modeladmin.options import ModelAdmin
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
    
        def full_name_property(self):
            return self.first_name + ' ' + self.last_name
        full_name_property.short_description = "Full name of the person"
    
        full_name = property(full_name_property)
    
    
    class PersonAdmin(ModelAdmin):
        list_display = ('full_name',)
    

(modeladmin_list_export)=

ModelAdmin.list_export

Expected value: A list or tuple, where each item is the name of a field or single-argument callable on your model, or a similarly simple method defined on the ModelAdmin class itself.

Set list_export to set the fields you wish to be exported as columns when downloading a spreadsheet version of your index_view

class PersonAdmin(ModelAdmin):
    list_export = ('is_staff', 'company')

(modeladmin_list_filter)=

ModelAdmin.list_filter

Expected value: A list or tuple, where each item is the name of model field of type BooleanField, CharField, DateField, DateTimeField, IntegerField or ForeignKey.

Set list_filter to activate filters in the right sidebar of the list page for your model. For example:

class PersonAdmin(ModelAdmin):
    list_filter = ('is_staff', 'company')

(modeladmin_export_filename)=

ModelAdmin.export_filename

Expected value: A string specifying the filename of an exported spreadsheet, without file extensions.

class PersonAdmin(ModelAdmin):
    export_filename = 'people_spreadsheet'

(modeladmin_search_fields)=

ModelAdmin.search_fields

Expected value: A list or tuple, where each item is the name of a model field of type CharField, TextField, RichTextField or StreamField.

Set search_fields to enable a search box at the top of the index page for your model. You should add names of any fields on the model that should be searched whenever somebody submits a search query using the search box.

Searching is handled via Django's QuerySet API by default, see about changing this behaviour. This means by default it will work for all models, whatever search backend your project is using, and without any additional setup or configuration.

(modeladmin_search_handler_class)=

ModelAdmin.search_handler_class

Expected value: A subclass of wagtail.contrib.modeladmin.helpers.search.BaseSearchHandler

The default value is DjangoORMSearchHandler, which uses the Django ORM to perform lookups on the fields specified by search_fields.

If you would prefer to use the built-in Wagtail search backend to search your models, you can use the WagtailBackendSearchHandler class instead. For example:

from wagtail.contrib.modeladmin.helpers import WagtailBackendSearchHandler

from .models import Person

class PersonAdmin(ModelAdmin):
    model = Person
    search_handler_class = WagtailBackendSearchHandler

Extra considerations when using WagtailBackendSearchHandler

ModelAdmin.search_fields is used differently

The value of search_fields is passed to the underlying search backend to limit the fields used when matching. Each item in the list must be indexed on your model using .

To allow matching on any indexed field, set the search_fields attribute on your ModelAdmin class to None, or remove it completely.

Indexing extra fields using index.FilterField

The underlying search backend must be able to interpret all of the fields and relationships used in the queryset created by IndexView, including those used in prefetch() or select_related() queryset methods, or used in list_display, list_filter or ordering.

Be sure to test things thoroughly in a development environment (ideally using the same search backend as you use in production). Wagtail will raise an IndexError if the backend encounters something it does not understand, and will tell you what you need to change.

(modeladmin_extra_search_kwargs)=

ModelAdmin.extra_search_kwargs

Expected value: A dictionary of keyword arguments that will be passed on to the search() method of search_handler_class.

For example, to override the WagtailBackendSearchHandler default operator you could do the following:

from wagtail.contrib.modeladmin.helpers import WagtailBackendSearchHandler
from wagtail.search.utils import OR

from .models import IndexedModel

class DemoAdmin(ModelAdmin):
    model = IndexedModel
    search_handler_class = WagtailBackendSearchHandler
    extra_search_kwargs = {'operator': OR}

(modeladmin_ordering)=

ModelAdmin.ordering

Expected value: A list or tuple in the same format as a models ordering parameter.

Set ordering to specify the default ordering of objects when listed by IndexView. If not provided, the models default ordering will be respected.

If you need to specify a dynamic order (for example, depending on user or language) you can override the get_ordering() method instead.

(modeladmin_list_per_page)=

ModelAdmin.list_per_page

Expected value: A positive integer

Set list_per_page to control how many items appear on each paginated page of the index view. By default, this is set to 100.

(modeladmin_get_queryset)=

ModelAdmin.get_queryset()

Must return: A QuerySet

The get_queryset method returns the 'base' QuerySet for your model, to which any filters and search queries are applied. By default, the all() method of your model's default manager is used. But, if for any reason you only want a certain sub-set of objects to appear in the IndexView listing, overriding the get_queryset method on your ModelAdmin class can help you with that. The method takes an HttpRequest object as a parameter, so limiting objects by the current logged-in user is possible.

For example:

from django.db import models
from wagtail.contrib.modeladmin.options import ModelAdmin

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    managed_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)


class PersonAdmin(ModelAdmin):
    model = Person
    list_display = ('first_name', 'last_name')

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        # Only show people managed by the current user
        return qs.filter(managed_by=request.user)

(modeladmin_get_extra_attrs_for_row)=

ModelAdmin.get_extra_attrs_for_row()

Must return: A dictionary

The get_extra_attrs_for_row method allows you to add html attributes to the opening <tr> tag for each result, in addition to the data-object_pk and class attributes already added by the result_row_display template tag.

If you want to add additional CSS classes, simply provide those class names as a string value using the 'class' key, and the odd/even will be appended to your custom class names when rendering.

For example, if you wanted to add some additional class names based on field values, you could do something like:

from decimal import Decimal
from django.db import models
from wagtail.contrib.modeladmin.options import ModelAdmin

class BankAccount(models.Model):
    name = models.CharField(max_length=50)
    account_number = models.CharField(max_length=50)
    balance = models.DecimalField(max_digits=5, num_places=2)


class BankAccountAdmin(ModelAdmin):
    list_display = ('name', 'account_number', 'balance')

    def get_extra_attrs_for_row(self, obj, context):
        if obj.balance < Decimal('0.00'):
            classname = 'balance-negative'
        else:
            classname = 'balance-positive'
        return {
            'class': classname,
        }

(modeladmin_get_extra_class_names_for_field_col)=

ModelAdmin.get_extra_class_names_for_field_col()

Must return: A list

The get_extra_class_names_for_field_col method allows you to add additional CSS class names to any of the columns defined by list_display for your model. The method takes two parameters:

  • obj: the object being represented by the current row
  • field_name: the item from list_display being represented by the current column

For example, if you'd like to apply some conditional formatting to a cell depending on the row's value, you could do something like:

from decimal import Decimal
from django.db import models
from wagtail.contrib.modeladmin.options import ModelAdmin

class BankAccount(models.Model):
    name = models.CharField(max_length=50)
    account_number = models.CharField(max_length=50)
    balance = models.DecimalField(max_digits=5, num_places=2)


class BankAccountAdmin(ModelAdmin):
    list_display = ('name', 'account_number', 'balance')

    def get_extra_class_names_for_field_col(self, obj, field_name):
        if field_name == 'balance':
            if obj.balance <= Decimal('-100.00'):
                return ['brand-danger']
            elif obj.balance <= Decimal('-0.00'):
                return ['brand-warning']
            elif obj.balance <= Decimal('50.00'):
                return ['brand-info']
            else:
                return ['brand-success']
        return []

(modeladmin_get_extra_attrs_for_field_col)=

ModelAdmin.get_extra_attrs_for_field_col()

Must return: A dictionary

The get_extra_attrs_for_field_col method allows you to add additional HTML attributes to any of the columns defined in list_display. Like the get_extra_class_names_for_field_col method above, this method takes two parameters:

  • obj: the object being represented by the current row
  • field_name: the item from list_display being represented by the current column

For example, you might like to add some tooltip text to a certain column, to help give the value more context:

from django.db import models
from wagtail.contrib.modeladmin.options import ModelAdmin


class Person(models.Model):
    name = models.CharField(max_length=100)
    likes_cat_gifs = models.NullBooleanField()


class PersonAdmin(ModelAdmin):
    model = Person
    list_display = ('name', 'likes_cat_gifs')

    def get_extra_attrs_for_field_col(self, obj, field_name=None):
        attrs = super().get_extra_attrs_for_field_col(obj, field_name)
        if field_name == 'likes_cat_gifs' and obj.likes_cat_gifs is None:
            attrs.update({
                'title': (
                    'The person was shown several cat gifs, but failed to '
                    'indicate a preference.'
                ),
            })
        return attrs

Or you might like to add one or more data attributes to help implement some kind of interactivity using JavaScript:

from django.db import models
from wagtail.contrib.modeladmin.options import ModelAdmin


class Event(models.Model):
    title = models.CharField(max_length=255)
    start_date = models.DateField()
    end_date = models.DateField()
    start_time = models.TimeField()
    end_time = models.TimeField()


class EventAdmin(ModelAdmin):
    model = Event
    list_display = ('title', 'start_date', 'end_date')

    def get_extra_attrs_for_field_col(self, obj, field_name=None):
        attrs = super().get_extra_attrs_for_field_col(obj, field_name)
        if field_name == 'start_date':
            # Add the start time as data to the 'start_date' cell
            attrs.update({ 'data-time': obj.start_time.strftime('%H:%M') })
        elif field_name == 'end_date':
            # Add the end time as data to the 'end_date' cell
            attrs.update({ 'data-time': obj.end_time.strftime('%H:%M') })
        return attrs

(modeladmin_thumbnailmixin)=

wagtail.contrib.modeladmin.mixins.ThumbnailMixin

If you're using wagtailimages.Image to define an image for each item in your model, ThumbnailMixin can help you add thumbnail versions of that image to each row in IndexView. To use it, simply extend ThumbnailMixin as well as ModelAdmin when defining your ModelAdmin class, and change a few attributes to change the thumbnail to your liking, like so:

from django.db import models
from wagtail.contrib.modeladmin.mixins import ThumbnailMixin
from wagtail.contrib.modeladmin.options import ModelAdmin

class Person(models.Model):
    name = models.CharField(max_length=255)
    avatar = models.ForeignKey('wagtailimages.Image', on_delete=models.SET_NULL, null=True)
    likes_cat_gifs = models.NullBooleanField()

class PersonAdmin(ThumbnailMixin, ModelAdmin):

    # Add 'admin_thumb' to list_display, where you want the thumbnail to appear
    list_display = ('admin_thumb', 'name', 'likes_cat_gifs')

    # Optionally tell IndexView to add buttons to a different column (if the
    # first column contains the thumbnail, the buttons are likely better off
    # displayed elsewhere)
    list_display_add_buttons = 'name'

    """
    Set 'thumb_image_field_name' to the name of the ForeignKey field that
    links to 'wagtailimages.Image'
    """
    thumb_image_field_name = 'avatar'

    # Optionally override the filter spec used to create each thumb
    thumb_image_filter_spec = 'fill-100x100' # this is the default

    # Optionally override the 'width' attribute value added to each `<img>` tag
    thumb_image_width = 50 # this is the default

    # Optionally override the class name added to each `<img>` tag
    thumb_classname = 'admin-thumb' # this is the default

    # Optionally override the text that appears in the column header
    thumb_col_header_text = 'image' # this is the default

    # Optionally specify a fallback image to be used when the object doesn't
    # have an image set, or the image has been deleted. It can an image from
    # your static files folder, or an external URL.
    thumb_default = 'https://lorempixel.com/100/100'

(modeladmin_list_display_add_buttons)=

ModelAdmin.list_display_add_buttons

Expected value: A string matching one of the items in list_display.

If for any reason you'd like to change which column the action buttons appear in for each row, you can specify a different column using list_display_add_buttons on your ModelAdmin class. The value must match one of the items your class's list_display attribute. By default, buttons are added to the first column of each row.

See the ThumbnailMixin example above to see how list_display_add_buttons can be used.

(modeladmin_index_view_extra_css)=

ModelAdmin.index_view_extra_css

Expected value: A list of path names of additional stylesheets to be added to the IndexView

See the following part of the docs to find out more:

(modeladmin_index_view_extra_js)=

ModelAdmin.index_view_extra_js

Expected value: A list of path names of additional js files to be added to the IndexView

See the following part of the docs to find out more:

(modeladmin_index_template_name)=

ModelAdmin.index_template_name

Expected value: The path to a custom template to use for IndexView

See the following part of the docs to find out more:

(modeladmin_index_view_class)=

ModelAdmin.index_view_class

Expected value: A custom view class to replace modeladmin.views.IndexView

See the following part of the docs to find out more: