Wagtail - Как я могу использовать пользовательский структурный блок с программно назначаемыми значениями по умолчанию?

Я создал потоковое поле для некоторых настроек сайта. Это потоковое поле содержит коллекцию пользовательских структурных блоков с именем FacetBlock. FacetBlock содержит логический блок, CharBlock, IntegerBlock и ChoiceBlock. Теперь мне нужно сделать CharBlock переводимым, что означает, что администратор сможет вводить по одному тексту для каждого языка, доступного на сайте.

Я думал сделать это, используя другой пользовательский StructBlock, на этот раз называемый TranslatableTextBlock. Он будет содержать ChoiceBlock, CharBlock и некоторые скрытые входные данные для хранения полного значения. Используя javascript, администратор выберет язык, введет текст для этого языка, а затем перейдет к следующему языку. Я пока не уверен, как это будет сохранено в базе данных, но я даже не в состоянии продвинуться так далеко.

Прямо сейчас у меня могут либо отображаться поля без значений по умолчанию, либо я получаю ошибку javascript (TypeError: e is null) в vendor.js, которая вызывает ту же ошибку в comments.js. Вот код, который у меня есть на данный момент. Я опустил определение FacetBlock, потому что оно отлично работает при преобразовании TranslatableTextBlock в CharBlock.

Код на Python:

class TranslatableTextBlock(StructBlock):
    def __init__(self, default_text:str = None, default_language:str = None, local_blocks = None, search_index = True, **kwargs):
        local_blocks = [
            (
                "language",
                ChoiceBlock(
                    choices=LANGUAGES,
                    default=default_language if default_language else None,
                    help_text=_("Select the language for this text")
                )
            ),
            (
                "text",
                CharBlock(
                    default=default_text if default_text else None,
                    help_text=_("Enter your text in the selected language"),
                )
            )
        ]
        super().__init__(local_blocks, search_index, **kwargs)

    class Meta:
        form_classname = "struct-block translatable-text-block"
        form_template = 'blocks/translatable_text_block_admin.html'
        icon = 'doc-full'
        label = _('Translated text')
        template = 'blocks/translatable_text_block.html'


class TranslatableTextBlockAdapter(StructBlockAdapter):
    js_constructor = "website.blocks.TranslatableTextBlock"

    @cached_property
    def media(self):
        structblock_media = super().media
        return Media(
            js=structblock_media._js + ["website/js/translatable_text_block.js"],
            css=structblock_media._css
        )

register(TranslatableTextBlockAdapter(), TranslatableTextBlock)

Шаблон администратора (translatable_text_block_admin.html):

{% load wagtailadmin_tags  %}

<div class="{{ classname }}">
{% if help_text %}
    <span>
        <div class="help">
            {% icon name="help" classname="default" %}
            {{ help_text }}
        </div>
    </span>
{% endif %}
    {% for child in children %}
    {{ child }}
    <div class="w-field" data-field data-contentpath="{{ child.block.name }}">
        {% if child.block.label %}
        <label class="w-field__label" {% if child.id_for_label %}for="{{ child.id_for_label }}"{% endif %}>{{ child.block.label }}{% if child.block.required %}<span class="w-required-mark">*</span>{% endif %}</label>
        {% endif %}
        {{ child.render_form }}
    </div>
    {% endfor %}
</div>

Код Javascript (translatable_text_block.js):

if (typeof TranslatableTextBlockDefinition === "undefined") {
    class TranslatableTextBlockDefinition extends window.wagtailStreamField.blocks.StructBlockDefinition {
        render(placeholder, prefix, initialState, initialError) {
            const block = super.render(placeholder, prefix, initialState, initialError);    // The error happens on this line.
            // Some custom modifications would happen here.
            return block;
        }
    }

    window.telepath.register("website.blocks.TranslatableTextBlock", TranslatableTextBlockDefinition);
}

Ошибка javascript:

TypeError: e is null
vendor.js:2:191488
Uncaught TypeError: e is null
comments.js:1:47007

Кто-нибудь знаком с вложенным структурным блоком для потоковых полей в настройках администратора? Или, может быть, я ошибаюсь и есть более простое решение? Я приветствую проблемы с фреймом :)

Удалось ли вам решить эту проблему?

