添加 class 到表单字段 Django ModelForm

Add class to form field Django ModelForm

我正在尝试使用 Django ModelForm 编写 Bootstrap 表单。我已经阅读了 Django 文档 Django Documentation about Forms,所以我有以下代码:

<div class="form-group">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}</div>

{{form.subject}} 由 Django 渲染,例如在 CharField 字段模型中,作为 input 标签,

<input type="text"....> etc.

我需要将 "form-control" class 添加到每个输入以获得 Bootstrap 输入外观(没有第三方包)。我找到了这个解决方案 Django add class to form <input ..> field。有什么方法可以在默认情况下将 class 添加到每个字段,而无需在表单 class 的 class 的每个属性中指定它?

class ExampleForm(forms.Form):
   name = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   email = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   address = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   country = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))

等等..

一种方法是创建基本表单 class 并在 __init__ 方法中手动更新字段的属性。

另一种方法是使用像这样的现有库: https://github.com/dyve/django-bootstrap3

github 周围有很多这样的图书馆。环顾四周。

如果您不能使用第三方应用程序并希望以 DRY 方式向表单中的每个字段添加 class(例如“表单控件”),您可以这样做以 class __init__() 形式的方法如下:

class ExampleForm(forms.Form):
    # Your declared form fields here
    ...

    def __init__(self, *args, **kwargs):
        super(ExampleForm, self).__init__(*args, **kwargs)
        for visible in self.visible_fields():
            visible.field.widget.attrs['class'] = 'form-control'

您可能还需要处理对属性中现有 classes 的检查,如果出于某种原因您将以声明方式添加 classes __init__() 内。上面的代码没有考虑到这种情况。

值得一提:

您指定不想使用第三方包。但是,我会花一点时间来提一下,自动使表单以 Bootstrap 样式呈现的最简单方法之一是使用 django-crispy-forms,如下所示:

# settings.py
CRISPY_TEMPLATE_PACK = 'bootstrap3'

# forms.py
from crispy_forms.helper import FormHelper
class ExampleForm(forms.Form):
    # Your declared form fields here
    ...
    helper = FormHelper()

# In your template, this renders the form Bootstrap-style:
{% load crispy_forms_tags %}
{% crispy form %}

您可以在forms.py

中添加CSS类
subject = forms.CharField(label='subject', max_length=100 , widget=forms.TextInput(attrs={'class': "form-control"}))

松脆的表格是必经之路。 Bootstrap 的提示 4. 添加到@Christian Abbott 的回答,对于 forms ,bootstrap 说,使用 form-group 和 form-control 。 这就是它对我有用的方式。

我的forms.py

class BlogPostForm(forms.ModelForm):
    class Meta:
        model = models.Post
        fields = ['title', 'text', 'tags', 'author', 'slug']
    helper = FormHelper()
    helper.form_class = 'form-group'
    helper.layout = Layout(
        Field('title', css_class='form-control mt-2 mb-3'),
        Field('text', rows="3", css_class='form-control mb-3'),
        Field('author', css_class='form-control mb-3'),
        Field('tags', css_class='form-control mb-3'),
        Field('slug', css_class='form-control'),
    )

我的post_create.html

{% extends 'blog/new_blog_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">

<form method='POST' enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.media }}
    {% crispy form %}

<hr>
<input type="submit" name="Save" value="Save" class='btn btn-primary'> <a href="{% url 'home' %}" class='btn btn-danger'>Cancel</a>
</form>

</div>
{% endblock %}

注意:如果您在模型字段中使用 CK Editor RichTextField(),则该字段不会受到影响。如果有人知道,请更新此内容。

我知道作者问过 Bootstrap 用于自己的表单,但是还有一种方法可以在 Django 表单中包含 Bootstrap class 标记以进行身份​​验证、密码重置等。

如果我们创建标准格式的模板:

<form action="" method="post">
   {% csrf_token %}
   {{ form }}
</form>

然后在浏览器源代码中我们可以看到所有带有标签的表单域:

<form action="" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="xxx">
    <tr><th><label for="id_old_password">Old password:</label></th><td><input type="password" name="old_password" autofocus required id="id_old_password"></td></tr>
    <tr><th><label for="id_new_password1">New password:</label></th><td><input type="password" name="new_password1" required id="id_new_password1"></td></tr>
    <tr><th><label for="id_new_password2">New password confirmation:</label></th><td><input type="password" name="new_password2" required id="id_new_password2"></td></tr>
</form>

我们模板中的变量 {{ form }} 现在可以用此代码替换 Bootstrap class 我们需要的是:

<div class="fieldWrapper form-group" aria-required="true">
    <label for="id_old_password">Old password:</label><span class="required">*</span>
    <input type="password" **class="form-control"** name="old_password" autofocus required id="id_old_password">
</div>

也许它对重新设计内置静态表单很有用。

因为我花了比我(django 新手)更多的时间来解决这个问题,所以我也会把我的结果放在这里。

为每个字段设置小部件只是为了一遍又一遍地添加一个 class 违反了重复的编程规则,并导致许多不必要的行。使用 bootstrap 表单时尤其会发生这种情况。

这是我的(工作)示例,不仅添加 bootstrap classes:

forms.py

