Добавление дополнительной информации в каждое поле формы
Я использую 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.
Теперь проблема, с которой я столкнулся, заключается в том, что я не могу получить параметр 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__
И, наконец, отрендеренная страница из браузера:
Как видите, тег шаблона 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
.
Это решение по большей части работает, но, по моему честному мнению, я не очень горжусь им, так как считаю его неэффективным и халтурным.