Добавление дополнительной информации в каждое поле формы

Я использую Django/Wagtail и пакет wagtailstreamforms для создания форм. К сожалению, в конструкторе форм wagtailstreamforms нельзя добавить два поля рядом, поэтому я пытаюсь добавить функциональность, которая позволит пользователю ввести целое число от 1 до 12 (на основе столбцов Bootstrap - от col-1 до col-12) для каждого поля, и это число будет получено в шаблонах и будет использовано.

В файле wagtailstreamforms_fields.py я начал переопределять модель CharField, чтобы добавить дополнительное целое число width:

from django import forms
from wagtailstreamforms.fields import BaseField, register
from wagtail.core import blocks

class CustomCharField(forms.CharField):
    def __init__(self,*args, **kwargs):
        self.width = kwargs.pop('width')
        super().__init__(*args,**kwargs)

@register('singleline')
class Column_SingleLineTextField(BaseField):
    field_class = CustomCharField

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({'width': block_value.get('width')})
        return options


    def get_formfield(self, block_value):
        options = super().get_formfield(block_value)
        return options
        

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
            ('default_value', blocks.CharBlock(required=False)),
            ('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
        ], icon=self.icon, label=self.label)

Этот код добавляет дополнительный целочисленный ввод для поля singleline на странице конструктора форм wagtailstreamforms в настройках Wagtail.

Singleline width input

Теперь проблема, с которой я столкнулся, заключается в том, что я не могу получить параметр width в шаблонах.

Файл template/custom_form_block.html выглядит следующим образом:

<form{% if form.is_multipart %} enctype="multipart/form-data"{% endif %} class="row g-3 normal-form" action="{{ value.form_action }}" method="post" novalidate>
    {{ form.media }}
    {% csrf_token %}
    {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
    {% for field in form.visible_fields %}
        {% include 'partials/custom_form_field.html' %}
    {% endfor %}
    <div class="col-12">
        <button class="btn btn-primary">{{ value.form.submit_button_text }}</button>
    </div>
</form>

А templates/partials/custom_form_field.html выглядит следующим образом:

{% load form_tags %}

<div class="col-{{ field.col_width }}">
    {{ field.label_tag }}
    {{ field }}
    {{ field.width }}
    <p>{% dict field %}</p>
    {% if field.help_text %}<p class="help-text">{{ field.help_text }}</p>{% endif %}
    {{ field.errors }}
</div>

А тег шаблона в templatetags/form_tags.py содержит:

from django import template

register = template.Library()

@register.simple_tag
def dict(this_object):
    return this_object.__dict__

И, наконец, отрендеренная страница из браузера:

Broswer output

Как видите, тег шаблона dict возвращает все параметры формы, а параметр width не передается.

Как исправить это и позволить параметру width читаться в шаблонах?

Мое грубое, но эффективное решение

Поскольку я не смог переопределить модель CharField для добавления дополнительного параметра width, я решил, что будет проще использовать уже существующий параметр для передачи значения width в шаблоны. В этом подходе я буду использовать параметр attrs для передачи значения width в HTML рендеринг экземпляра каждого поля формы, затем я буду использовать jQuery для извлечения значения и добавления его в класс контейнера div.

Начиная с однострочного поля CharField, нам нужно переопределить поле, чтобы позволить пользователю вводить значение width, мой класс переопределения поля выглядит следующим образом:

@register('singleline')
class Column_SingleLineTextField(BaseField):
    field_class = CustomSinglelineField
    icon = "pilcrow"
    label = "Text field (single line)"

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({'width': block_value.get('width')})
        return options

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
            ('default_value', blocks.CharBlock(required=False)),
            ('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
        ], icon=self.icon, label=self.label)

Теперь нам нужно переопределить класс CharField, чтобы установить значение width в качестве одного из атрибутов в attrs:

class CustomSinglelineField(forms.CharField):
    def __init__(self,*args,**kwargs):
        self.width = kwargs.pop('width')
        self.widget = forms.widgets.TextInput(attrs={'col_width': self.width})
        super().__init__(*args,**kwargs)

Весь код (wagtailstreamforms_fields.py)

Я также добавил опцию placeholder к некоторым применимым полям, опцию атрибута rows для многострочного поля, а также опцию width:

Теперь шаблоны

templates/custom_form_block.html остается неизменным и выглядит следующим образом:

<form{% if form.is_multipart %} enctype="multipart/form-data"{% endif %} class="row g-3 normal-form" action="{{ value.form_action }}" method="post" novalidate>
    {{ form.media }}
    {% csrf_token %}
    {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
    {% for field in form.visible_fields %}
        {% include 'partials/custom_form_field.html' %}
    {% endfor %}
    <div class="col-12">
        <button class="btn btn-primary">{{ value.form.submit_button_text }}</button>
    </div>
</form>

А templates/partials/custom_form_field.html имеет все изменения и выглядит следующим образом:

<div class="field-column-id_{{ value.form.slug }}_{{ field.name }}">
    {{ field.label_tag }}
    {{ field }}
    {% if field.help_text %}<p class="help-text">{{ field.help_text }}</p>{% endif %}
    {{ field.errors }}
</div>

<script>
    var col_width = $(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("col_width")
    $(".field-column-id_{{ value.form.slug }}_{{ field.name }}").addClass("col-"+col_width);
    
    // Set the "None" placeholders as ""
    var placeholder = $(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("placeholder")
    if (placeholder == "None") {
        $(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("placeholder", "")
    }
</script>

Я устанавливаю родительский div для каждого поля как field-column-id_{{ value.form.slug }}_{{ field.name }}, таким образом, если у нас будет несколько форм с одноименными полями на одной странице, не будет никаких конфликтов, так как каждая форма будет иметь уникальный slug.

jQuery извлек HTML-атрибут col_width, который мы задали в качестве параметра width, добавит его к col- и установит в качестве класса родительского div.

По умолчанию, если мы не задаем значение в опции 'placeholder', wagtailstreamforms устанавливает эту опцию как 'None'. Мы не хотим, чтобы пустой placeholder отображался как 'None' в HTML, поэтому у нас есть сценарий jQuery, который заменяет None пустой строкой. В основном это работает, но эта функциональность ломается, когда пользователь хочет, чтобы заполнитель действительно был установлен на None.

Это решение по большей части работает, но, по моему честному мнению, я не очень горжусь им, так как считаю его неэффективным и халтурным.

Вернуться на верх