mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-29 09:33:54 +01:00
Allow customising snippet listing columns with list_display
This commit is contained in:
parent
5e2a827afa
commit
0a0bd30fad
@ -77,6 +77,7 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit
|
||||
```{eval-rst}
|
||||
.. autoclass:: wagtail.snippets.views.snippets.SnippetViewSet
|
||||
|
||||
.. autoattribute:: list_display
|
||||
.. autoattribute:: filterset_class
|
||||
.. autoattribute:: index_view_class
|
||||
.. autoattribute:: add_view_class
|
||||
|
@ -323,7 +323,7 @@ You can also save revisions programmatically by calling the {meth}`~wagtail.mode
|
||||
The `DraftStateMixin` class was introduced.
|
||||
```
|
||||
|
||||
If a snippet model inherits from {class}`~wagtail.models.DraftStateMixin`, Wagtail will automatically change the "Save" action menu in the snippets admin to "Save draft" and add a new "Publish" action menu. Any changes you save in the snippets admin will be saved as revisions and will not be reflected to the "live" snippet instance until you publish the changes. For example, the `Advert` snippet could save draft changes by defining it as follows:
|
||||
If a snippet model inherits from {class}`~wagtail.models.DraftStateMixin`, Wagtail will automatically add a live/draft status column to the listing view, change the "Save" action menu to "Save draft", and add a new "Publish" action menu in the editor. Any changes you save in the snippets admin will be saved as revisions and will not be reflected to the "live" snippet instance until you publish the changes. For example, the `Advert` snippet could save draft changes by defining it as follows:
|
||||
|
||||
```python
|
||||
# ...
|
||||
@ -400,7 +400,7 @@ This can be done by removing the `@register_snippet` decorator on your model cla
|
||||
register_snippet(MyModel, viewset=MyModelViewSet)
|
||||
```
|
||||
|
||||
For example, with the following `Member` model:
|
||||
For example, with the following `Member` model and a `MemberFilterSet` class:
|
||||
|
||||
```python
|
||||
# models.py
|
||||
@ -418,6 +418,12 @@ class Member(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
shirt_size = models.CharField(max_length=5, choices=ShirtSize.choices, default=ShirtSize.MEDIUM)
|
||||
|
||||
def get_shirt_size_display(self):
|
||||
return self.ShirtSize(self.shirt_size).label
|
||||
|
||||
get_shirt_size_display.admin_order_field = "shirt_size"
|
||||
get_shirt_size_display.short_description = "Size description"
|
||||
|
||||
|
||||
class MemberFilterSet(WagtailFilterSet):
|
||||
class Meta:
|
||||
@ -425,16 +431,18 @@ class MemberFilterSet(WagtailFilterSet):
|
||||
fields = ["shirt_size"]
|
||||
```
|
||||
|
||||
You can add a `filterset_class` to the listing view by defining a subclass of `SnippetViewSet` as below:
|
||||
You can define a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.list_display` attribute to specify the columns shown on the listing view. You can also add the ability to filter the listing view by defining a {attr}`~wagtail.snippets.views.snippets.SnippetViewSet.filterset_class` attribute on a subclass of `SnippetViewSet`. For example:
|
||||
|
||||
```python
|
||||
# views.py
|
||||
from wagtail.admin.ui.tables import UpdatedAtColumn
|
||||
from wagtail.snippets.views.snippets import SnippetViewSet
|
||||
|
||||
from myapp.models import MemberFilterSet
|
||||
|
||||
|
||||
class MemberViewSet(SnippetViewSet):
|
||||
list_display = ["name", "shirt_size", "get_shirt_size_display", UpdatedAtColumn()]
|
||||
filterset_class = MemberFilterSet
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.contrib.admin.utils import quote, unquote
|
||||
from django.contrib.admin.utils import label_for_field, quote, unquote
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models, transaction
|
||||
@ -22,7 +22,7 @@ from wagtail.admin import messages
|
||||
from wagtail.admin.forms.search import SearchForm
|
||||
from wagtail.admin.panels import get_edit_handler
|
||||
from wagtail.admin.templatetags.wagtailadmin_tags import user_display_name
|
||||
from wagtail.admin.ui.tables import DateColumn, StatusTagColumn, Table, TitleColumn
|
||||
from wagtail.admin.ui.tables import Column, Table, TitleColumn, UpdatedAtColumn
|
||||
from wagtail.log_actions import log
|
||||
from wagtail.log_actions import registry as log_registry
|
||||
from wagtail.models import DraftStateMixin, RevisionMixin
|
||||
@ -86,6 +86,7 @@ class IndexView(
|
||||
filters = None
|
||||
filterset_class = None
|
||||
table_class = Table
|
||||
list_display = ["__str__", UpdatedAtColumn()]
|
||||
|
||||
def setup(self, request, *args, **kwargs):
|
||||
super().setup(request, *args, **kwargs)
|
||||
@ -241,48 +242,49 @@ class IndexView(
|
||||
}
|
||||
return queryset.filter(**filters)
|
||||
|
||||
def _get_title_column(self, column_class=TitleColumn):
|
||||
def title_accessor(obj):
|
||||
draftstate_enabled = self.model and issubclass(self.model, DraftStateMixin)
|
||||
|
||||
if draftstate_enabled and obj.latest_revision:
|
||||
return obj.latest_revision.object_str
|
||||
return str(obj)
|
||||
|
||||
return column_class(
|
||||
"name",
|
||||
label=gettext_lazy("Name"),
|
||||
accessor=title_accessor,
|
||||
get_url=self.get_edit_url,
|
||||
def _get_title_column(self, field_name, column_class=TitleColumn, **kwargs):
|
||||
if not self.model:
|
||||
return column_class(
|
||||
"name",
|
||||
label=gettext_lazy("Name"),
|
||||
accessor=str,
|
||||
get_url=self.get_edit_url,
|
||||
)
|
||||
return self._get_custom_column(
|
||||
field_name, column_class, get_url=self.get_edit_url, **kwargs
|
||||
)
|
||||
|
||||
def _get_updated_at_column(self, column_class=DateColumn):
|
||||
return column_class("_updated_at", label=_("Updated"), sort_key="_updated_at")
|
||||
def _get_custom_column(self, field_name, column_class=Column, **kwargs):
|
||||
label, attr = label_for_field(field_name, self.model, return_attr=True)
|
||||
sort_key = getattr(attr, "admin_order_field", None)
|
||||
|
||||
# attr is None if the field is an actual database field,
|
||||
# so it's possible to sort by it
|
||||
if attr is None:
|
||||
sort_key = field_name
|
||||
|
||||
def _get_status_tag_column(self, column_class=StatusTagColumn):
|
||||
return column_class(
|
||||
"status_string",
|
||||
label=_("Status"),
|
||||
sort_key="live",
|
||||
primary=lambda instance: instance.live,
|
||||
field_name,
|
||||
label=label.title(),
|
||||
sort_key=sort_key,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _get_default_columns(self):
|
||||
columns = [
|
||||
self._get_title_column(),
|
||||
self._get_updated_at_column(),
|
||||
]
|
||||
|
||||
draftstate_enabled = self.model and issubclass(self.model, DraftStateMixin)
|
||||
if draftstate_enabled:
|
||||
columns.append(self._get_status_tag_column())
|
||||
return columns
|
||||
|
||||
def get_columns(self):
|
||||
try:
|
||||
return self.columns
|
||||
except AttributeError:
|
||||
return self._get_default_columns()
|
||||
columns = []
|
||||
for i, field in enumerate(self.list_display):
|
||||
if isinstance(field, Column):
|
||||
column = field
|
||||
elif i == 0:
|
||||
column = self._get_title_column(field)
|
||||
else:
|
||||
column = self._get_custom_column(field)
|
||||
columns.append(column)
|
||||
|
||||
return columns
|
||||
|
||||
def get_index_url(self):
|
||||
if self.index_url_name:
|
||||
|
@ -23,6 +23,7 @@ from wagtail.admin.ui.tables import (
|
||||
Column,
|
||||
DateColumn,
|
||||
InlineActionsTable,
|
||||
LiveStatusTagColumn,
|
||||
TitleColumn,
|
||||
UserColumn,
|
||||
)
|
||||
@ -158,8 +159,19 @@ class IndexView(generic.IndexView):
|
||||
results_only = False
|
||||
table_class = InlineActionsTable
|
||||
|
||||
def _get_title_column(self, column_class=SnippetTitleColumn):
|
||||
return super()._get_title_column(column_class)
|
||||
def _get_title_column(self, field_name, column_class=SnippetTitleColumn, **kwargs):
|
||||
accessor = kwargs.pop("accessor", None)
|
||||
|
||||
if not accessor and field_name == "__str__":
|
||||
|
||||
def accessor(obj):
|
||||
if isinstance(obj, DraftStateMixin) and obj.latest_revision:
|
||||
return obj.latest_revision.object_str
|
||||
return str(obj)
|
||||
|
||||
return super()._get_title_column(
|
||||
field_name, column_class, accessor=accessor, **kwargs
|
||||
)
|
||||
|
||||
def get_columns(self):
|
||||
return [
|
||||
@ -680,6 +692,21 @@ class SnippetViewSet(ViewSet):
|
||||
#: A subclass of ``wagtail.admin.filters.WagtailFilterSet``, which is a subclass of `django_filters.FilterSet <https://django-filter.readthedocs.io/en/stable/ref/filterset.html>`_. This will be passed to the ``filterset_class`` attribute of the index view.
|
||||
filterset_class = None
|
||||
|
||||
#: A list or tuple, where each item is either:
|
||||
#:
|
||||
#: - The name of a field on the model;
|
||||
#: - The name of a callable or property on the model that accepts a single parameter for the model instance; or
|
||||
#: - An instance of the ``wagtail.admin.ui.tables.Column`` class.
|
||||
#:
|
||||
#: If the name refers to a database field, the ability to sort the listing by the database column will be offerred and the field's verbose name will be used as the column header.
|
||||
#:
|
||||
#: If the name refers to a callable or property, a ``admin_order_field`` attribute can be defined on it to point to the database column for sorting.
|
||||
#: A ``short_description`` attribute can also be defined on the callable or property to be used as the column header.
|
||||
#:
|
||||
#: This list will be passed to the ``list_display`` attribute of the index view.
|
||||
#: If left unset, the ``list_display`` attribute of the index view will be used instead, which by default is defined as ``["__str__", wagtail.admin.ui.tables.UpdatedAtColumn()]``.
|
||||
list_display = None
|
||||
|
||||
#: The view class to use for the index view; must be a subclass of ``wagtail.snippet.views.snippets.IndexView``.
|
||||
index_view_class = IndexView
|
||||
|
||||
@ -713,6 +740,17 @@ class SnippetViewSet(ViewSet):
|
||||
#: The view class to use for previewing on the edit view; must be a subclass of ``wagtail.snippet.views.snippets.PreviewOnEditView``.
|
||||
preview_on_edit_view_class = PreviewOnEditView
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
self.preview_enabled = issubclass(self.model, PreviewableMixin)
|
||||
self.revision_enabled = issubclass(self.model, RevisionMixin)
|
||||
self.draftstate_enabled = issubclass(self.model, DraftStateMixin)
|
||||
|
||||
if not self.list_display:
|
||||
self.list_display = self.index_view_class.list_display.copy()
|
||||
if self.draftstate_enabled:
|
||||
self.list_display += [LiveStatusTagColumn()]
|
||||
|
||||
@property
|
||||
def revisions_revert_view_class(self):
|
||||
"""
|
||||
@ -745,6 +783,7 @@ class SnippetViewSet(ViewSet):
|
||||
add_url_name=self.get_url_name("add"),
|
||||
edit_url_name=self.get_url_name("edit"),
|
||||
delete_multiple_url_name=self.get_url_name("delete-multiple"),
|
||||
list_display=self.list_display,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -759,6 +798,7 @@ class SnippetViewSet(ViewSet):
|
||||
add_url_name=self.get_url_name("add"),
|
||||
edit_url_name=self.get_url_name("edit"),
|
||||
delete_multiple_url_name=self.get_url_name("delete-multiple"),
|
||||
list_display=self.list_display,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -896,7 +936,7 @@ class SnippetViewSet(ViewSet):
|
||||
path("history/<str:pk>/", self.history_view, name="history"),
|
||||
]
|
||||
|
||||
if issubclass(self.model, PreviewableMixin):
|
||||
if self.preview_enabled:
|
||||
urlpatterns += [
|
||||
path("preview/", self.preview_on_add_view, name="preview_on_add"),
|
||||
path(
|
||||
@ -906,8 +946,8 @@ class SnippetViewSet(ViewSet):
|
||||
),
|
||||
]
|
||||
|
||||
if issubclass(self.model, RevisionMixin):
|
||||
if issubclass(self.model, PreviewableMixin):
|
||||
if self.revision_enabled:
|
||||
if self.preview_enabled:
|
||||
urlpatterns += [
|
||||
path(
|
||||
"history/<str:pk>/revisions/<int:revision_id>/view/",
|
||||
@ -929,7 +969,7 @@ class SnippetViewSet(ViewSet):
|
||||
),
|
||||
]
|
||||
|
||||
if issubclass(self.model, DraftStateMixin):
|
||||
if self.draftstate_enabled:
|
||||
urlpatterns += [
|
||||
path("unpublish/<str:pk>/", self.unpublish_view, name="unpublish"),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user