Я запустил его на тестовом сайте, чтобы посмотреть, смогу ли я обнаружить ошибку, поскольку ваш код выглядел нормально, он отлично работает на моем сайте:

enter image description here

Единственное отличие, которое я сделал, было:

  • закомментируйте шаблоны
  • добавьте их в потоковый блок страницы вместо базовой настройки

Однако ни то, ни другое не должно иметь никакого значения.

Возможно, ссылка на строку содержит неверные данные, и ошибка возникает где-то еще в js-коде адаптера? Попробуйте вернуться к ванильной версии, которую вы опубликовали, и, если это сработает, создайте ее заново, чтобы посмотреть, что из этого получится.

Кстати, если вы хотите загрузить язык по умолчанию для своего сайта, вы можете использовать Locale.get_default(), но это нужно делать с помощью отложенного запроса, чтобы избежать вывода предупреждения "приложения не готовы" на консоль:

from wagtail.models import Locale
from django.utils.functional import lazy
get_default_language = lazy(lambda: Locale.get_default().language_code, str)

class TranslatableTextBlock(StructBlock):
    def __init__(self, default_text:str = None, default_language:str = None, local_blocks = None, search_index = True, **kwargs):        
        local_blocks = [
            (
                "language",
                ChoiceBlock(
                    choices=settings.LANGUAGES,
                    default=default_language or get_default_language,
                    help_text=_("Select the language for this text")
                )
            ),
            (
                "text",
                CharBlock(
                    default=default_text or None,
                    help_text=_("Enter your text in the selected language"),
                )
            )
        ]
        super().__init__(local_blocks, search_index, **kwargs)

Вот пример использования вашего TranslatableTextBlock в ListBlock для хранения нескольких языковых значений и тега шаблона для извлечения соответствующего значения. Я не знаю, в каком контексте вы используете этот параметр, но тег можно достаточно легко адаптировать к обычному коду, если у вас есть экземпляр значения ListBlock. тег вернет данные для языка по умолчанию, если в запрошенной локали их нет.

Я добавил некоторую проверку, чтобы убедиться, что языковой стандарт по умолчанию включен в блок списка перед сохранением, а также что ни один языковой стандарт не был повторен.

#translatable_text_block_tags.py

import logging
from django import template
from django.utils.translation import get_language
from wagtail.models import Locale

register = template.Library()

@register.simple_tag(takes_context=True)
def translated_text(context, field_name='text'):
    try:
        request = context.get("request")
        items = context.get('self').bound_blocks
        current_lang = getattr(request, "LANGUAGE_CODE", None) or get_language()
        # Try exact match
        match = next(
            (item for item in items if item.value.get("language", "") == current_lang),
            None
        )
        # If not found, try base language (e.g. "es" from "es-mx") if locale includes region
        if not match and "-" in current_lang:
            base_lang = current_lang.split("-")[0]
            match = next(
                (item for item in items if item.value.get("language", "") == base_lang),
                None
            )
        # If still not found, try default locale
        if not match:
            default_lang = Locale.get_default().language_code
            match = next(
                (item for item in items if item.value.get("language", "") == default_lang),
                None
            )
        return match.value.get(field_name, "") if match else ""
    except Exception as e:
        logging.error(
            f"{type(e).__name__} at line {e.__traceback__.tb_lineno} of {__file__}: {e}"
        )
    return ""

В шаблоне ListBlock вы бы назвали это чем-то вроде:

{% load translatable_text_block_tags %}
<p>{% translated_text %}</p>

Если вы хотите, чтобы элементы списка содержали больше, чем просто CharBlock, вы можете добавить столько блоков, сколько захотите, а затем вернуть значение по словарю обратно из тега. Ваш шаблон будет выглядеть примерно так:

{% load translatable_text_block_tags %}
{% translated_text as translated %}
{{ translated.text }} ... {{ translated.url }} ... {{ translated.other_stuff }}

Если все это предназначено только для перевода статического текста в шаблонах без использования PO-файлов, эта статья может оказаться полезной. С помощью этого метода вы создаете статические текстовые коллекции, переводите их с помощью wagtail-localize (или другого метода), затем вызываете тег шаблона, который выводит соответствующую переведенную коллекцию:

{% get_template_set 'social' as trans %}
<h5><strong>{{ trans.login_title }}<strong></h5>
<p>{{ trans.login_prompt }}.</p>
Вернуться на верх