Snippets vs ModelAdmin parity #10206
Replies: 4 comments 2 replies
-
Good to have an overview! 👏 ModelAdmin is a Wagtail contrib module and quite stable for a long time. It is very likely that ModelAdmin once removed from Wagtail core, will be shipped as a third-party package. So, for anyone that is reading this issue and scared that ModelAdmin will be gone: don't panic. ModelAdmin will stay available. Snippets enables crud views and a chooser. But sometimes only one or the other is needed. It would be awesome if the new-style snippets support that. I really dislike this hack, but use it all the time to remove redundant menu items. ModelAdmin is used a lot to present a flat list of pages of some specific type. It gives content editors a one-stop menu item. It saves them the effort of navigating the page tree. Pages in model admin enable the choose parent view, exclude from navigation, jump to the create view, next url to go back. This features are not mentioned in this overview because but will be covered by wagtail/rfcs#82 Registering listing buttons can be done in ModelAdmin but is not mentioned in this overview. |
Beta Was this translation helpful? Give feedback.
-
This new discussion summarises things excellently, thanks @laymonage! On this one:
In the absence of hard and fast rules to determine how much complexity is too much for one component, I think it makes sense to fall back to the "simple is better than complex" rule. While I love viewsets as a concept, their limited (by design) scope has likely played a part in how they have come to be used so far. With additional options to control how custom models are represented, I feel there may be more sense in bringing things like Don't get me wrong - I love dedicated components (hence 'helper classes'), but with something so fundamental to the developer experience, it pays to keep the 'point of entry' simple. |
Beta Was this translation helpful? Give feedback.
-
Thanks also @laymonage for the detailed comparison. Adding my few cents worth:
I vote yes here. I have a couple of use cases where I want the |
Beta Was this translation helpful? Give feedback.
-
Hi all! 👋 I turned this discussion into an RFC at wagtail/rfcs#85 to make it easier to review each individual customisation point. The RFC has been updated with more details, as some of the customisation points described in this discussion have been implemented and merged into Wagtail. As such, let's continue the discussion on the RFC and I will close this discussion. |
Beta Was this translation helpful? Give feedback.
-
Note
This discussion is locked, continue in #10740 for further discussion.
This discussion is part of the long-term work discussed in
In the above discussion, we had planned to make Snippets the "definitive" way to edit Django models in the Wagtail admin. We began the work by progressively enhancing the capabilities of Snippets to become as powerful as pages. Since then, those features have been implemented in Wagtail.
Right now, we still have two primary ways to edit Django models in the Wagtail admin: Snippets and ModelAdmin. Based on previous discussions, the long-term goal is to make Snippets up to par with ModelAdmin, before we eventually "detach" ModelAdmin into a separate package.
Quoting @allcaps:
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 discussion serves as the main place to discuss those details.
I will be using these emojis to mark each feature based on my personal judgement:
ModelAdmin documented features
The following details are structured based on what we have publicly documented for ModelAdmin: https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/index.html
ModelAdmin's configuration is mostly placed in the
ModelAdmin
class. Its instance gets passed down to the views (available asself.model_admin
), thus allowing access to the ModelAdmin config, e.g. the icons. The class works somewhat similarly toViewSet
s, but with more customisability for the views and Wagtail's menu.To achieve a similar thing with Snippets, we could either make the
SnippetViewSet
act like theModelAdmin
class, or we introduce a new class e.g.SnippetConfig
that does a similar thing. Considering that Snippets have two separate viewsets, i.e.SnippetViewSet
andSnippetChooserViewSet
, we should probably introduce a new class. Otherwise, we can also makeSnippetChooserViewSet
a part ofSnippetViewSet
, and still useSnippetViewSet
as the main config class.In any case, we will refer to this config class as theSnippetConfig
for now.Update: as of #10216,
SnippetChooserViewSet
is part ofSnippetViewSet
, so we could go ahead and useSnippetViewSet
as the main entry point for customisation.Customising the base URL path 👀
Customisable through
ModelAdmin.base_url_path
which resolves toadmin/{base_url_path}
and defaults toadmin/{app_label}/{model_name}
.Snippets equivalent
wagtail/wagtail/snippets/models.py
Lines 145 to 147 in 04cca97
wagtail/wagtail/snippets/models.py
Line 101 in 04cca97
To support this, we could:
base_url_path
property toSnippetViewSet
that's initially set toNone
.get_admin_base_path
method toSnippetViewSet
, resolving tosnippets/{app_label}/{model_name}
ifbase_url_path
isNone
. (Note that the Wagtailadmin/
prefix will be applied after.)get_admin_base_path
to the model class to reduce pollution, though there might be other code or people that depend on this being available.Done in:
SnippetViewSet
#10235Customising the menu item 👀
This covers the configuration for the menu item that shows up in the sidebar. Snippets currently have no such configuration (it only registers one "Snippets" top-level menu item). If we want to add support for custom menu item registration, we can do it through the
SnippetViewSet
class.Configuration
ModelAdmin.menu_label
👀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 andsnippet
for others.Initial work for custom icons before custom menu item support is done in Allow customising icons for snippets via
SnippetViewSet.icon
#10178.ModelAdmin.menu_order
👀The order the item will appear in the sidebar, should be an integer between
1
and999
.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
.ModelAdmin.add_to_admin_menu
❓ 👀Whether the menu item will be registered. Defaults to
True
.ModelAdmin.menu_item_name
👀String to pass on as the
name
parameter when instantiating theMenuItem
.Customising
IndexView
- the listing view ❓ 👀 🛠️Configuration
ModelAdmin.list_display
✅The fields/methods/properties to be shown on the index view. They can exist on the model or on the
ModelAdmin
class itself.This is partially supported in snippets through
SnippetViewSet.list_display
andIndexView.list_display
, but we only look for the attributes at the model and not theSnippetViewSet
. If desired, we could also look for the attributes defined on theSnippetViewSet
(❓ 🛠️).ModelAdmin.empty_value_display
❓String to display in place of empty values (e.g.
None
,""
,[]
). Defaults to"-"
.ModelAdmin.get_empty_value_display()
❓As with
empty_value_display
, but can return different strings based on thefield_name
.{Model,ModelAdmin}.attribute.admin_order_field
✅The database field to use for ordering the displayed attribute.
{Model,ModelAdmin}.attribute.short_description
✅The label to use for the column of the displayed attribute.
ModelAdmin.list_export
👀Same as
list_display
, but for exporting to spreadsheets.ModelAdmin.list_filter
👀The fields of certain types to be used for filters. Currently unsupported in snippets, but a similar functionality can be achieved through
SnippetViewSet.filterset_class
.filterset_class
for snippets and genericIndexView
throughlist_filter
#10256, though it works somewhat differently to ModelAdmin's filtersModelAdmin.export_filename
👀The name of the exported spreadsheet.
ModelAdmin.search_fields
❓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.
If Wagtail's search backend is used, this list must be a subset of the fields indexed by
index.SearchField
, or set toNone
(or left out) to allow matching on any indexed field.ModelAdmin.search_handler_class
❓The class that handles search, subclass of
wagtail.contrib.modeladmin.helpers.search.BaseSearchHandler
.ModelAdmin.extra_search_kwargs
❓Keyword arguments to be passed to
search_handler_class.search()
, e.g.{"operator": OR}
.ModelAdmin.ordering
❓List of fields to specify the default ordering of objects. If not specified, the model's default ordering is used.
ModelAdmin.get_ordering(request)
❓As with
ordering
, but can be customised per request.ModelAdmin.list_per_page
👀The number of items to be shown per page. Currently unsupported in snippets, but the
IndexView.paginate_by
can be overridden through a customSnippetViewSet
. Done in Allow customising the number of items per page throughSnippetViewSet
#10241.ModelAdmin.get_queryset(request)
❓The base queryset to be used on the listing. Currently unsupported in snippets, but the
IndexView.get_queryset()
method can be overridden through a customSnippetViewSet
.ModelAdmin.get_extra_attrs_for_row(obj, context)
❓Returns a dictionary of extra attributes to be added to the
<tr>
tag.ModelAdmin.get_extra_class_names_for_field_col(obj, field_name)
❓Returns a list of CSS classes for the column of the field for the given object (row).
wagtail.contrib.modeladmin.mixins.ThumbnailMixin
❓ 👀 🛠️Allows showing a thumbnail image by specifying
"admin_thumb"
inlist_display
.Note: If we want to support this, we can probably create a
Column
subclass that can be put intolist_display
to make this easier.ModelAdmin.thumb_image_field_name
❓The
ForeignKey
field towagtailimages.Image
ModelAdmin.thumb_image_width
❓Thumbnail width, defaults to
50
.ModelAdmin.thumb_classname
❓CSS class name to be added to the
<img>
element, defaults to"admin-thumb"
.ModelAdmin.thumb_col_header_text
❓Column header text for the thumbnail, defaults to
"image"
.ModelAdmin.thumb_default
❓Fallback image if missing, can be from the static files or an external URL.
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 overridingIndexView.get_columns()
if necessary.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. Currently unsupported in snippets, but the
IndexView.template_name
can be overridden through a customSnippetViewSet
.ModelAdmin.index_view_class
✅The index view class to use for the listing. Currently supported through a custom
SnippetViewSet
.Customising
CreateView
,EditView
andDeleteView
❓ 👀 🛠️Configuration
Changing which fields appear in
CreateView
&EditView
✅Editable fields can be configured through the
panels
oredit_handler
attribute on the model definition. This is supported in snippets. However, ModelAdmin also allows specifying this on theModelAdmin
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. Currently unsupported in snippets, but the
CreateView.template_name
can be overridden through a customSnippetViewSet
.ModelAdmin.create_view_class
✅The create view class to use. Currently supported through a custom
SnippetViewSet
.ModelAdmin.edit_template_name
❓The template name to be used for the edit view. Currently unsupported in snippets, but the
EditView.template_name
can be overridden through a customSnippetViewSet
.ModelAdmin.edit_view_class
✅The edit view class to use. Currently supported through a custom
SnippetViewSet
.ModelAdmin.delete_template_name
❓The template name to be used for the delete view. Currently unsupported in snippets, but the
DeleteView.template_name
can be overridden through a customSnippetViewSet
.ModelAdmin.delete_view_class
✅The delete view class to use. Currently supported through a custom
SnippetViewSet
.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
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 customSnippetViewSet
.Enabling & customising
InspectView
❓ 👀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 settingModelAdmin.inspect_view_enabled = True
.We currently don't have an equivalent of
InspectView
in snippets. If desired, we can make a genericInspectView
and include it as part of theSnippetViewSet
.Configuration
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
❓ 🗑️A list of CSS files to be added to the inspect view.
ModelAdmin.inspect_view_extra_js
❓ 🗑️A list of JS files to be added to the inspect view.
ModelAdmin.inspect_template_name
❓The template name to be used for the inspect view.
ModelAdmin.inspect_view_class
👀The inspect view class to use.
Other mechanisms
Template overrides ❓ 👀
By default, ModelAdmin looks in these directories for template discovery:
templates/modeladmin/app-name/model-name/
templates/modeladmin/app-name/
templates/modeladmin/
Which allows template overrides without specifying
foo_template_name
in theModelAdmin
class. This is currently unsupported in snippets.Helper classes ❓
Helper classes encapsulate the logic that is commonly used across views in ModelAdmin. There are three types of helper classes:
ModelAdmin.url_helper_class
❓ 👀Helps with the consistent generation, naming and referencing of URLs. If we want to support custom URLs for snippets, we'll probably need something similar.
ModelAdmin.permission_helper_class
❓ 🗑️Helps with ensuring only users with sufficient permissions can perform certain actions, or see options to perform those actions. With snippets, we use an instance of the
ModelPermissionPolicy
class exposed asself.permission_policy
on theSnippetViewSet
.ModelAdmin.button_helper_class
❓ 🛠️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 undocumentedget_buttons_for_obj
method must be overridden.For snippets, we have the
register_snippet_listing_buttons
andconstruct_snippet_listing_buttons
hooks. Theregister_snippet_listing_buttons
hook makes it considerably easier to add custom buttons compared to ModelAdmin. However, overriding the default ones is slightly harder as you'll need to scan the list of buttons in aconstruct_snippet_listing_buttons
hook and replace it with a newSnippetListingButton
instance. Due to the nature of the hooks, these customisations are done inwagtail_hooks.py
, separate from theSnippetViewSet
.Intentionally left out
ChooseParentView
🗑️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 82: Treeless page listings rfcs#82.
Mentions of ModelAdmin in the docs
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.
Mentions
docs/advanced_topics/icons.md
: Links to ModelAdmin's menu icon configurationdocs/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 useddocs/reference/contrib/index.md
: Normal mention as part of the list of contrib modulesdocs/reference/pages/model_recipes.md
: Normal mention as part ofClusterableModel
requirement for tagging. However, there's a specific "Managing tags with Wagtail'sModelAdmin
" 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 ModelAdmindocs/advanced_topics/multi_site_multi_instance_multi_tenancy.md
: A bit elaborate mention:General open questions
Ideally, I'd like us to consider each of the above customisation points of ModelAdmin and decide whether we want to support it in snippets and if so, how.
In addition to that, there are also some open questions regarding the bigger picture of snippets vs ModelAdmin parity and how we're going to implement the features:
Shall we introduce a new class for the configuration, or keep using
SnippetViewSet
? ✅If it's the latter, we will need to wrap everything snippet-related into
SnippetViewSet
. This includes the chooser viewset and the chooser widget. Either way, we'll make theregister_snippet
function much simpler, with most of the registration handled by the viewset itself.register_snippet
internalswagtail/wagtail/snippets/models.py
Lines 86 to 135 in 2519dc8
Update: as of Move snippet choosers and model check registration to
SnippetViewSet.on_register()
#10216,SnippetChooserViewSet
is part ofSnippetViewSet
, so we could go ahead and useSnippetViewSet
as the main entry point for customisation.Shall we allow partial registration, i.e. CRUD without chooser and vice versa?
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.
With the ability to register each snippet model as its own menu item, when should the "Snippets" menu item be shown?
Shall the "Snippets" menu item only be shown if there are snippet models that do not have their own menu items? Shall we introduce a setting (or other mechanism) to just always hide the menu item?
Related discussions:
Beta Was this translation helpful? Give feedback.
All reactions