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
Filter PageChooserPanel queryset #1250
Comments
Yea! I thought about it aswell. For peeps at torchbox, it should be known for adding authors for blog posts in wagtail-torchbox 😄 Much better to jump directly to people pages. |
Interesting idea! Clearly you can't pass an arbitrary queryset as part of the URL to /admin/pages/chooser, so you'd probably have to pass it a reference to the original model field (something like You'd also need to define the queryset in a way that avoids evaluating it at server startup - it would probably have to be a callable (e.g. a lambda function) that returns a queryset. In fact, now that I think about it some more - this is an unsolved problem within Django, too. limit_choices_to is Django's attempt at limiting the values of foreign key fields, but it doesn't work with arbitrary querysets (only filter dicts and Q objects), and it doesn't integrate fully with raw_id_fields in Django admin, which is Django admin's equivalent of Wagtail's PageChooserPanel. In other words - there's an opportunity here for someone to solve this problem for both Wagtail and Django :-) But to bring this back down to earth... maybe this is all overkill, and the easy solution is to make the page chooser interface a bit smarter. If the page chooser is asking for an EventPage, and all of our EventPages exist in a single section, then there's no point dropping the user at the root level - we should just take them straight to that section. |
Maybe it would be possible to drop the user at the shallowest level where a page of the specified type appears? A simple list of pages would also work nicely for most cases. IMO, the search is main advantage over a simple select field. |
Related / alternative suggestion: #670 |
We're using this to select featured articles for a blog index page from a blogs child posts. It would be much less confusing for the blog author to start from the index page instead of the root of the tree, that way only the blog posts for that blog are shown. At the very least it would be lovely to have a boolean to select use currently page as the root of the tree, but having full queryset capabilities would be amazing. |
For context, the suggestion in #670 proposes a function approach, eg. pass a function into PageChooserPanel that returns a list of pages. This would allow for a more robust solution, being able to filter by anything the developer needs like if pages have images attached. |
Hey can I get some advice on how to approach fixing this issue? I need to have many of my snippet models scoped by site (I have a multitenanted app). So I need to be able to override the query done in wagtailsnippets/views/chooser.py's choose method. I am currently monkey patching that method so that I can add a class method on my snippet class that contains the queryset for the chooser panel. Please see this gist. If this would be acceptable, I could work it into a patch for SnippetChooserPanel - and perhaps for PageChooserPanel as well The last comment on this thread seemed to point to a solution that was more about passing the query override into the panel definition - more like this. The explicitness of that is rather nice - but would require more code tracing for me to explore a solution. Comments? |
Thanks for looking into this @cnk! While the Firstly, we want to avoid assuming that the editor is logged in through the same hostname as the site they're editing, as that closes off the possibility of having one central team responsible for multiple sites (I believe there are a few Wagtail installations in the wild working on that model). Thinking about it more abstractly - It's hard to say how we can avoid this, but I think the first step is to make choosers more customisable so that, rather than an all-purpose |
Yes, my example of restricting to items associated with a specific site was just an example of the general case of "pass a function to do the restriction". I see your point about the request being an unknowable bag of attributes that might change so is a slightly risky thing to depend on in one's code. I'll take a look at the wagtailmodelchoosers repo above. Hopefully it will give me some ideas on how to make more customizable choosers. Working from the Bakery Demo and some other GitHub issues, I have been trying to cobble together the UI I want for my ManyToMany relationship and frankly am getting very frustrated. If that doesn't get me what I need, I'll extract a demo project and ask for advice. |
Not quite the same as specifying the That hook might then be used like: @hooks.register('construct_page_chooser_queryset')
def limit_page_chooser_to_site_specific_pages(pages, request):
try:
return pages.in_site(request.site)
except Exception as e:
# couldn't filter by site so return untouched pages queryset
return pages In order to achieve the site-limited query sets mentioned by @cnk. |
Isn't this something that can be implemented like the Django ModelAdmin function formfield_for_foreignkey? I need this functionality for custom models, but this isn't possible because the ModelAdmin of Wagtail is custom. |
For our use case, we want to select
Note, the global hook approach wouldn't work in our case as it would affect all Alternatively, I've considered setting the default button state from "save draft" to "publish" for the Author form so no Author can be added in a draft state. |
@brylie Can an author be unpublished while it has articles associated with it? If so, a widget that limits choice is only a partial solution in your case. Whatever author is selected, you still need to check if the related author is live before rendering a link to it on the article page.... and once you have that, do you need to restrict the chooser queryset any longer? On the one hand, it's helpful for editors to see relevant options only, but on the other, maybe both pages are a work-inprogress, and not being able to select the draft author for the article is a barrier that doesn't explain itself very well? |
I believe specifying the As mentioned above, I'm also trying to figure out how to remove the "save as draft", and by extension, "unpublish", button from the "create author" form, since we don't want to save authors in the draft state or unpublish them. The template check for |
@brylie okay, that all adds up. I just wanted to use your example to illustrate that related objects have their own lifecycle, affected both by direct and indirect actions, from editors and developers. In over ten years of Django development, I have used As maintainers, we have a responsibility to prevent developers from shooting themselves in the foot. Maybe that can be done with heavy caveat-ing in the documentation, but my sense is that, even with that, it would still lead to a gradual influx of queries, false bug reports, and general dissatisfaction. I honestly think the best solution here is to have some better base classes for developers to extend from... Then if they decide that overriding the queryset is worth the effort of jumping through a few hoops to create a custom panel/widget, they can do that easily enough. |
@brylie based on your requirements, and the fact you are considering removing the save as draft button, your |
That could be an excellent approach to define a regular Django model but might lack some of the benefits of the Wagtail page model. Particularly, the end-user needs to select a related entity among tens or hundreds of other entities. The Wagtail PageChooser is part of the core project and provides a friendly user experience. The example use-case was meant to illustrate why a global hook approach may not be granular enough for some projects. Potential foot guns aside, a queryset property on the PageChooser widget is an intuitive place for developers to specify the results they would like to show to the user. |
@brylie For the time being, I think the best option is: https://pypi.org/project/wagtail-modelchooser/.
You may well be aware of this already, but If you want a nice chooser experience, but don't really need any |
https://github.com/wagtail/wagtail-generic-chooser is a good option if trying to do more complex page choosing scenarios. The plan is, eventually, to bring parts of that code into Wagtail core. It might be a good chance for others to use that library and see if it fits your needs, if so please report back or otherwise raise an issue on that repo.
|
We recently switched to wagtail-generic-chooser for our custom choosers (largely to allow us to use a custom base queryset for the chooser listing), and it's been great! We were able to remove a bunch of much jankier code that we'd written to support this, and the whole thing feels a lot more solid, now. Really hoping this functionality gets added to Wagtail core soon. |
Another option to achieve this sort of thing is Proxy models.
You can do something similar for snippets / SnippetChooser too, but I haven't found a nice way to hide the menu item / separate listing just yet. |
As of Wagtail 4 the core replacement for wagtail-generic-chooser is now in place - https://docs.wagtail.org/en/stable/extending/generic_views.html#chooserviewset It isn't currently a like-for-like replacement for the page chooser, as it doesn't support tree-based navigation, but maybe it'll fit your needs. |
Is there a recommended way to specify the |
@brylie Overriding |
Is there an example to filter the I have the I am trying to override the class ResourceChooserViewSet(ChooserViewSet):
model = "cms.Resource"
choose_one_text = "Choose a Resource"
per_page = 20
def get_object_list(self):
pages = super().get_object_list()
return pages.filter(live=True) am I missing something? |
@patillacode I would suspect you're missing some additional code to actually register your viewset with wagtail. When I want to override the chooser viewset for snippets, I usually need to do something like this in from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.chooser import SnippetChooserViewSet
from wagtail.snippets.views.snippets import SnippetViewSet
class ResourceChooserViewSet(SnippetChooserViewSet):
choose_one_text = "Choose a Resource"
def get_object_list(self):
pages = super().get_object_list()
return pages.filter(live=True)
class ResourceViewSet(SnippetViewSet):
model = Resource
chooser_per_page = 20
# This is how Wagtail knows to use the custom viewset defined above
chooser_viewset_class = ResourceChooserViewSet
register_snippet(ResourceViewSet) |
It would be great if you could filter Pages on PageChooserPanel using a queryset.
Something like:
The text was updated successfully, but these errors were encountered: