Wagtail - customise FieldPanel to show results for current Locale

825 Views Asked by At

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 ParentalManytoManyFields in the model, it would be too cluttered for the editors.

Screenshot 2022-07-29 at 15 07 59

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
1

There are 1 best solutions below

2
Rich - enzedonline On BEST ANSWER

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, 
    ),