Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 85: Snippets parity with ModelAdmin #85

Merged

Conversation

laymonage
Copy link
Member

@laymonage laymonage commented Mar 24, 2023

Rendered

Follow up to the Snippets vs ModelAdmin parity discussion, as I think using an RFC is easier for reviewing each individual customisation point described in the discussion.

Note that some of the items in the RFC have been implemented and merged into main (PRs are linked). However, it is likely that some of the customisations need a clearer implementation plan, hence the RFC. These features may not make it into the next release (5.0 at the time of writing), so this RFC can still be useful for future reference.

I highly recommend giving the code of the current SnippetViewSet class a read: https://github.com/wagtail/wagtail/blob/main/wagtail/snippets/views/snippets.py#L585

@laymonage laymonage self-assigned this Mar 24, 2023
@laymonage laymonage force-pushed the rfc-85-modeladmin-parity-for-snippets branch from a8518ce to 79043f1 Compare March 24, 2023 20:09

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.

If we want to support this, there are a few questions to answer:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discuss


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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discuss, though I personally don't see much value for this

Copy link

@ababic ababic Mar 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The menu_icon attribute in ModelAdmin serves the same purpose really (it isn't just for the 'menu' - it appears in headers and everywhere else icons are featured), so I would class this as done. Icons are all about improving recognisably, so it's difficult to imagine wanting a separate icon to appear in the menu.

Comment on lines 135 to 141
- [ ] **`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 the `field_name`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this? We're currently using the tables component from wagtail.admin.ui.tables, so adding these may be slightly tricky. I'd imagine we could add an additional keyword argument to the base Column class for the empty value display and pass the value from SnippetViewSet there. However, we have quite a lot of keyword arguments on the Column class already

Copy link

@ababic ababic Mar 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I added these to ModelAdmin for parity with Django, but I haven't ever used them in a project. These days, if I wanted to change the blank/null representation for a specific field, I'd be more likely to add a property method to the model class and use that instead.


Same as `list_display`, but for exporting to spreadsheets.

- [ ] **`ModelAdmin.list_filter`**
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be marked as done, or do we want to do something more?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's definitely scope for something more convenient than the django-filter approach, but that would probably fair better as a separate RFC, as the aim should be for a consistant approach throughout.


The name of the exported spreadsheet.

- [ ] **`ModelAdmin.search_fields`**
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think it is not worth it, but feel free to discuss

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a like-for-like implementation would be desirable either. Though, having some control over which fields to search on when searching from the listing page could definitely be useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I noticed that Wagtail's generic IndexView already has search_fields in place that we could hook up from SnippetViewSet.

I also managed to add support to switch between Wagtail's search backend or Django's QuerySet API in a minimal way that doesn't need a separate search handler class. I believe the main use cases for ModelAdmin's search handler is to choose between Wagtail search and Django anyway, so I think this should be enough for our parity goal.

See:

Comment on lines 341 to 351
### Template overrides

By default, ModelAdmin looks in these directories for template discovery:

1. `templates/modeladmin/app-name/model-name/`
2. `templates/modeladmin/app-name/`
3. `templates/modeladmin/`

Which allows template overrides without specifying `foo_template_name` in the `ModelAdmin` class.

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`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the current templates live in templates/wagtailsnippets/snippets/, the likely discovery order would be:

  1. templates/wagtailsnippets/snippets/app-name/model-name/
  2. templates/wagtailsnippets/snippets/app-name/
  3. templates/wagtailsnippets/snippets/

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found myself wanting to override a template to get some custom JS into a chooser's 'create' view and this feature would definitely be useful. I don't suppose the paths matter too much (as long as they are documented), but I would have thought that wagtailsnippets/ instead of wagtailsnippets/snippets/ would be verbose enough to avoid clashes with anything else.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this was an old diff of the RFC and the feature was implemented in wagtail/wagtail#10271 (available in 5.0+).

wagtailsnippets/snippets/ was kept for backwards-compatibility, but it can be customised via SnippetViewSet.template_prefix. The RFC has been updated to reflect this.

Comment on lines 353 to 355
### Helper classes

Helper classes encapsulate the logic that is commonly used across views in ModelAdmin. There are three types of helper classes:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal opinion is to not implement these for snippets, as we can achieve the same things via other means that are more in line with how we do things in Wagtail

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fundamentally, I agree. Helper classes were just my way of encapsulating certain bits of logic in a way that made things portable/swappable, without complicating the ModelAdmin class too much (something I'd always felt Django's version could do better).

It feels as though Wagtail has naturally evolved to do much of this in different ways, which is fine. I think it's fair to say there are some rough edges as a result of that though, that will need addressing at some point (for example, the title column in snippet listings always adding an edit link, regardless of whether the user has that permission). The route to overriding permission checking logic via generic views in general feels like it needs some refinement, but that doesn't necessarily need to be part of this RFC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, thanks, I'll try to revisit this in a future release.

Comment on lines 402 to 404
- **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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discuss

As noted in wagtail/wagtail#10206 (reply in thread), I think one way we could allow disabling this is by overriding the {foo}_view_class (or the {foo}_view) properties on the SnippetViewSet to None. Then, in get_urlpatterns(), we only register the URLs if the corresponding view is not None.

As noted in that comment, though, the tricky part is to gracefully handle the UI when the expected URLs are not available.

Copy link

@ababic ababic Jul 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all it's sins, ModelAdmin does at least have this element down, but the recommended solution would be to use a custom PermissionHelper class to block things at the permission-checking level (e.g. By always returning False).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think this would be possible to some extent via the permission_policy that is customisable on SnippetViewSet, but I haven't tested this. Will revisit in a future release.

Comment on lines 406 to 412
- **Shall we consider changing the signature of `register_snippet`?**

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.

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.

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).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat in favour of allowing both register_snippet(SnippetModel) and register_snippet(SnippetViewSet), with the option to deprecate the viewset parameter in the future.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, that flexibility is a nice improvement on what modeladmin has.