class CompanyForm(forms.Form):
    name = forms.CharField(label='Jméno')
    shortcut = forms.CharField(label='Zkratka')
    webpage = forms.URLField(label='Webové stránky')
    logo = forms.FileField(label='Logo')

templatetags/custom_tags.py

from django import template
from django.urls import reverse

register = template.Library()

@register.filter('input_type')
def input_type(ob):
    '''
    Extract form field type
    :param ob: form field
    :return: string of form field widget type
    '''
    return ob.field.widget.__class__.__name__


@register.filter(name='add_classes')
def add_classes(value, arg):
    '''
    Add provided classes to form field
    :param value: form field
    :param arg: string of classes seperated by ' '
    :return: edited field
    '''
    css_classes = value.field.widget.attrs.get('class', '')
    # check if class is set or empty and split its content to list (or init list)
    if css_classes:
        css_classes = css_classes.split(' ')
    else:
        css_classes = []
    # prepare new classes to list
    args = arg.split(' ')
    for a in args:
        if a not in css_classes:
            css_classes.append(a)
    # join back to single string
    return value.as_widget(attrs={'class': ' '.join(css_classes)})

reusable_form_fields.html(模板)

{% load custom_tags %}

{% csrf_token %}
{% for field in form %}
    <div class="form-group row">
        {% if field|input_type == 'TextInput' %}
            <div for="{{ field.label }}" class="col-sm-2 col-form-label">
                {{ field.label_tag }}
            </div>
            <div class="col-sm-10">
                {{ field|add_classes:'form-control'}}
                {% if field.help_text %}
                    <small class="form-text text-muted">{{ field.help_text }}</small>
                {% endif %}
            </div>
        {% else %}
            ...
        {% endif %}
    </div>
{% endfor %}

我发现通过 css 识别元素并在其中添加样式更容易。使用 Django 表单,您可以为每个表单字段获得一个唯一的 ID(如果您在模板中多次显示表单,则为用户表单前缀)。

# views.py
def my_view_function(request):
    form_a = MyForm(prefix="a")
    form_b = MyForm(prefix="b")
    context = {
        "form_a": form_a,
        "form_b": form_b
    }
    return render(request, "template/file.html", context)

风格

// file.css
form input#by_id {
  width: 100%;
}

好的,一段时间过去了,但我遇到了同样的问题。我来到这个解决方案:

class FormCssAttrsMixin():
    cssAttrs = {}

    def inject_css_attrs(self):
        # iterate through fields
        for field in self.fields:
            widget = self.fields[field].widget
            widgetClassName = widget.__class__.__name__

            # found widget which should be manipulated?
            if widgetClassName in self.cssAttrs.keys():
                # inject attributes
                attrs = self.cssAttrs[widgetClassName]
                for attr in attrs:
                    if attr in widget.attrs:  # attribute already existing
                        widget.attrs.update[attr] = widget[attr] + " " +    attrs[attr]  # append
                    else:  # create attribute since its not existing yet
                        widget.attrs[attr] = attrs[attr]


class MyForm(FormCssAttrsMixin, forms.Form):
    # add class attribute to all django textinputs widgets
    cssAttrs = {"TextInput": {"class": "form-control"}}

    name = forms.CharField()
    email = forms.CharField()
    address = forms.CharField()
    country = forms.CharField()

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.inject_css_attrs()

使用此 Mixin class,您可以以通用方式操作表单小部件的属性。只需添加一个字典作为 class 变量,其中包含每个小部件所需的属性和值。 这样您就可以在定义字段的同一位置添加 css classes。唯一的缺点是,您必须在某处调用“inject_css_attrs”方法,但我认为这没问题。

这是对 的回答。

如果您使用大量表单,不必每次都重写 init 的选项可能是创建您自己的表单 class:

class MyBaseForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for visible in self.visible_fields():
            visible.field.widget.attrs['class'] = 'form-control'

然后你可以继承这个class它会自动为你制作样式。

class ExampleForm(MyBaseForm):
    # Your declared form fields here
    ...

通过简单地创建一个继承自 ModelForm 的 MyBaseModelForm,ModelForm 也可以完成同样的事情。

您还可以明确提及要将 class 应用到

的字段

class ProfileForm(ModelForm):

   class Meta:
       model = Profile 
        fields = ['avatar','company']  
    
    
        def __init__(self, *args, **kwargs):
           super().__init__(*args, **kwargs)
           self.fields['avatar'].widget.attrs.update({'class': 'form-control'})
           self.fields['company'].widget.attrs.update({'class':'form-control'})

这个很实用:

class CreateSomethingForm(forms.ModelForm):

    class Meta:
        model = Something
        exclude = []

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

这样就不用一个一个地跑了。

@christian-abbott 响应的通用版本:

class ExampleForm(forms.Form):
    
    _HTML_CLASSES = ('form-control', 'something-else')
    
    # Your declared form fields here
    ...

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for visible in self.visible_fields():
            missing_classes = list(self._HTML_CLASSES)
            if 'class' in visible.field.widget.attrs:
                current_classes = visible.field.widget.attrs['class'].split(' ')
                for current_class in current_classes:
                    if current_class in missing_classes:
                        missing_classes.remove(current_class)
            else:
                current_classes = []
            visible.field.widget.attrs['class'] = ' '.join(current_classes + missing_classes)