diff --git a/text/085-snippets-parity-modeladmin.md b/text/085-snippets-parity-modeladmin.md index c3d9611a..907d3b81 100644 --- a/text/085-snippets-parity-modeladmin.md +++ b/text/085-snippets-parity-modeladmin.md @@ -3,7 +3,7 @@ * RFC: 85 * Author: Sage Abdullah * Created: 2023-03-24 -* Last Modified: 2023-03-24 +* Last Modified: 2023-07-07 > The following RFC is based on [an existing GitHub Discussion][snippets-modeladmin-discussion]. > @@ -17,9 +17,7 @@ Currently, we still have two primary ways to edit Django models in the Wagtail a To achieve that goal, we need to figure out which ModelAdmin features we'd like to reimplement in Snippets, which features we'd like to leave out, and how we're going to proceed with the implementation and deprecation. This RFC serves as the main place to discuss those details. -## Specification - -### Current state of Snippets and ModelAdmin +## Basic ground for parity Up until Wagtail 4.0, registering a snippet is primarily done through the `register_snippet` decorator on the Django model. @@ -31,174 +29,149 @@ class Advert(models.Model): text = models.TextField() ``` -In Wagtail 4.1, we added the ability to customise the `ViewSet` class to use for a given snippet. Along with this, we introduced another way to register a snippet, by calling `register_snippet` as a function inside `wagtail_hooks.py`. +In Wagtail 4.1 and later improved in Wagtail 5.0, we added the ability to customise the `ViewSet` class to use for a given snippet. Along with this, we introduced another way to register a snippet, by calling `register_snippet` as a function inside `wagtail_hooks.py`. ```python class AdvertViewSet(SnippetViewSet): + model = Advert list_display = ("title", "url") -register_snippet(Advert, viewset=AdvertViewSet) +register_snippet(AdvertViewSet) ``` This method provides a better separation of concerns between the model and the admin views. It also aligns with the way we register Wagtail features and customisations, including ModelAdmin. As such, this method will be the recommended way to register a snippet. ---- - -Meanwhile, ModelAdmin is registered in a similar way, by defining a `ModelAdmin` subclass and calling `modeladmin_register` as a function inside `wagtail_hooks.py`. - -```python -class AdvertAdmin(ModelAdmin): - model = Advert - list_display = ('title', 'url') +The `SnippetViewSet` acts as the Snippets-equivalent for the `ModelAdmin` class, which will be the main point of comparison for the parity between the two in this document. +## ModelAdmin features to reimplement in Snippets -modeladmin_register(AdvertAdmin) -``` - -ModelAdmin's configuration is mostly placed in the `ModelAdmin` class. Its instance gets passed down to the views (available as `self.model_admin`), thus allowing access to the ModelAdmin config, e.g. the icons. The class works somewhat similarly to `ViewSet`s, but with more customisability for the views and Wagtail's menu. - -To better achieve parity between Snippets and ModelAdmin, we should start by making it possible to use the `SnippetViewSet` class as the main entry point for Snippets customisation. The [#10216][10216] PR in particular is a good example of moving the possible customisation points to the `SnippetViewSet` class. Once that is done, we can continue by defining similar attributes from the `ModelAdmin` class to the `SnippetViewSet` class. Unless there is a good reason not to, we should try to use the same names and defaults as ModelAdmin to make it easier for developers to migrate. - -The following subsections summarise the currently documented customisation points in ModelAdmin, whether we want to, and how we can achieve parity in Snippets. +The following subsections summarise the currently documented customisation points in ModelAdmin for features that we would like to reimplement or have already reimplemented in Snippets. Features that we would like to leave out or undecided will be listed later in this document. ### [Customising the base URL path](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/base_url.html) -Customisable through `ModelAdmin.base_url_path`, which resolves to `admin/{base_url_path}` and defaults to `admin/{app_label}/{model_name}`. - -To support this, we could: - -- Add a `base_url_path` property to `SnippetViewSet` that is initially set to `None`. -- Move the [`get_admin_base_path`][get_admin_base_path] method to `SnippetViewSet`, returning `base_url_path` if set, before falling back to `snippets/{app_label}/{model_name}` (the original default). - - Note that the Wagtail `admin/` prefix will be applied outside of this. +Support was added in [#10235][10235], which further expands the customisation by adding the following attributes to `SnippetViewSet`: -This was done in: - -- [Allow customising the base URL and URL namespace for snippet views through `SnippetViewSet` #10235][10235] - -Additionally, as noted in [this comment][stop_patching], we could stop patching the `get_admin_base_path` on the model class to reduce pollution. +- `base_url_path` +- `admin_url_namespace` +- `chooser_base_url_path` +- `chooser_admin_url_namespace` ### [Customising the menu item](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/menu_item.html) -This covers the configuration for the menu item that shows up in the sidebar menu. - -Snippets currently have no such configuration, as it only registers one "Snippets" top-level menu item. The only possible customisation is to hide the top-level menu item through the `construct_main_menu` hook. Adding a menu item for each snippet needs to be done separately using the `register_admin_menu_item` hook. +Support was added in [#10330][10330], which adds the following `ModelAdmin` attributes to `SnippetViewSet` with the same behaviour unless otherwise noted: -If we want to support this, there are a few questions to answer: +- [x] **`ModelAdmin.menu_item_name`** +- [x] **`ModelAdmin.menu_label`** +- [x] **`ModelAdmin.menu_order`** +- [x] **`ModelAdmin.menu_icon`** -- Whether to show or hide the "Snippets" menu item if all snippets have their own menu items. -- Whether to show or hide such snippet models on the snippet models list. - - It's likely that we still want to show it, but we might want to add an option to hide it. For example, `SnippetViewSet` could have a `show_in_snippets_menu` attribute that defaults to `True`, and we will hide the "Snippets" menu item if all snippets have `show_in_snippets_menu` set to `False`. Alternatively, the attribute can default to the inverse of `add_to_admin_menu`. -- Whether to have a "group" class like `ModelAdminGroup` to group multiple snippets under a single menu item. - - The name `SnippetViewSetGroup` may not be suitable as the sole purpose of the class is to group snippets under a single menu item. In addition, the snippets registration mechanism may need to be reworked, as currently the model is passed to `register_snippet` instead of set as an attribute on the `SnippetViewSet`. This raises the question as to how the group class should be registered. + Supported via `SnippetViewSet.icon`. The attribute name is `icon` instead of `menu_icon` as it is used throughout the admin views, not just the menu item. A separate menu icon can be defined by overriding `SnippetViewSet.get_menu_icon`. -ModelAdmin uses the following attributes to customise the menu item: +- [x] **`ModelAdmin.add_to_settings_menu`** +- [x] **`ModelAdmin.add_to_admin_menu`** -- [ ] **`ModelAdmin.menu_label`** + Unlike ModelAdmin, this defaults to `False` for Snippets, as traditionally we have the top-level "Snippets" menu item instead. If all snippets have `add_to_admin_menu` set to `True`, the "Snippets" menu item will be hidden. - The label for the menu item in the sidebar, defaults to `Model._meta.verbose_name_plural`. - -- [ ] **`ModelAdmin.menu_icon`** - - The icon in the sidebar menu and the header in the views unless customised further. Defaults to `doc-full-inverse` for pages and `snippet` for others. - - We have added support for custom icons in Snippets in [#10178][10178] using the `SnippetViewSet.icon` attribute. The icon is used throughout the admin views, so it makes sense to reuse it for the menu item. - - If desired, we could also add a `menu_icon` attribute to `SnippetViewSet` to allow customising the menu icon separately. - -- [ ] **`ModelAdmin.menu_order`** - - The order the item will appear in the sidebar, should be an integer between `1` and `999`. - -- [ ] **`ModelAdmin.add_to_settings_menu`** - - Whether the menu item will be registered inside the "Settings" menu item instead of the top level. Defaults to `False`. +### [Customising `IndexView` - the listing view](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/indexview.html) -- [ ] **`ModelAdmin.add_to_admin_menu`** +- [x] **`ModelAdmin.list_display`** - Whether the menu item will be registered. Defaults to `True`. + - [x] **`{Model,ModelAdmin}.attribute.admin_order_field`** + - [x] **`{Model,ModelAdmin}.attribute.short_description`** -- [ ] **`ModelAdmin.menu_item_name`** + This is partially implemented in Snippets in [#9147][9147] through `SnippetViewSet.list_display` and `IndexView.list_display`. Unlike ModelAdmin that looks for the attributes on both the model and the `ModelAdmin` class, we only look for attributes/methods on the model and not the `SnippetViewSet`. - String to pass on as the `name` parameter when instantiating the `MenuItem`. + In `SnippetViewSet`, this attribute also supports instances of the `Column` class, which provides better control of the column's behaviour, including the corresponding field used for sorting. -### [Customising `IndexView` - the listing view](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/indexview.html) +- [x] **`ModelAdmin.list_filter`** -- [x] **`ModelAdmin.list_display`** + A similar support has been implemented in [#10256][10256], but it uses django-filter under the hood, similar to our `ReportView` implementation. - The fields/methods/properties to be shown on the index view. They can exist on the model or on the `ModelAdmin` class itself. + Wagtail's ModelAdmin uses Django's admin filters, and changes in Django may break our ModelAdmin filters, as shown by [#10209][10209] and [#10242][10242]. Thus, we have a good reason to move forward with django-filter. - This is partially supported in Snippets through `SnippetViewSet.list_display` and `IndexView.list_display`, but we only look for attributes/methods on the model and not the `SnippetViewSet`. + In addition, `SnippetViewSet` also allows customising the filters through `SnippetViewSet.filterset_class` for more advanced use cases. - If desired, we could also look on the `SnippetViewSet`, but this would require further overrides on Snippets' `IndexView`. +- [x] **`ModelAdmin.list_export`** - - [ ] **`ModelAdmin.empty_value_display`** + Implemented in [#10626][10626]. - String to display in place of empty values (e.g. `None`, `""`, `[]`). Defaults to `"-"`. + - [x] **`ModelAdmin.export_filename`** - - [ ] **`ModelAdmin.get_empty_value_display()`** +- [x] **`ModelAdmin.search_fields`** - As with `empty_value_display`, but can return different strings based on the `field_name`. + Implemented in [#10290][10290]. - - [x] **`{Model,ModelAdmin}.attribute.admin_order_field`** +- [x] **`ModelAdmin.ordering`** - The database field to use for ordering the displayed attribute. + Implemented in [#10276][10276]. - - [x] **`{Model,ModelAdmin}.attribute.short_description`** +- [x] **`ModelAdmin.list_per_page`** - The label to use for the column of the displayed attribute. + Implemented in [#10241][10241]. -- [ ] **`ModelAdmin.list_export`** +- [x] **`ModelAdmin.get_queryset(request)`** - Same as `list_display`, but for exporting to spreadsheets. + Implemented in [#10275][10275]. -- [ ] **`ModelAdmin.list_filter`** +- [x] **`ModelAdmin.index_template_name`** - The fields of certain types to be used for filters. + Implemented in [#10271][10271] along with the other views. - A similar support has been implemented in [#10256][10256], but it uses django-filter under the hood, similar to our `ReportView` implementation. +- [x] **`ModelAdmin.index_view_class`** - Wagtail's ModelAdmin uses Django's admin filters, and changes in Django may break our ModelAdmin filters, as shown by [#10209][10209] and [#10242][10242]. Thus, we have a good reason to move forward with django-filter. + Implemented in [#8422][8422] along with the other views. - In addition, `SnippetViewSet` also allows customising the filters through `SnippetViewSet.filterset_class` for more advanced use cases. +### [Customising `CreateView`, `EditView` and `DeleteView`](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/create_edit_delete_views.html) -- [ ] **`ModelAdmin.export_filename`** +- [x] **Changing which fields appear in `CreateView` & `EditView`** - The name of the exported spreadsheet. + Implemented in [#10299][10299]. -- [ ] **`ModelAdmin.search_fields`** + - [x] **`ModelAdmin.get_edit_handler`** + - [x] **`ModelAdmin.form_fields_exclude`** - The fields of certain types to be used for searching. Searching is handled via Django's QuerySet API by default, but can be changed to use Wagtail's search backend. + Implemented as `SnippetViewSet.exclude_form_fields` to align with the existing attribute in `ModelViewSet`. - If Wagtail's search backend is used, this list must be a subset of the fields indexed by `index.SearchField`, or set to `None` (or left out) to allow matching on any indexed field. +- [x] **`ModelAdmin.create_template_name`** +- [x] **`ModelAdmin.create_view_class`** +- [x] **`ModelAdmin.edit_template_name`** +- [x] **`ModelAdmin.edit_view_class`** +- [x] **`ModelAdmin.delete_template_name`** +- [x] **`ModelAdmin.delete_view_class`** - Snippets search is currently only handled by Wagtail's search backend. Using Django's QuerySet API for searching can be implemented using the `list_filter` attribute, since that is how django-filter works under the hood. As such, adding multiple search handlers to Snippets might be a more complex task than it is worth, as it would require a lot of changes to how the queryset is handled in the `IndexView`. +### [Enabling & customising `InspectView`](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/inspectview.html) -- [ ] **`ModelAdmin.search_handler_class`** +Implemented in [#10621][10621]. - The class that handles search, subclass of `wagtail.contrib.modeladmin.helpers.search.BaseSearchHandler`. +- [x] **`ModelAdmin.inspect_view_fields`** +- [x] **`ModelAdmin.inspect_view_fields_exclude`** +- [x] **`ModelAdmin.inspect_template_name`** +- [x] **`ModelAdmin.inspect_view_class`** -- [ ] **`ModelAdmin.extra_search_kwargs`** +### Template overrides - Keyword arguments to be passed to `search_handler_class.search()`, e.g. `{"operator": OR}`. +Implemented in [#10271][10271], which allows templates to be customised by creating the templates in the following directories. -- [ ] **`ModelAdmin.ordering`** +1. `templates/wagtailsnippets/snippets/{app_label}/{model_name}/` +2. `templates/wagtailsnippets/snippets/{app_label}/` +3. `templates/wagtailsnippets/snippets/` - List of fields to specify the default ordering of objects. If not specified, the model's default ordering is used. +In addition, the `wagtailsnippets/snippets/` prefix can be customised by overriding the `SnippetViewSet.template_prefix` attribute. Specifying `{foo}_template_name` attribute for certain views that are supported in ModelAdmin is also supported in Snippets. - - [ ] **`ModelAdmin.get_ordering(request)`** +## ModelAdmin features we intentionally leave out - As with `ordering`, but can be customised per request. +### Using ModelAdmin to manage Page models -- [x] **`ModelAdmin.list_per_page`** +Relevant docs: [**Customising `ChooseParentView`**](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/chooseparentview.html). - The number of items to be shown per page. +ModelAdmin allows the registration of Page models, but only the index view is supported, as there are page-specific operations in the other views that are best handled by Wagtail's page views. For this reason, registering a Page model as a snippet is not a use case we want to support. Instead, we will introduce a new "treeless" listing view for pages, as outlined in [RFC 082][rfc-082] and the [Universal Listings discussion][universal-listings]. - Done in [Allow customising the number of items per page through `SnippetViewSet` #10241][10241]. +### Customisation of index view table rows and columns -- [ ] **`ModelAdmin.get_queryset(request)`** +ModelAdmin has a number of APIs that allow customisation of the index view table rows and columns. Meanwhile, we use Wagtail's generic tables UI framework for Snippets, which has its own set of APIs. We are gradually moving towards using the generic tables UI framework throughout the admin, so we should not introduce new APIs that diverge from that goal. - The base queryset to be used on the listing. Currently unsupported in snippets, but the `IndexView.get_queryset()` method can be overridden through a custom `SnippetViewSet`. +The following APIs will not be reimplemented in Snippets. The same effect can be achieved by using the generic tables UI framework, generally by implementing a `wagtail.ui.tables.Column` subclass and using it in `list_display`. Any further customisations can be done by overriding the index view template and/or class. - [ ] **`ModelAdmin.get_extra_attrs_for_row(obj, context)`** @@ -208,12 +181,14 @@ ModelAdmin uses the following attributes to customise the menu item: Returns a list of CSS classes for the column of the field for the given object (row). +- [ ] **`ModelAdmin.list_display_add_buttons`** + + The column where the buttons will be shown. Currently unsupported in snippets, but can be achieved similarly by making use of the `SnippetTitleColumn` class and overriding `IndexView.get_columns()` if necessary. + - [ ] **`wagtail.contrib.modeladmin.mixins.ThumbnailMixin`** Allows showing a thumbnail image by specifying `"admin_thumb"` in `list_display`. - **Note:** If we want to support this, we can probably create a `Column` subclass that can be put into `list_display` to make this easier. - - [ ] **`ModelAdmin.thumb_image_field_name`** The `ForeignKey` field to `wagtailimages.Image` @@ -234,188 +209,107 @@ ModelAdmin uses the following attributes to customise the menu item: Fallback image if missing, can be from the static files or an external URL. -- [ ] **`ModelAdmin.list_display_add_buttons`** +### Custom CSS and JS - The column where the buttons will be shown. Currently unsupported in snippets, but can be achieved similarly by making use of the `SnippetTitleColumn` class and overriding `IndexView.get_columns()` if necessary. +ModelAdmin supports inserting custom extra CSS and JS files into the admin via dedicated attributes on the `ModelAdmin` class. This is not a pattern we want to encourage, as it makes it difficult to maintain a consistent UI across the admin. For JavaScript customisations, we are moving towards using Stimulus controllers as covered in [RFC 078][rfc-078]. Even so, adding custom CSS and JS can still be achieved by overriding the relevant templates and/or views. - [ ] **`ModelAdmin.index_view_extra_css`** - - A list of CSS files to be added to the index view. - - [ ] **`ModelAdmin.index_view_extra_js`** - - A list of JS files to be added to the index view. - -- [ ] **`ModelAdmin.index_template_name`** - - The template name to be used for the index view. - -- [x] **`ModelAdmin.index_view_class`** - - The index view class to use for the listing. - -### [Customising `CreateView`, `EditView` and `DeleteView`](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/create_edit_delete_views.html) - -- [ ] **Changing which fields appear in `CreateView` & `EditView`** - - Editable fields can be configured through the `panels` or `edit_handler` attributes. - - This is supported in Snippets if the attribute is specified on the model. However, ModelAdmin also allows specifying this on the `ModelAdmin` class, which can be useful for external models. - - [ ] **`ModelAdmin.form_view_extra_css`** - - A list of CSS files to be added to the create and edit views. - - [ ] **`ModelAdmin.form_view_extra_js`** - - A list of JS files to be added to the create and edit views. - -- [ ] **`ModelAdmin.create_template_name`** - - The template name to be used for the create view. - -- [x] **`ModelAdmin.create_view_class`** - - The create view class to use. - -- [ ] **`ModelAdmin.edit_template_name`** - - The template name to be used for the edit view. - -- [x] **`ModelAdmin.edit_view_class`** - - The edit view class to use. - -- [ ] **`ModelAdmin.delete_template_name`** - - The template name to be used for the delete view. - -- [x] **`ModelAdmin.delete_view_class`** - - The delete view class to use. - -- [ ] **`ModelAdmin.form_fields_exclude`** - - A list of field names to be excluded from the create and edit views, useful for external models. - -- [ ] **`ModelAdmin.prepopulated_fields`** - - A dict mapping prepopulated fields to a tuple of fields to prepopulate from, useful for slug generation on Page models. - -- [ ] **`ModelAdmin.get_edit_handler`** - - Returns the appropriate `edit_handler` for the modeladmin class. Currently unsupported in Snippets, but the `{Create,Edit}View.get_panel()` can be overridden through a custom `SnippetViewSet`. - -### [Enabling & customising `InspectView`](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/inspectview.html) - -`InspectView` is a view that enables users to view more detailed information about an instance without the option to edit it. The view can be enabled by setting `ModelAdmin.inspect_view_enabled = True`. - -We currently don't have an equivalent of `InspectView` in Snippets. If desired, we can make a generic `InspectView` and include it as part of the `SnippetViewSet`. While we're at it, we can also add a `InspectView` for pages as well. - -- [ ] **`ModelAdmin.inspect_view_fields`** - - A list of field names to be shown on the inspect view. - -- [ ] **`ModelAdmin.inspect_view_fields_exclude`** - - A list of field names to be excluded from the inspect view. If `inspect_view_fields` is defined, `inspect_view_fields_exclude` is ignored. - - [ ] **`ModelAdmin.inspect_view_extra_css`** +- [ ] **`ModelAdmin.inspect_view_extra_js`** - A list of CSS files to be added to the inspect view. +### Helper classes -- [ ] **`ModelAdmin.inspect_view_extra_js`** +Helper classes encapsulate the logic that is commonly used across views in ModelAdmin. Instead of splitting the logic into ad hoc helper classes, centralising it in the `SnippetViewSet` and delegating specific mechanisms to existing solutions within Wagtail/Django would be a better approach in terms of maintainability and developer experience. Thus, we will not be implementing helper classes in Snippets. - A list of JS files to be added to the inspect view. +There are three types of helper classes in ModelAdmin: -- [ ] **`ModelAdmin.inspect_template_name`** +- [ ] **`ModelAdmin.url_helper_class`** - The template name to be used for the inspect view. + Helps with the consistent generation, naming and referencing of URLs. -- [ ] **`ModelAdmin.inspect_view_class`** 👀 + We already have the `get_urlpatterns()` and `get_url_name()` methods on the `SnippetViewSet` that come from Wagtail's base `ViewSet` class to manage URLs of Snippets views. The URL names can then be used with Django's `reverse()` function to generate URLs. - The inspect view class to use. +- [ ] **`ModelAdmin.permission_helper_class`** -## Other mechanisms + Helps with ensuring only users with sufficient permissions can perform certain actions, or see options to perform those actions. -### Template overrides + With Snippets, we use an instance of the `ModelPermissionPolicy` class exposed as `self.permission_policy` on the `SnippetViewSet`. Subclasses can override this attribute to use a custom permission policy. Permission policies are the standard mechanism for managing permissions in Wagtail. -By default, ModelAdmin looks in these directories for template discovery: +- [ ] **`ModelAdmin.button_helper_class`** -1. `templates/modeladmin/app-name/model-name/` -2. `templates/modeladmin/app-name/` -3. `templates/modeladmin/` + With the help of the other two helper classes, this class helps with the generation of buttons for use in a number of places. This class mainly helps with the creation of buttons on the index view, but it can be used in other views as needed. Overriding each of the default buttons can be done by overriding the `foo_button()` method on the helper class. To add custom buttons, the undocumented `get_buttons_for_obj` method must be overridden. -Which allows template overrides without specifying `foo_template_name` in the `ModelAdmin` class. + For Snippets, we have the existing [`register_snippet_listing_buttons`][register_snippet_listing_buttons] and [`construct_snippet_listing_buttons`][construct_snippet_listing_buttons] hooks. The `register_snippet_listing_buttons` hook makes it considerably easier to add custom buttons compared to ModelAdmin. -This is currently unsupported in Snippets. Adding the support in Snippets seems feasible, and it would be useful for customising the templates without having to specify the template names in the `SnippetViewSet`. + However, overriding the default ones is slightly harder as you'll need to scan the list of buttons in a `construct_snippet_listing_buttons` hook and replace it with a new `SnippetListingButton` instance. Due to the nature of hooks, these customisations are done directly in `wagtail_hooks.py`, separate from the `SnippetViewSet`. -### Helper classes + The hooks may be enough for most use cases. That said, they have been around since before `SnippetViewSet` was introduced, so we may also consider replacing them with a more cohesive approach within the `SnippetViewSet` in the future. -Helper classes encapsulate the logic that is commonly used across views in ModelAdmin. There are three types of helper classes: +- [ ] **`ModelAdmin.search_handler_class`** -- **`ModelAdmin.url_helper_class`** + The class that handles search, subclass of `wagtail.contrib.modeladmin.helpers.search.BaseSearchHandler`. Even though the attribute uses the term `handler`, the module location suggests that is part of the helper classes. - Helps with the consistent generation, naming and referencing of URLs. + The main purpose of this class is to allow switching between Wagtail's search backend and the Django ORM. Instead of providing this via a helper class, [#10290][10290] added support to specify the search backend via the `search_backend_name` attribute and will fall back to the Django ORM if the attribute is unset. -- **`ModelAdmin.permission_helper_class`** + - [ ] **`ModelAdmin.extra_search_kwargs`** - Helps with ensuring only users with sufficient permissions can perform certain actions, or see options to perform those actions. + This attribute is not part of the helper class but is related to it. This specifies the keyword arguments to be passed to `search_handler_class.search_queryset()`, e.g. `{"operator": OR}`. - With Snippets, we use an instance of the `ModelPermissionPolicy` class exposed as `self.permission_policy` on the `SnippetViewSet`, so we might not need this. + As we will not support helper classes, we will not support this attribute. Any further customisations to the search mechanism can be done by overriding the index view. -- **`ModelAdmin.button_helper_class`** +### Other attributes - With the help of the other two, helps with the generation of buttons for use in a number of places. This class mainly helps with the creation of buttons on the index view, but it can be used in other views as needed. Overriding each of the default buttons can be done by overriding the `foo_button()` method on the helper class. To add custom buttons, the undocumented `get_buttons_for_obj` method must be overridden. +- [ ] **`ModelAdmin.empty_value_display`** - For Snippets, we have the [`register_snippet_listing_buttons`][register_snippet_listing_buttons] and [`construct_snippet_listing_buttons`][construct_snippet_listing_buttons] hooks. The `register_snippet_listing_buttons` hook makes it considerably easier to add custom buttons compared to ModelAdmin. + String to display in place of empty values (e.g. `None`, `""`, `[]`). Defaults to `"-"`. - However, overriding the default ones is slightly harder as you'll need to scan the list of buttons in a `construct_snippet_listing_buttons` hook and replace it with a new `SnippetListingButton` instance. Due to the nature of the hooks, these customisations are done in `wagtail_hooks.py`, separate from the `SnippetViewSet`. + This is only used in the index and inspect views. For the index view, this can be delegated to the `Column` class of the tables UI framework. For inspect view, we will not support it, but the same effect can be achieved by implementing a `get_foo_display()` method on the model, which is an established pattern in Django. - The hooks may be enough for our use case, but we can also consider adding a way to customise the buttons through the `SnippetViewSet`. +- [ ] **`ModelAdmin.get_empty_value_display()`** -## Intentionally left out + As with `empty_value_display`, but can return different strings based on the `field_name`. -- [**Customising `ChooseParentView`**](https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/chooseparentview.html) 🗑️ + Will not be supported for the same reason as `empty_value_display`. - This concerns the use of ModelAdmin to manage Page models. This is not a use case that we will support (i.e. registering a Page model as a snippet), so we will leave this out. Instead, we will introduce a new "treeless" listing view for pages, as outlined in [RFC 082][rfc-082]. +- [ ] **`ModelAdmin.get_ordering(request)`** -## Mentions of ModelAdmin in the docs + Like `ModelAdmin.ordering`, but can be customised per request. -Right now, we have a few places where we mention ModelAdmin in the documentation. Most of them should be fine to leave as-is, but we likely will want to change some of them to make sure that Snippets will be the preferred way of editing Django models in the Wagtail admin. + The `ordering` attribute covers the default ordering of the index view. Instances of Wagtail's `Column` class that can be used in the `SnippetViewSet.list_display` attribute can specify the corresponding database field for ordering, which covers most use cases of per-request ordering. For more complex use cases, the index view can be overridden. -- `docs/advanced_topics/icons.md`: Links to ModelAdmin's menu icon configuration -- `docs/extending/admin_views.md`: First paragraph links to ModelAdmin docs as the "first-choice" for creating custom admin views -- `docs/extending/forms.md`: Normal mentions of how panels are used -- `docs/reference/contrib/index.md`: Normal mention as part of the list of contrib modules -- `docs/reference/pages/model_recipes.md`: Normal mention as part of `ClusterableModel` requirement for tagging. However, there's a specific "Managing tags with Wagtail's `ModelAdmin`" section that implies it's the preferred way to manage Tags through the admin. -- `docs/reference/pages/panels.md`: Mention of custom placeholders for a model that's exposed via ModelAdmin -- `docs/advanced_topics/multi_site_multi_instance_multi_tenancy.md`: A bit elaborate mention: - > Snippets are global pieces of content so not suitable for multi-tenancy but any model that can be registered as a snippet can also be managed via the Wagtail model admin. You can add a site_id to the model and then use the model admin get_queryset method to determine which site can manage each object. The built-in snippet choosers can be replaced by [modelchooser](https://pypi.org/project/wagtail-modelchooser/) that allows filtering the queryset to restrict which sites may display which objects. + As explained in [a review comment][get-ordering-review], we will not support this method. -## General open questions +- [ ] **`ModelAdmin.prepopulated_fields`** -We can discuss each of the above customisation points of ModelAdmin in order to decide whether we want (and how) to support it in Snippets. + A dict mapping prepopulated fields to a tuple of fields to prepopulate from, useful for slug generation on Page models. -In addition to that, there are also some open questions regarding the bigger picture of `SnippetViewSet`: + This feature uses a custom JavaScript file that concatenates and slugifies the values of the specified fields into a target field. The `TitleFieldPanel` added in [#10568][10568] demonstrates a different approach to this feature that builds on top of Wagtail's Panels API and it can be used for Snippets. We can expand the new approach to support more generic use cases via Stimulus in the future. Thus, we will not support this attribute. -- **Shall we allow partial registration, i.e. CRUD without chooser and vice versa?** +### Deprecation timeline of ModelAdmin - There has been requests for the ability to register snippets without having the chooser registered, and vice versa, register the chooser but without the CRUD views. +Now that most features of ModelAdmin have been reimplemented in Snippets, and the remaining features will not be supported, we are aiming to start the deprecation process of ModelAdmin in the next feature release of Wagtail, i.e. Wagtail 5.1 (to be released in August 2023) at the time of writing. -- **Shall we consider changing the signature of `register_snippet`?** +The deprecation timeline will be as follows: - Currently, `register_snippet` takes a model class as its first argument, while the `viewset` is an optional second argument. With ModelAdmin, only the `ModelAdmin` class is passed to `modeladmin_register` function, and the model class is retrieved from the `model` attribute of the `ModelAdmin` class. +- Pre-release of Wagtail 5.1 (July 2023): Release the `modeladmin` contrib package as a separate package. This will be a copy of the `wagtail.contrib.modeladmin` app. + - Issues in the `wagtai/wagtail` repository that are related to ModelAdmin will be moved to the new repository. A list of existing PRs for ModelAdmin will be made on the new repository, with the original PRs closed and contributors will be asked to resubmit their PRs to the new repository. Security fixes to `wagtail.contrib.modeladmin` may still be accepted in the `wagtail/wagtail` repository as deemed necessary. +- Wagtail 5.1 (August 2023): Deprecate `wagtail.contrib.modeladmin`. + - A migration guide will be provided for users who want to migrate from ModelAdmin to Snippets or the external package. +- Wagtail 6.0 (TBD): Remove `wagtail.contrib.modeladmin`. + - Documentation of ModelAdmin within Wagtail's documentation will be redirected to the external package documentation. - We could consider doing the same for Snippets. However, to avoid this being a breaking change, we could still support a model class as a first argument by checking whether it's a model class or a `SnippetViewSet` class and do the registration accordingly. +Considering that ModelAdmin is heavily used by Wagtail users, the deprecation timeline may be longer than usual. In addition, as Wagtail future version numbers are provisional, the Wagtail 6.0 release may or may not be the release after Wagtail 5.1. Per our deprecation policy, we will ensure that there is at least two feature releases between the deprecation and removal of ModelAdmin. As a result, if Wagtail 6.0 is released after Wagtail 5.1, ModelAdmin will be removed in Wagtail 7.0. - Changing the signature of `register_snippet` would also allow us to accept a group class as described in [Customising the menu item](#customising-the-menu-item). +## Possible future improvements -- **What is the deprecation timeline of ModelAdmin?** +There are some open questions regarding the bigger picture of Snippets and `SnippetViewSet` for the future. We do not need to answer them in this RFC, but these are some things to consider. - We should consider how long we want to keep ModelAdmin as a contrib module before detaching it and publishing it as an external package. Before the app is detached, we should also provide a migration path for users who want to migrate from ModelAdmin to Snippets. Once the app is detached, we could leave a stub page in the ModelAdmin documentation that links to the external package. +- **Shall we allow partial registration, i.e. CRUD without chooser and vice versa?** -- Add more questions here. + There have been requests for the ability to register snippets without having the chooser registered, and vice versa, register the chooser but without the CRUD views. There have also been requests to turn off specific views, e.g. the index, create, and delete view, which can be useful to enforce a singleton snippet. ## See also @@ -429,6 +323,8 @@ In addition to that, there are also some open questions regarding the bigger pic [snippets]: https://docs.wagtail.org/en/v4.2.1/topics/snippets.html [modeladmin]: https://docs.wagtail.org/en/v4.2.1/reference/contrib/modeladmin/index.html [4095]: https://github.com/wagtail/wagtail/issues/4095 +[8422]: https://github.com/wagtail/wagtail/pull/8422 +[9147]: https://github.com/wagtail/wagtail/pull/9147 [10178]: https://github.com/wagtail/wagtail/pull/10178 [10209]: https://github.com/wagtail/wagtail/pull/10209 [10216]: https://github.com/wagtail/wagtail/pull/10216 @@ -436,9 +332,19 @@ In addition to that, there are also some open questions regarding the bigger pic [10241]: https://github.com/wagtail/wagtail/pull/10241 [10242]: https://github.com/wagtail/wagtail/pull/10242 [10256]: https://github.com/wagtail/wagtail/pull/10256 +[10271]: https://github.com/wagtail/wagtail/pull/10271 +[10275]: https://github.com/wagtail/wagtail/pull/10275 +[10276]: https://github.com/wagtail/wagtail/pull/10276 +[10290]: https://github.com/wagtail/wagtail/pull/10290 +[10299]: https://github.com/wagtail/wagtail/pull/10299 +[10330]: https://github.com/wagtail/wagtail/pull/10330 +[10621]: https://github.com/wagtail/wagtail/pull/10621 +[10626]: https://github.com/wagtail/wagtail/pull/10626 [comment-on-supercharging-snippets]: https://github.com/wagtail/wagtail/discussions/8609#discussioncomment-5006831 [get_admin_base_path]: https://github.com/wagtail/wagtail/blob/04cca97f09eb724b686e02ccf8348e6d3afcb531/wagtail/snippets/models.py#L145-L147 -[stop_patching]: https://github.com/wagtail/wagtail/pull/10235#discussion_r1145184334 [register_snippet_listing_buttons]: https://docs.wagtail.org/en/stable/reference/hooks.html#register-snippet-listing-buttons [construct_snippet_listing_buttons]: https://docs.wagtail.org/en/stable/reference/hooks.html#construct-snippet-listing-buttons +[rfc-078]: https://github.com/wagtail/rfcs/pull/78 [rfc-082]: https://github.com/wagtail/rfcs/pull/82 +[get-ordering-review]: https://github.com/wagtail/wagtail/pull/10276#pullrequestreview-1385503020 +[universal-listings]: https://github.com/wagtail/wagtail/discussions/10446