Copy link
Member Author

@laymonage laymonage Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, again this has been implemented in wagtail/wagtail@8df6e62 and available in Wagtail 5.0+.

Comment on lines 414 to 416
- **What is the deprecation timeline of ModelAdmin?**

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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably answer this myself, but I wonder what others might think about it

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I don't think we should bother breaking modeladmin out into it's own package - the maintenence overhead wouldn't really justify the effort of removing it in the first place. I think it'd be quite reasonable to deprecate it without any fixed timeline for removal, encourage users to feed back on any pain points that come with migration, and just remove it once we feel ready.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the modeladmin package as serving a similar role to the hallo.js one - a courtesy for people who are "locked in" to modeladmin, perhaps because they've made low-level customisations or simply because they don't have a developer available for the task of figuring out the correspondences from their modeladmin code to snippets. Keeping it outside Wagtail core will communicate that new projects shouldn't use it (and third-party packages that depend on it should migrate away). We only need to maintain it to the extent that the demand from the community exists, and over time the need for that package will organically fade away.

@lb-
Copy link
Member

lb- commented Mar 25, 2023

Just wanted to say wow! Thanks @laymonage - this is an incredible write up and covers the discussion of every single feature I can think of so far.

I've had a quick read but will double back with comments when I have a bit more time.

One thing that could be clearer is that Snippets are intended to become the recommended way to manage non-Page models within Wagtail.

You have mentioned that there is a seperate RFC for managing other views of Pages which is good. However, I think this is something that will intentionally be different compared to ModelAdmin.

@thibaudcolas
Copy link
Member

Since ModelAdmin has relatively unique API names, particularly for all the customisable attributes, I had a go at checking how many of the projects I have access to use those APIs. Here are the results: ModelAdmin API usage - RFC 85 ModelAdmin vs. Snippets feature parity.

This is for 70 "Torchbox" website/client projects, and 70 "Wagtail" packages (excluding Wagtail itself). Those projects aren’t particularly representative but I hope this nonetheless helps illustrate how much usage those APIs might get relative to one-another. Summary:

Things that show up a lot (some of which could be false positives): menu_icon, menu_label, menu_order, list_filter add_to_settings_menu, button_helper_class, InspectView

Things that have 0 use across all 140 projects:

add_to_admin_menu
base_url_path
choose_parent_template_name
choose_parent_view_class
delete_template_name
export_filename
get_empty_value_display
get_extra_attrs_for_row
menu_item_name
search_handler_class
thumb_classname
thumb_col_header_text
thumb_default
thumb_image_field_name
thumb_image_width
ThumbnailMixin

Things that see lots of usage in TBX sites/projects, very little in Wagtail packages:

list_export
permission_helper_class
inspect_view_fields
index_view_class
InspectView
admin_order_field

Things that see lots of usage in Wagtail packages, little on TBX projects:

form_view_extra_css
form_view_extra_js
index_view_extra_js

@laymonage
Copy link
Member Author

Hi all, I've updated the RFC to reflect the current state of the parity and our plans for the deprecation timeline. Please leave a comment if you have any feedback, thanks!


A dict mapping prepopulated fields to a tuple of fields to prepopulate from, useful for slug generation on Page models.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to call out there are already issues to do similar sets of behaviour and it is best we solve this with either documented example Stimulus approaches or Wagtail wide solutions such as via Panels as mentioned.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I've updated this paragraph to add this detail.


- Turn off specific views

There have also been requests to turn off specific views. While it is possible to override the URL registration of the views, the current views implementation rely on the other views being available. For example, the ability to turn off the index, create, and delete views can be useful to enforce a singleton snippet within the admin.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d like to see us move the documentation for a custom clean method to somewhere else if possible.

Either to a Snippets tips & tricks or somewhere else suitable, this is a common request and the functionality is actually something generic to all forms in Wagtail and is often something asked about on Slack.

https://docs.wagtail.org/en/stable/reference/contrib/modeladmin/tips_and_tricks/custom_clean.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I'll try to do this during the 5.1 feature freeze. Thank you!

Copy link
Member

@lb- lb- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incredible @laymonage

I was quite skeptical when this was discussed over a year ago as ModelAdmin is one of my favourite features. However, I feel that the approach taken has been smooth, steady and offers a much better API surface area that also makes sense in the context of the rest of Wagtail.

I think we should do our best to ensure that the split out ModelAdmin repo has some nightly tests against Wagtail main, at least for one LTS release cycle. So that the core team / others can try to keep compatibility with this external package while the community migrates.

It may be worth also reviewing what other key community packages are reliant on ModelAdmin and alert them with an issue on their repo about this RFC once approved. This may already be done.

Happy to approve this in its current state and very glad to see this all worked through in such great detail.

@laymonage laymonage force-pushed the rfc-85-modeladmin-parity-for-snippets branch from 5d36445 to b16d7c8 Compare July 18, 2023 11:08
@laymonage
Copy link
Member Author

Thank you everyone for all the comments! As this has been approved for a while and we've made a lot of progress in the Wagtail repo, I'm going to merge this just in time for Wagtail 5.1 RC release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Status: Done
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

5 participants