I have a site which is i18n enabled and using wagtail-localize
. When editing (or creating) the original language of a page, all the snippets show values for every language, if you use the standard FieldPanel
. Using the SnipperChooserPanel is not an option because there are a lot of ParentalManytoManyField
s in the model, it would be too cluttered for the editors.
This is how the model and snippet is constructed.
@register_snippet
class Level(TranslatableMixin):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Meta:
verbose_name = "Educational Level"
unique_together = ('translation_key', 'locale')
class Activity(Page):
...
level = ParentalManyToManyField(Level, verbose_name='Education level', blank=True)
MultiFieldPanel([
....
FieldPanel('level', widget=forms.CheckboxSelectMultiple),
])
I'm trying to work out how to subclass FieldPanel
so it uses the page's locale to filter the snippet queryset.
I have hacky/temporary solution to this using the limit_choices_to
kwarg for ParentalManyToManyField
but I can only filter by the user language not the page language.
def limit_lang_choice():
limit = models.Q(locale__language_code=get_language())
return limit
Turns out the locale is lurking in the BoundPanel.instance
Here's a select panel that will filter according to locale. It'll match the default panel type for the field, or you can override with an appropriate form widget (one of CheckboxSelectMultiple
, RadioSelect
, Select
or SelectMultiple
). Set typed_choice_field=True
to force Select
into a dropdown widget (default is a list).
from django.core.exceptions import ImproperlyConfigured
from django.forms.models import ModelChoiceIterator
from django.forms.widgets import (CheckboxSelectMultiple, RadioSelect, Select,
SelectMultiple)
from django.utils.translation import gettext_lazy as _
from wagtail.admin.panels import FieldPanel
class LocalizedSelectPanel(FieldPanel):
"""
Customised FieldPanel to filter choices based on locale of page/model being created/edited
Usage:
widget_class - optional, override field widget type
- should be CheckboxSelectMultiple, RadioSelect, Select or SelectMultiple
typed_choice_field - set to True with Select widget forces drop down list
"""
def __init__(self, field_name, widget_class=None, typed_choice_field=False, *args, **kwargs):
if not widget_class in [None, CheckboxSelectMultiple, RadioSelect, Select, SelectMultiple]:
raise ImproperlyConfigured(_(
"widget_class should be a Django form widget class of type "
"CheckboxSelectMultiple, RadioSelect, Select or SelectMultiple"
))
self.widget_class = widget_class
self.typed_choice_field = typed_choice_field
super().__init__(field_name, *args, **kwargs)
def clone_kwargs(self):
return {
'heading': self.heading,
'classname': self.classname,
'help_text': self.help_text,
'widget_class': self.widget_class,
'typed_choice_field': self.typed_choice_field,
'field_name': self.field_name,
}
class BoundPanel(FieldPanel.BoundPanel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
if not self.panel.widget_class:
self.form.fields[self.field_name].widget.choices=self.choice_list
else:
self.form.fields[self.field_name].widget = self.panel.widget_class(choices=self.choice_list)
if self.panel.typed_choice_field:
self.form.fields[self.field_name].__class__.__name__ = 'typed_choice_field'
pass
@property
def choice_list(self):
self.form.fields[self.field_name].queryset = self.form.fields[self.field_name].queryset.filter(locale_id=self.instance.locale_id)
choices = ModelChoiceIterator(self.form.fields[self.field_name])
return choices
So in your Activity class, you'd call this with
LocalizedSelectPanel(
'level',
widget_class=CheckboxSelectMultiple,
),