Snippets generalisation: bringing features from SnippetViewSet
into generic ViewSet
/ModelViewSet
#10740
Replies: 8 comments 4 replies
-
Thanks @laymonage - this is looking good to me! Happy we're going with revisions etc being standard features of snippets specifically rather than models in general - as I recall, those were briefly part of the generic model views during the development of those features for snippets, and ended up swamping the codebase in ways that were counter-productive for simpler models. Also, the models making use of those features are more likely to be ones that fit with the existing perception of snippet as "a chunk of web content", rather than arbitrary business objects where people would be more hesitant about the name Snippet, so that works out nicely 👍 Just a small point on the admin URL configuration part: for viewsets in general, I think specifying the URL namespace during instantiation is most natural, since there could conceivably be situations where there are multiple instances of the same viewset class registered within the urlconf, at different URL paths and namespaces. Of course, that doesn't fit naturally with how snippet viewsets are configured, since the URL registration (and thus instantiating the viewset) is hidden from the developer via |
Beta Was this translation helpful? Give feedback.
-
Without really knowing what's involved it'd be great if this worked like snippets instead of needing custom code, i.e. your model extends |
Beta Was this translation helpful? Give feedback.
-
We're in the process of migrating from |
Beta Was this translation helpful? Give feedback.
-
Copying the following: #8609 (comment) for visibility. Are there any plans to actually get rid of the "Snippets" term and/or this extra level (e.g. in the URL, breadcrumbs,...)? We've actually never used snippets before, we've only used the ModelAdmin, since this extra level of "Snippets" (and the term itself) created much confusion in the beginning ("Everything is basically a model, why are pages, images, documents, forms,... in the root or in custom submenus, and some other models below 'Snippets'"?). Since this has been improved a lot by your amazing work, we are now trying to migrate everything (new) to the But even with Currently, we use the following tweaks for our
So I'm thinking of making the snippet-less configuration the default, but still allow to switch to the old setup (for a deprecation period vs. just keep the option). |
Beta Was this translation helpful? Give feedback.
-
I am trying to migrate from |
Beta Was this translation helpful? Give feedback.
-
Honestly, while I support making Snippets more powerful and generalized, I am surprised at the choice to deprecate I hope Torchbox can reconsider the decision to deprecate ModelAdmin and reverse course, as we are losing a powerful and well-known tool with the current decision. |
Beta Was this translation helpful? Give feedback.
-
Our website also makes use of the So, clear guidance on how to define custom buttons or URLs on |
Beta Was this translation helpful? Give feedback.
-
Hi there, I've started a related discussion. Didn't post it here, as it is kinda different subject, though I've written about ModelAdmin depreciation. |
Beta Was this translation helpful? Give feedback.
-
As a continuation of RFC 85: Snippets parity with ModelAdmin and following the discussion on Wagtail Slack, there are concerns about how Snippets is replacing ModelAdmin, yet the term "snippet" doesn't always fit all models.
From the docs:
Models such as "shopping cart orders", "feedback submissions", "taxonomies", and other descriptive data do not really fit the above description. However, it still makes sense to manage these models in the Wagtail admin.
As @ababic put it:
Meanwhile, Wagtail has a generic
ModelViewSet
that can be used to build basic CRUD views in the admin. TheModelViewSet
class is built on top of the baseViewSet
class, which is "Wagtail’s mechanism for defining a group of related admin views with shared properties, as a single unit".In fact, as of #10294,
SnippetViewSet
is now a subclass ofModelViewSet
. That said, most of the features for Snippets (including the ones listed by Andy) are implemented inSnippetViewSet
, which makes it necessary to register the models as snippets in order to use the features.To solve this problem, we have two main options:
SnippetViewSet
toModelViewSet
, and providing a similar registration mechanism.As the term "snippet" is used in code and the admin interface, renaming it is non-trivial and will likely require a transitional period. The second option is much more feasible, and allows us to have the separation between the current "snippet" concept and models in general, while still avoiding code duplication. It also doesn't keep us from dropping/renaming the term "snippet" in the future.
Hence, this discussion proposes that we start "lifting" the code from
SnippetViewSet
to theModelViewSet
, or even the baseViewSet
class. The discussion will be broken down into these sections:ViewSet
ModelViewSet
without requiring mixins on the modelModelViewSet
but depend on mixins applied on the modelTo be in base
ViewSet
The following are features in
SnippetViewSet
that we could bring toModelViewSet
or potentially baseViewSet
. These can be useful even for non-model basedViewSet
s, though we currently don't have any that's built-in:Admin URL configuration
Relevant snippets PR: #10235.
This concerns the customisation of the URL namespace that's used when doing
reverse()
, e.g.myownmodel:edit
instead ofwagtailsnippets_myapp_mymodel:edit
, and the base path in the admin, e.g./admin/myownmodel/
instead of/admin/snippets/myapp/mymodel/
.The base
ViewSet
class (and by extensionModelViewSet
) already supports this, but it's done during the instantiation of the class. OnSnippetViewSet
, you can customise this using class attributes. See:wagtail/wagtail/snippets/views/snippets.py
Lines 1364 to 1377 in 801eccc
wagtail/wagtail/snippets/views/snippets.py
Lines 852 to 856 in 801eccc
If we want to add the same support, we'll likely need to change
ViewSet.__init__
's signature:wagtail/wagtail/admin/viewsets/base.py
Lines 12 to 14 in 801eccc
So that both
name
andurl_prefix
are optional, or at least fall back to the same methods/attributes used on theSnippetViewSet
. See the "Viewset registration" section much further in this discussion for more details.Menu registration
Relevant snippets PR: #10330.
Menu-related methods and properties
We need to move the following methods and properties to the base
ModelViewSet
class if we want to allow a quick way to register a menu item for the model. This might be useful if people create a customViewSet
subclass that does not rely on a model, so it seems to make sense to put this in the baseViewSet
class instead.wagtail/wagtail/snippets/views/snippets.py
Lines 1232 to 1275 in 801eccc
wagtail/wagtail/snippets/views/snippets.py
Lines 1552 to 1556 in 801eccc
Note
If moved to base
ViewSet
, it may not make sense forChooserViewSet
to inherit these methods.For
register_menu_item
, it's probably useful to make the hook name configurable (defaults to"register_admin_menu_item"
), so it can be put in other top-level menu e.g. the help menu item.Generic
ViewSetGroup
It would be nice to turn
SnippetViewSetGroup
(if you're not familiar, thinkModelAdminGroup
) into a genericViewSetGroup
that works with anyViewSet
s (maybe exceptChooserViewSets
) to ease registration of viewsets under a single top-level menu item.wagtail/wagtail/snippets/views/snippets.py
Lines 1568 to 1657 in 801eccc
Icons
Relevant snippets PR: #10178.
wagtail/wagtail/snippets/views/snippets.py
Lines 1228 to 1230 in 801eccc
It might make sense to put
icon
andget_icon()
in the baseViewSet
class. However, worth noting thatViewSet
has no views configured so it will only be used by subclasses and notViewSet
itself. Still, just like with the menu, this might be useful if people create a customViewSet
subclass that does not rely on a model.Currently, the
ModelViewSet
already supports theicon
attribute but not theget_icon()
method.To be in
ModelViewSet
Template customisation points
Relevant snippets PR: #10271.
get_template()
app_label
andmodel_name
as namespaces. See:wagtail/wagtail/snippets/views/snippets.py
Lines 1291 to 1304 in 801eccc
template_prefix
"wagtailadmin/generic/"
.{foo}_template_name
SnippetViewSet
, e.g.index_template_name
,edit_template_name
, etc. We might want to put this onModelViewSet
as well.Filter definitions
Relevant snippets PR: #10256.
The automatic
FilterSet
class generation is already in the genericIndexView
, so we only need to allowlist_filter
/filterset_class
to be defined onModelViewSet
and pass it down to theIndexView
kwargs.Custom columns
Relevant snippets PR: #9147.
As with filters, this is mostly handled by the generic
IndexView
so we only need to passlist_display
fromModelViewSet
.Search configuration
Relevant snippets PR: #10290.
As with filters and columns, this is mostly handled by the generic
IndexView
. We need to passsearch_fields
andsearch_backend_name
fromModelViewSet
.Listing export
Relevant snippets PR: #10626.
This requires
SpreadsheetExportMixin
andlist_export
to be defined on theIndexView
. The question is, where shouldSpreadsheetExportMixin
be applied? It can be applied dynamically in theModelViewSet
just before theIndexView.as_view()
call based onlist_export
, or we might as well apply it to the genericIndexView
.QuerySet override
Relevant snippets PR: #10275.
This allows the queryset used on the index view to be customised on a per-request basis. To support this, we can move the
get_base_queryset()
override in snippets'IndexView
:wagtail/wagtail/snippets/views/snippets.py
Lines 170 to 175 in 4d870af
to the generic
IndexView
, spefically moving theif
check to anelif
after theisinstance
check here:wagtail/wagtail/admin/views/generic/models.py
Lines 188 to 191 in 4d870af
Then, we pass
ModelViewSet.get_queryset(request)
as thequeryset
attribute for the index view duringas_view()
.That said, only using the override on the index view may not be very useful, as outlined in #10746. This likely needs more thought, so we'll probably tackle this customisation separately.
Edit handler
Relevant snippets PR: #10299.
wagtail/wagtail/snippets/views/snippets.py
Lines 1518 to 1541 in 801eccc
This allows edit handlers/panels to be defined on the
ModelViewSet
instead of the model, allowing for better separation between models and view-related code.Inspect view
Relevant snippets PR: #10621.
When inspect view was added to snippets, we implemented a generic
InspectView
as well, so this just needs to be added to theModelViewSet
.The challenge is that we now have to show the "Inspect" button somewhere, and the generic
IndexView
currently has no mechanism to add arbitrary buttons on the listing. We may have to introduce a way to customise the listing buttons via theModelViewSet
(and consequently,SnippetViewSet
) class. Snippets currently rely on hooks to customise the listing buttons, which is against the goal of making the viewset class the "one customisation point". Adding support for customising the listing buttons on the viewset class also brings us somewhat closer to how ModelAdmin did it.Usage view
Relevant snippets PR: #10007.
When usage view was added to snippets, we implemented a generic
InspectView
as well, so this just needs to be added to theModelViewSet
.The challenge is that this is normally accessed via the status side panel. The generic views currently have no support for the status side panel, so we need to add it somehow. Consequently, the status side panel is normally accessed via the header/breadcrumbs, which is also not available in the generic views. As a result, we may also have to add the breadcrumbs.
History view
We've always had a history view for snippets, but we don't yet have a generic
HistoryView
class. We need to implement a genericHistoryView
and include it inModelViewSet
. The history view is also typically accessed via the header/breadcrumbs, so we will also have to add it.Left in
SnippetViewSet
There are additional features supported in
SnippetViewSet
that requires the model to extend from certain model mixins. Due to the complexities of the possible model mixin combinations, and the fact that this also requires the generic views to be enhanced with additional view mixins, we'll likely leave these features as snippets-only:str
representation on theIndexView
That said, it should technically still be possible to selectively enable these features by extending
ModelViewSet
and cherry-picking the overrides fromSnippetViewSet
.Other points
Viewset registration
Currently, registration of viewsets is handled by the
register_admin_viewset
hook. For aModelViewSet
, the relationship between the viewset and model is only fromModelViewSet
to the model, and not the other way around. This means that you can register as manyModelViewSet
s as you want for a given model, but as a consequence you cannot get a specificModelViewSet
instance given a particular model.Certain features for snippets require a 'canonical' viewset, which is attached to the model as
model.snippet_viewset
, mostly to get the URL configuration:As the above features are not relevant or have other workarounds for non-snippet models, having a "canonical" viewset is probably not necessary. If we do need it, a better approach would probably be using a registry instead of patching it on the model.
That said, the way
register_admin_viewset
hook works is slightly different to how theregister_snippet
andmodeladmin_register
function work.ModelViewSet
registrationThe following is currently how a
ModelViewSet
can be registered:SnippetViewSet
registrationMeanwhile, the following is how a
SnippetViewSet
can be registered:Keep in mind that the
SnippetViewSet
approach is very similar to theModelAdmin
approach.Making the
ModelViewSet
registration similar toSnippetViewSet
If we want to make
ModelViewSet
registration similar toSnippetViewSet
, then we need to:ModelViewSet.__init__()
(orModelViewSet.__init__()
) to allow the base URL path and the URL namespace to be defined on theModelViewSet
class.register_viewset
function that:ModelViewSet
subclass without any argument, e.g.viewset_instance = MyModelViewSet()
wagtail.admin.viewsets.viewsets.register(viewset_instance)
ReferenceIndex.register_model(viewset_instance.model)
MyModel
toMyModelViewSet
to a registry.Choosers
Worth noting that a generic
ChooserViewSet
is also available, and optionally can be used in tandem withModelViewSet
. WithSnippetViewSet
, theSnippetChooserViewSet
is registered alongside with it through composition. I think it makes sense for genericChooserViewSet
to be separate from theModelViewSet
so that it's opt-in rather than opt-out, especially with how ModelAdmin didn't support choosers.Also worth noting that if desired, allowing to disable the chooser widget in
SnippetViewSet
is probably as simple as extracting theviewsets.register(self.chooser_viewset)
line in the following method into a separate method that can be overridden into a no-op:wagtail/wagtail/snippets/views/snippets.py
Lines 1558 to 1565 in 801eccc
Questions
Summarising open questions from above:
ViewSet.__init__()
's signature to make thename
andurl_prefix
optional?ModelViewSet.get_queryset(request)
?ModelViewSet
to be used in the views #10746?SpreadsheetExportMixin
?"wagtailadmin/generic/"
.Conclusion
Once all of the above is done, we can say that:
ModelViewSet
is the direct replacement to theModelAdmin
class.SnippetViewSet
is aModelViewSet
subclass that adds support for snippets-only features, e.g. revisions, publishing, previews, choosers, etc.SnippetViewSet
instead ofModelViewSet
only means:SnippetChooserViewSet
for the model (and the chooser widget that comes with it).Beta Was this translation helpful? Give feedback.
All reactions