我粗略但有效的解决方案
由于我无法覆盖 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 时,此功能会中断。
这个解决方案在大多数情况下都有效,但老实说,我并不为此感到自豪,因为我认为这效率低下而且很老套。