Как добавить поля из других (не страничных) моделей в админку Wagtail Page?
Я ищу способ включить поля других моделей в редактор страниц в Wagtail. Сценарий - цифровой рынок, который мы хотим развернуть для нескольких клиентов. Каждый клиент имеет свои собственные спецификации для участников рынка и поэтому нуждается в различных полях в дополнение к основным полям (название, адрес и т.д.), которые мы определяем заранее
Я ищу решение для администратора торговой площадки (которым будем не мы), чтобы иметь возможность добавлять эти дополнительные поля, например, с помощью сниппетов Wagtail. Они могли бы создать новый сниппет пользовательского поля и определить для него некоторые параметры.
Затем администратор страницы для участников рынка должен отобразить эти дополнительные поля в своей форме (либо в пользовательской вкладке, либо сразу под всеми основными полями в панели содержимого).
Есть ли способ добиться этого? Мне удалось получить пользовательские поля в форме страницы, но без какой-либо стилизации Wagtail. Я знаю о похожем вопросе здесь, но, к сожалению, ответы там не помогли, а здесь сценарий немного другой. В вопросе по ссылке дополнительное поле находится в коде Python в определении модели, чего мы хотим избежать
Вместо этого мы стремимся к структуре данных, аналогичной тому, как WordPress сохраняет метаданные поста (одна таблица базы данных для постов, другая для post_meta). Мы хотим сохранить дополнительные поля в отдельной таблице базы данных и убедиться, что данные сохраняются должным образом при каждом сохранении связанной страницы.
Конечной целью является создание настраиваемого интерфейса для участников рынка, который мы можем развернуть на каждом клиенте без необходимости изменения исходного кода для каждого клиента. Конфигурация должна быть полностью выполнима через интерфейс администратора Wagtail.
Ниже приведен код для сценария, включая мою попытку добавить поля путем переопределения класса base_form_class и скриншот результата. Мой подход не включает никакой проверки или сохранения данных, потому что я еще не дошел до этого. Я уже борюсь с тем, чтобы просто правильно отобразить поля.
Надеюсь, я включил все необходимое для понимания проблемы, но если чего-то не хватает, я с удовольствием добавлю это в этот вопрос.
models.py
class ParticipantListPage(Page):
"""Page to list marketplace participants."""
[...]
intro = RichTextField(
blank=True,
features=['bold', 'italic', 'link'],
verbose_name=_('Intro'),
help_text=_('Optional. Introductional text for this list page.'),
)
content_panels = Page.content_panels + [
# Field panels...
[...]
]
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['participants'] = self.get_children().public().live()
return context
class ParticipantDetailPage(Page):
"""Page to show an individual participant in detail."""
# class variables...
[..]
base_form_class = ParticipantDetailPageForm
# Field definition...
[...]
content_panels = Page.content_panels + [
# Panels for fields...
[...]
]
class CustomParticipantField(ClusterableModel):
"""A model to define a custom field for marketplace participants."""
FIELD_TYPES = (
('text', _('Text')),
('number', _('Number')),
('decimal', _('Decimal Number')),
('url', _('URL')),
('checkbox', _('Checkbox')),
('select', _('Select')),
)
label = models.CharField(
max_length=128,
unique=True,
verbose_name=_('Label'),
help_text=_('The label for this field in the admin interface.'),
)
field_type = models.CharField(
max_length=10,
choices=FIELD_TYPES,
verbose_name=_('Type'),
help_text=_('The type of this field. This determines the way it will '
'be rendered on the page.'),
)
participants = ParentalManyToManyField(
'dmp_marketplace.ParticipantListPage',
blank=True,
related_name='custom_fields',
verbose_name=_('Participants'),
help_text=_('The participants this field will be used for.'),
)
is_required = models.BooleanField(
blank=True,
default=False,
verbose_name=_('Is Required'),
help_text=_('Optional. Choose whether this field is mandatory.'),
)
forms.py
class ParticipantDetailPageForm(WagtailAdminPageForm):
"""Form for ParticipantDetailPage to display associated custom fields."""
def __init__(self, data=None, files=None, parent_page=None, *args, **kwargs):
super().__init__(data, files, *args, **kwargs)
participant_type = self.instance.get_parent().specific_deferred
custom_fields = CustomParticipantField.objects.filter(
participants__in=[participant_type],
)
for field in custom_fields:
field_id = slugify(field.label).replace('-', '_')
if not self.fields.get(field_id, None):
field_attr = {
'required': field.is_required,
'label': field.label,
}
if field.field_type == 'text':
self.fields[field_id] = forms.CharField(max_length=512, **field_attr)
elif field.field_type == 'number':
self.fields[field_id] = forms.IntegerField(**field_attr)
elif field.field_type == 'checkbox':
self.fields[field_id] = forms.BooleanField(**field_attr)
[...]