Wagtail - настройка панели FieldPanel для отображения результатов для текущей локали
У меня есть сайт, который поддерживает i18n и использует wagtail-localize
. При редактировании (или создании) исходного языка страницы все сниппеты показывают значения для каждого языка, если использовать стандартный FieldPanel
. Использование SnipperChooserPanel не является вариантом, потому что в модели много ParentalManytoManyField
, это было бы слишком загромождено для редакторов.
Вот как строится модель и сниппет.
@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),
])
Я пытаюсь понять, как подкласс FieldPanel
, чтобы он использовал локаль страницы для фильтрации кверисета сниппетов.
У меня есть халтурное/временное решение для этого, используя limit_choices_to
kwarg для ParentalManyToManyField
, но я могу фильтровать только по языку пользователя, а не по языку страницы.
def limit_lang_choice():
limit = models.Q(locale__language_code=get_language())
return limit
Я долго мучился с этой проблемой, когда создавал систему меню. Мои объекты меню могут быть добавлены как подменю других объектов меню, но та же проблема, что и у вас, это должно быть 4 версии каждого меню (4 языка).
Единственный способ, который я смог найти для определения локали - это URI страницы редактирования. Для новых элементов URI заканчивается на /?locale=xx
. Для отредактированных элементов последним параметром является id объекта, по которому можно запросить локаль. Хитрый способ, но он работает.
Проблема в том, что вы не можете получить доступ к URI из определения модели, для этого нужен пользовательский edit_handler
.
Для этой системы меню подменю добавляются как упорядоченные к объекту родительского меню.
Первое, что мне понадобилось в models.py
- это вызываемый класс, возвращающий все объекты редактируемого класса (в данном случае Menu):
class MenuListQuerySet(object):
# Call as class()() to act as a function call, passes all menus to SubMenuPanel dropdown
# Useful to make a function call in class declaration to make dynamic class variables
def __call__(self, *args, **kwds):
return Menu.objects.all()
Затем в определении панелей моего подменю orderable:
SubMenuFieldPanel("submenu_id", MenuListQuerySet()()),
Наконец, панель FieldPanel:
Важно, чтобы в классе, вызывающем эту панель, вы не определили choices
, иначе он переопределит эту панель и создаст статический список выбора.
Поскольку здесь перечислены объекты меню, которые могут быть добавлены в качестве подменю, я отфильтровываю вызывающий объект (id родителя в этой панели), чтобы меню не могло быть добавлено к самому себе в качестве подменю. Удалите код, связанный с parent_menu_id
, если вы используете его для других целей.
Это дает вам выпадающий селектор, в котором перечислены только опции для добавляемой/редактируемой локали.
В качестве edit_handler
используется Wagtail 3.x, версию 2.x можно найти внизу edit_handlers.py
Пожалуй, не так много работы, чтобы сделать это общим - переименуйте ссылки на Menu, удалите код parent_menu_id
, обновите класс MenuListQuerySet
, чтобы передавать соответствующие данные.
Оказывается, локаль скрывается в BoundPanel.instance
Вот панель выбора, которая будет фильтровать в зависимости от локали. Она будет соответствовать типу панели по умолчанию для данного поля, или вы можете переопределить его с помощью соответствующего виджета формы (один из CheckboxSelectMultiple
, RadioSelect
, Select
или SelectMultiple
). Установите typed_choice_field=True
, чтобы заставить Select
использовать выпадающий виджет (по умолчанию это список).
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
Так что в классе Activity вы бы вызвали это с помощью
LocalizedSelectPanel(
'level',
widget_class=CheckboxSelectMultiple,
),