You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Django ModelForm生成的表单添加自定义CSS样式?

Django ModelForm 样式定制方案

问题

用ModelForm生成的原生表单无法给每个字段添加带类名的div块,无法应用自定义CSS样式,想找合适的工具或方法来定制表单模板(已有CSS文件和目标HTML结构)。

现有代码

model.py

from django.db import models
from datetime import date

class Product(models.Model):
    name = models.CharField(max_length=40)
    
    def __str__(self):
        return self.name

class Variety(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name

class Tender(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    variety = models.ForeignKey(Variety, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=4, decimal_places=2,)
    delivery_date = models.DateField(default=date.today)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    delivery_basis = models.CharField(max_length=3, choices=[('CPT', 'CPT')])
    payments = models.CharField(max_length=20, choices=[('Наличный расчет', 'Наличный расчет')])
    comment = models.TextField(max_length=4000, blank=True, default='Комментарий отсутствует.')

    def __str__(self):
        return self.product.name

forms.py

from django import forms
from .models import Tender

class AddTenderForm(forms.ModelForm):
    class Meta:
        model = Tender
        fields = '__all__'

views.py

from django.shortcuts import render, redirect
from .forms import AddTenderForm

def index(request):
    form = AddTenderForm()
    if request.method == 'POST':
        form = AddTenderForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('add')
    return render(request, 'fmtender/form_for_adding_a_tender.html', {'form': form})

原模板(form_for_adding_a_tender.html)

<form class="needs-validation"
      id="AddTender"
      data-variety-url="{% url 'ajax_load_variety' %}"
      novalidate>
    {% csrf_token %}
    {{ form.as_p }}
    <button class="w-100 btn btn-primary btn-lg"
            type="submit">Continue to checkout</button>
</form>

效果说明

  • 当前效果:表单字段仅用<p>标签包裹,无自定义类名,无法匹配CSS样式
  • 期望效果:每个字段被带自定义类名的<div>包裹,结构符合目标HTML模板

解决方案

1. 手动逐个渲染字段(完全可控)

直接在模板中手动输出每个字段,包裹需要的HTML结构和类名,适合需要精确控制每个字段布局的场景。

修改后的模板:

<form class="needs-validation"
      id="AddTender"
      data-variety-url="{% url 'ajax_load_variety' %}"
      novalidate>
    {% csrf_token %}
    <!-- 产品字段 -->
    <div class="form-group">
        {{ form.product.label_tag }}
        {{ form.product }}
        {% if form.product.errors %}
            <div class="text-danger">{{ form.product.errors }}</div>
        {% endif %}
    </div>
    <!-- 品种字段 -->
    <div class="form-group">
        {{ form.variety.label_tag }}
        {{ form.variety }}
        {% if form.variety.errors %}
            <div class="text-danger">{{ form.variety.errors }}</div>
        {% endif %}
    </div>
    <!-- 价格字段 -->
    <div class="form-group">
        {{ form.price.label_tag }}
        {{ form.price }}
        {% if form.price.errors %}
            <div class="text-danger">{{ form.price.errors }}</div>
        {% endif %}
    </div>
    <!-- 重复上述结构处理剩余字段 -->
    <div class="form-group">
        {{ form.delivery_date.label_tag }}
        {{ form.delivery_date }}
        {% if form.delivery_date.errors %}
            <div class="text-danger">{{ form.delivery_date.errors }}</div>
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.category.label_tag }}
        {{ form.category }}
        {% if form.category.errors %}
            <div class="text-danger">{{ form.category.errors }}</div>
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.delivery_basis.label_tag }}
        {{ form.delivery_basis }}
        {% if form.delivery_basis.errors %}
            <div class="text-danger">{{ form.delivery_basis.errors }}</div>
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.payments.label_tag }}
        {{ form.payments }}
        {% if form.payments.errors %}
            <div class="text-danger">{{ form.payments.errors }}</div>
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.comment.label_tag }}
        {{ form.comment }}
        {% if form.comment.errors %}
            <div class="text-danger">{{ form.comment.errors }}</div>
        {% endif %}
    </div>

    <button class="w-100 btn btn-primary btn-lg"
            type="submit">Continue to checkout</button>
</form>

2. 给Form的Widget添加属性(批量处理)

在Form类中统一给每个字段的widget指定class,再通过模板循环渲染字段,减少重复代码。

修改forms.py:

from django import forms
from .models import Tender

class AddTenderForm(forms.ModelForm):
    class Meta:
        model = Tender
        fields = '__all__'
        widgets = {
            'product': forms.Select(attrs={'class': 'form-control'}),
            'variety': forms.Select(attrs={'class': 'form-control'}),
            'price': forms.NumberInput(attrs={'class': 'form-control'}),
            'delivery_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
            'category': forms.Select(attrs={'class': 'form-control'}),
            'delivery_basis': forms.Select(attrs={'class': 'form-control'}),
            'payments': forms.Select(attrs={'class': 'form-control'}),
            'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
        }

修改后的模板:

<form class="needs-validation"
      id="AddTender"
      data-variety-url="{% url 'ajax_load_variety' %}"
      novalidate>
    {% csrf_token %}
    {% for field in form %}
        <div class="form-group">
            {{ field.label_tag }}
            {{ field }}
            {% if field.errors %}
                <div class="text-danger">{{ field.errors }}</div>
            {% endif %}
        </div>
    {% endfor %}
    <button class="w-100 btn btn-primary btn-lg"
            type="submit">Continue to checkout</button>
</form>

3. 使用django-crispy-forms库(快速适配CSS框架)

这是Django生态中常用的表单美化工具,支持Bootstrap、Tailwind等主流CSS框架,能自动生成符合规范的带类名的HTML结构,适合快速实现目标效果。

步骤1:安装库

pip install django-crispy-forms

步骤2:配置settings.py

INSTALLED_APPS = [
    # 其他已安装应用
    'crispy_forms',
    'crispy_bootstrap4',  # 如果你用Bootstrap4,根据你的CSS框架选择对应包
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'  # 指定使用的CSS框架模板

步骤3:修改模板

{% load crispy_forms_tags %}
<form class="needs-validation"
      id="AddTender"
      data-variety-url="{% url 'ajax_load_variety' %}"
      novalidate>
    {% csrf_token %}
    {{ form|crispy }}  <!-- 使用crispy过滤器渲染表单 -->
    <button class="w-100 btn btn-primary btn-lg"
            type="submit">Continue to checkout</button>
</form>

4. 自定义表单渲染模板(复用性高)

创建通用的表单渲染模板片段,然后在需要的地方引入,适合多个表单需要统一样式的场景。

步骤1:创建模板片段(如templates/form_snippet.html)

{% for field in form %}
    <div class="form-group">
        {{ field.label_tag }}
        {{ field }}
        {% if field.errors %}
            <div class="invalid-feedback">{{ field.errors }}</div>
        {% endif %}
    </div>
{% endfor %}

步骤2:在主模板中引入

<form class="needs-validation"
      id="AddTender"
      data-variety-url="{% url 'ajax_load_variety' %}"
      novalidate>
    {% csrf_token %}
    {% include 'form_snippet.html' %}
    <button class="w-100 btn btn-primary btn-lg"
            type="submit">Continue to checkout</button>
</form>

内容的提问来源于stack exchange,提问作者DmitriPavlyuchenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:44:57