【问题标题】:Adding additional information into each form field在每个表单域中添加附加信息
【发布时间】:2021-10-24 16:38:24
【问题描述】:

我正在使用 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)

此代码在 Wagtail 设置的 wagtailstreamforms 表单构建器页面中为 singleline 字段添加额外的整数输入。

现在我面临的问题是,我无法在模板中检索 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 参数?

【问题讨论】:

    标签: python django forms wagtail form-fields


    【解决方案1】:

    我粗略但有效的解决方案

    由于我无法覆盖 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 选项:

    from django import forms
    from wagtailstreamforms.fields import BaseField, register
    from wagtail.core import blocks
    
    class CustomSinglelineField(forms.CharField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.placeholder = kwargs.pop('placeholder')
            self.widget = forms.widgets.TextInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
            super().__init__(*args,**kwargs)
    
    class CustomMultilineField(forms.CharField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.rows = kwargs.pop('rows')
            self.placeholder = kwargs.pop('placeholder')
            self.widget = forms.widgets.Textarea(attrs={'col_width': self.width, 'rows': self.rows, 'placeholder': self.placeholder})
            super().__init__(*args,**kwargs)
    
    class CustomDateField(forms.DateField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.DateInput(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomDatetimeField(forms.DateField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.DateTimeInput(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomEmailField(forms.EmailField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.placeholder = kwargs.pop('placeholder')
            self.widget = forms.widgets.EmailInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
            super().__init__(*args,**kwargs)
    
    class CustomURLField(forms.URLField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.placeholder = kwargs.pop('placeholder')
            self.widget = forms.widgets.URLInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
            super().__init__(*args,**kwargs)
    
    class CustomNumberField(forms.DecimalField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.placeholder = kwargs.pop('placeholder')
            self.widget = forms.widgets.URLInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
            super().__init__(*args,**kwargs)
    
    class CustomDropdownField(forms.ChoiceField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.Select(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomRadioField(forms.ChoiceField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.RadioSelect(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomCheckboxesField(forms.MultipleChoiceField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.SelectMultiple(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomCheckboxField(forms.BooleanField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.CheckboxInput(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomSinglefileField(forms.FileField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.ClearableFileInput(attrs={'col_width': self.width})
            super().__init__(*args,**kwargs)
    
    class CustomMultifileField(forms.FileField):
        def __init__(self,*args,**kwargs):
            self.width = kwargs.pop('width')
            self.widget = forms.widgets.ClearableFileInput(attrs={'col_width': self.width,"multiple": True})
            super().__init__(*args,**kwargs)
    
    
    @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({'placeholder': block_value.get('placeholder')})
            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)),
                ('placeholder', 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)
    
    @register('multiline')
    class MultiLineTextField(BaseField):
        field_class = CustomMultilineField
        icon = "form"
        label = "Text field (multiple lines)"
    
        def get_options(self, block_value):
            options = super().get_options(block_value)
            options.update({'width': block_value.get('width')})
            options.update({'rows': block_value.get('rows')})
            options.update({'placeholder': block_value.get('placeholder')})
            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)),
                ('placeholder', 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)),
                ('rows', blocks.IntegerBlock(help_text="Height of field", max_value=100, min_value=1, default=10, required=True)),
            ], icon=self.icon, label=self.label)
    
    @register('date')
    class DateField(BaseField):
        field_class = CustomDateField
        icon = "date"
        label = "Date field"
    
        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)
    
    @register('datetime')
    class DateTimeField(BaseField):
        field_class = CustomDatetimeField
        icon = "time"
        label = "Time field"
    
        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)
    
    @register('email')
    class EmailField(BaseField):
        field_class = CustomEmailField
        icon = "mail"
        label = "Email field"
    
        def get_options(self, block_value):
            options = super().get_options(block_value)
            options.update({'width': block_value.get('width')})
            options.update({'placeholder': block_value.get('placeholder')})
            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)),
                ('placeholder', 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)
    
    @register('url')
    class URLField(BaseField):
        field_class = CustomURLField
        icon = "link"
        label = "URL field"
    
        def get_options(self, block_value):
            options = super().get_options(block_value)
            options.update({'width': block_value.get('width')})
            options.update({'placeholder': block_value.get('placeholder')})
            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)),
                ('placeholder', 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)
    
    @register('number')
    class NumberField(BaseField):
        field_class = forms.DecimalField
        label = "Number field"
    
        def get_options(self, block_value):
            options = super().get_options(block_value)
            options.update({'width': block_value.get('width')})
            options.update({'placeholder': block_value.get('placeholder')})
            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)),
                ('placeholder', 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)
    
    @register('dropdown')
    class DropdownField(BaseField):
        field_class = CustomDropdownField
        icon = "arrow-down-big"
        label = "Dropdown field"
    
        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)),
                    ("empty_label", blocks.CharBlock(required=False)),
                    ("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
                    ('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,
            )
    
    @register('radio')
    class RadioField(BaseField):
        field_class = CustomRadioField
        icon = "radio-empty"
        label = "Radio buttons"
    
        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)),
                    ("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
                    ('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,
            )
    
    @register('checkboxes')
    class CheckboxesField(BaseField):
        field_class = CustomCheckboxesField
        icon = "tick-inverse"
        label = "Checkboxes"
    
        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)),
                    ("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
                    ('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,
            )
    
    @register('checkbox')
    class CheckboxField(BaseField):
        field_class = forms.BooleanField
        icon = "tick-inverse"
        label = "Checkbox field"
    
        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)),
                    ('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,
            )
    
    @register('singlefile')
    class SingleFileField(BaseField):
        field_class = CustomSinglefileField
        icon = "doc-full-inverse"
        label = "File field"
    
        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)),
                    ('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,
            )
    
    @register('multifile')
    class MultiFileField(BaseField):
        field_class = forms.FileField
        icon = "doc-full-inverse"
        label = "Files field"
    
        def get_form_block(self):
            return blocks.StructBlock(
                [
                    ("label", blocks.CharBlock()),
                    ("help_text", blocks.CharBlock(required=False)),
                    ("required", blocks.BooleanBlock(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,
            )
    

    现在是模板

    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 检索了我们设置为width 选项的col_width HTML 属性,并将其附加到col- 并将其设置为父div 的类。

    默认情况下,如果我们没有在“占位符”选项中设置值,wagtailstreamforms 会将此选项设置为“无”。我们不希望空占位符在 HTML 中显示为“无”,因此我们有一个 jQuery 脚本,用空字符串替换 None。这在大多数情况下都可以使用,但是当用户希望将占位符实际设置为 None 时,此功能会中断。

    这个解决方案在大多数情况下都有效,但老实说,我并不为此感到自豪,因为我认为这效率低下而且很老套。

    【讨论】:

    • 看来我们正试图同时解决这个问题。我昨天问过这个问题(stackoverflow.com/questions/68917646/…),它适用于常规的 wagtail contrib 表单,尽管我也想使用 wagtailstreamforms。我可以看到您在 wagtailstreamforms git repo 上打开了一个问题,希望有人能以更强大的解决方案回复您。考虑到它的实用性和简单性,这个功能不存在似乎很奇怪。无论如何,谢谢你写这篇文章
    • 啊,我猜你已经遇到了我打开的问题 (#180) 并且遇到了问题,谢谢 :) - 让我们看看我打开的 git 问题会发生什么,在那之前我将使用我的attrs 解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 2020-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多