Django 表单(Form)是处理用户输入、验证数据、生成 HTML 表单的核心组件,分为普通表单(Form/ModelForm)Admin 定制表单 两类。前者用于前端页面交互,后者用于后台管理系统的字段定制。以下是完整的定义方法、使用场景及 Admin 集成技巧。

一、核心概念

  1. Form:基础表单类,手动定义字段,适用于无模型关联的场景(如登录、搜索);
  2. ModelForm:模型关联表单,自动从 Model 生成字段,适用于模型数据的增删改查;
  3. Admin 表单:基于 ModelForm 定制 Admin 后台的表单展示、验证和交互。

二、普通表单的定义(Form/ModelForm)

1. 基础 Form(无模型关联)

适用于无需关联数据库模型的场景(如登录表单、联系表单)。

步骤 1:定义表单类(forms.py

在 Django App 下创建 forms.py,继承 django.forms.Form,手动定义字段及验证规则:

# blog/forms.py
from django import forms
from django.core.exceptions import ValidationError

# 登录表单(无模型关联)
class LoginForm(forms.Form):
    # 字段定义:字段类型 + 验证选项
    username = forms.CharField(
        label="用户名",
        max_length=50,
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}),
        error_messages={"required": "用户名不能为空"}
    )
    password = forms.CharField(
        label="密码",
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "请输入密码"}),
        error_messages={"required": "密码不能为空"}
    )

    # 自定义字段验证(方法名:clean_字段名)
    def clean_username(self):
        username = self.cleaned_data.get("username")
        if len(username) < 3:
            raise ValidationError("用户名长度不能少于3位")
        return username

    # 全局验证(多个字段联动验证)
    def clean(self):
        cleaned_data = super().clean()
        username = cleaned_data.get("username")
        password = cleaned_data.get("password")
        if username == "admin" and password == "123456":
            raise ValidationError("默认密码禁止使用,请修改密码后登录")
        return cleaned_data
步骤 2:视图中使用表单
# blog/views.py
from django.shortcuts import render, redirect
from .forms import LoginForm

def login(request):
    if request.method == "POST":
        # 绑定 POST 数据到表单
        form = LoginForm(request.POST)
        if form.is_valid():  # 验证数据(自动触发 clean_* 和 clean 方法)
            # 验证通过,获取清洗后的数据
            username = form.cleaned_data["username"]
            password = form.cleaned_data["password"]
            # 业务逻辑:验证用户、登录等
            return redirect("/index/")
    else:
        # GET 请求,初始化空表单
        form = LoginForm()
    return render(request, "login.html", {"form": form})
步骤 3:模板中渲染表单
<!-- templates/login.html -->
<form method="post">
    {% csrf_token %}
    <!-- 渲染所有字段(含错误提示) -->
    {{ form.as_p }}  <!-- as_p: 每个字段包裹在 <p> 标签中;也可用 as_table/as_ul -->
    
    <!-- 手动渲染单个字段(自定义布局) -->
    <div class="form-group">
        <label>{{ form.username.label }}</label>
        {{ form.username }}
        {% if form.username.errors %}
            <div class="text-danger">{{ form.username.errors.0 }}</div>
        {% endif %}
    </div>
    <div class="form-group">
        <label>{{ form.password.label }}</label>
        {{ form.password }}
        {% if form.password.errors %}
            <div class="text-danger">{{ form.password.errors.0 }}</div>
        {% endif %}
    </div>
    
    <!-- 全局错误提示 -->
    {% if form.non_field_errors %}
        <div class="text-danger">{{ form.non_field_errors.0 }}</div>
    {% endif %}
    
    <button type="submit" class="btn btn-primary">登录</button>
</form>

2. ModelForm(关联模型)

适用于与数据库模型联动的场景(如文章新增/编辑),自动从 Model 生成字段,减少重复代码。

步骤 1:定义 ModelForm 类
# blog/forms.py
from django import forms
from .models import Article

# 文章表单(关联 Article 模型)
class ArticleForm(forms.ModelForm):
    # 可选:新增模型外的字段(如确认密码)
    confirm_title = forms.CharField(label="确认标题", max_length=200)

    class Meta:
        # 关联的模型
        model = Article
        # 要包含的字段(__all__ 表示所有字段,或指定列表如 ["title", "content"])
        fields = ["title", "content", "author", "create_time"]
        # 排除的字段(与 fields 二选一)
        # exclude = ["update_time"]
        # 字段标签、小部件、错误信息定制
        labels = {
            "title": "文章标题",
            "content": "文章内容",
            "author": "作者",
        }
        widgets = {
            "title": forms.TextInput(attrs={"class": "form-control"}),
            "content": forms.Textarea(attrs={"class": "form-control", "rows": 10}),
            "create_time": forms.DateTimeInput(attrs={"type": "datetime-local", "class": "form-control"}),
        }
        error_messages = {
            "title": {"required": "标题不能为空", "max_length": "标题长度不能超过200位"},
        }

    # 自定义验证:确认标题与标题一致
    def clean(self):
        cleaned_data = super().clean()
        title = cleaned_data.get("title")
        confirm_title = cleaned_data.get("confirm_title")
        if title != confirm_title:
            raise ValidationError("两次输入的标题不一致")
        return cleaned_data

    # 重写 save 方法(可选,定制保存逻辑)
    def save(self, commit=True):
        instance = super().save(commit=False)
        # 自定义逻辑:比如自动补充作者
        if not instance.author:
            instance.author = "匿名用户"
        if commit:
            instance.save()
        return instance
步骤 2:视图中使用 ModelForm
# blog/views.py
from django.shortcuts import render, redirect, get_object_or_404
from .models import Article
from .forms import ArticleForm

def add_article(request):
    if request.method == "POST":
        form = ArticleForm(request.POST)
        if form.is_valid():
            # 保存到数据库(ModelForm 自带 save 方法)
            form.save()
            return redirect("/articles/")
    else:
        form = ArticleForm()
    return render(request, "add_article.html", {"form": form})

def edit_article(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if request.method == "POST":
        # 绑定实例 + POST 数据(编辑场景)
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()
            return redirect("/articles/")
    else:
        # 初始化表单时传入实例(显示现有数据)
        form = ArticleForm(instance=article)
    return render(request, "edit_article.html", {"form": form})

三、Admin 中的表单定制

Django Admin 默认自动生成模型表单,但可通过以下方式定制字段展示、验证、布局等。

1. 基础配置(注册模型到 Admin)

先在 admin.py 中注册模型,默认生成表单:

# blog/admin.py
from django.contrib import admin
from .models import Article

# 基础注册:使用默认表单
admin.site.register(Article)

2. 定制 Admin 表单(核心)

通过继承 admin.ModelAdmin,定制表单字段、验证、布局等,常用配置如下:

配置 1:定制显示字段/编辑字段
# blog/admin.py
from django.contrib import admin
from .models import Article

@admin.register(Article)  # 装饰器注册(替代 admin.site.register)
class ArticleAdmin(admin.ModelAdmin):
    # 列表页显示的字段
    list_display = ["title", "author", "create_time", "is_published"]
    # 列表页可编辑的字段(直接在列表页修改)
    list_editable = ["is_published"]
    # 详情页编辑的字段(控制表单显示的字段)
    fields = ["title", "content", "author", ("create_time", "update_time")]  # 元组表示一行显示多个字段
    # 排除的字段(不显示在表单中)
    # exclude = ["update_time"]
    # 字段分组(详情页分栏显示)
    fieldsets = (
        ("基础信息", {
            "fields": ("title", "author"),
            "classes": ("collapse",)  # 可折叠
        }),
        ("内容与时间", {
            "fields": ("content", "create_time", "update_time"),
            "description": "请填写文章内容,时间自动生成"  # 分组描述
        }),
    )
配置 2:自定义 Admin 表单类(ModelForm 集成)

若需更复杂的验证或字段定制,可自定义 ModelForm 并关联到 Admin:

# blog/admin.py
from django.contrib import admin
from django import forms
from .models import Article

# 自定义 Admin 表单(继承 ModelForm)
class ArticleAdminForm(forms.ModelForm):
    # 新增自定义字段
    read_count = forms.IntegerField(label="阅读量", min_value=0, initial=0)

    class Meta:
        model = Article
        fields = ["title", "content", "author", "create_time", "read_count"]

    # 自定义验证
    def clean_title(self):
        title = self.cleaned_data.get("title")
        if "敏感词" in title:
            raise forms.ValidationError("标题包含敏感词,请修改")
        return title

# 关联自定义表单到 Admin
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    form = ArticleAdminForm  # 指定自定义表单
    fieldsets = (
        ("基础信息", {"fields": ("title", "author", "read_count")}),
        ("内容", {"fields": ("content", "create_time")}),
    )
配置 3:Admin 表单的高级选项
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    # 搜索字段(列表页顶部搜索框)
    search_fields = ["title", "author", "content__contains"]
    # 过滤条件(列表页右侧过滤器)
    list_filter = ["author", "create_time__year", "is_published"]
    # 排序规则
    ordering = ["-create_time"]
    # 只读字段(详情页不可编辑)
    readonly_fields = ["update_time"]
    # 自动填充字段(如标题生成 slug)
    prepopulated_fields = {"slug": ("title",)}  # 输入标题时自动填充 slug 字段
    # 多对多字段的显示方式(默认多选框,改为弹窗选择)
    filter_horizontal = ["tags"]  # 适用于 ManyToManyField
    # 日期分层筛选(列表页顶部)
    date_hierarchy = "create_time"
配置 4:重写 Admin 保存逻辑
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        # request:当前请求对象;obj:模型实例;form:表单对象;change:是否为修改(True=修改,False=新增)
        if not change:  # 新增数据时
            obj.author = request.user.username  # 自动填充作者为当前登录用户
        obj.save()  # 保存实例

3. Admin 表单的常用扩展

扩展 1:自定义表单小部件(Widget)

修改 Admin 表单字段的 HTML 渲染方式:

from django.forms import widgets

class ArticleAdminForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = "__all__"
        widgets = {
            "content": widgets.Textarea(attrs={"rows": 20, "cols": 80}),  # 文本域大小
            "create_time": widgets.DateTimeInput(attrs={"type": "datetime-local"}),  # 时间选择器
        }

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    form = ArticleAdminForm
扩展 2:Admin 内联表单(Inline)

用于关联模型的联动编辑(如一对多关系:文章-评论):

# 定义评论模型
class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="comments")
    content = models.TextField()
    create_time = models.DateTimeField(auto_now_add=True)

# 定义内联表单
class CommentInline(admin.TabularInline):  # TabularInline:表格形式;StackedInline:堆叠形式
    model = Comment
    extra = 1  # 默认显示1个空白表单
    max_num = 5  # 最多显示5个表单
    fields = ["content", "create_time"]
    readonly_fields = ["create_time"]

# 关联到文章 Admin
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    inlines = [CommentInline]  # 文章详情页可编辑评论

四、核心区别与最佳实践

类型适用场景定义方式核心配置
普通 Form无模型关联(登录/搜索)继承 forms.Form,手动定义字段widgetsclean_*error_messages
ModelForm模型关联(增删改查)继承 forms.ModelForm,关联 ModelMeta.model/fieldssave()
Admin 表单后台管理系统继承 admin.ModelAdmin,关联 ModelFormfieldsetslist_displayinlines

最佳实践:

  1. 字段验证:普通表单和 ModelForm 的验证逻辑写在 clean_*clean 方法中,Admin 表单复用 ModelForm 的验证;
  2. Admin 性能list_display 避免关联字段过多(如外键嵌套),list_filter 慎用高基数字段(如时间戳);
  3. 安全性:Admin 中敏感字段(如密码)设置 readonly_fieldsexclude,避免误修改;
  4. 复用性:自定义 ModelForm 可同时用于前端页面和 Admin 后台,减少重复代码。

五、常见问题

  1. Admin 表单不显示自定义字段:检查 fields/fieldsets 是否包含该字段,或是否设置 exclude 排除;
  2. ModelForm 保存时报错:确保 save() 方法中 commit=True,或手动调用 instance.save()
  3. Admin 内联表单不显示:确认关联模型的 related_name 正确,且外键关联方向正确;
  4. 表单验证不生效:验证方法名需严格遵循 clean_字段名,且需调用 super().clean()

通过以上配置,可灵活定制 Django 表单的展示、验证和交互,无论是前端普通表单还是后台 Admin 表单,都能满足复杂的业务需求。
(来自豆包)

(以下deepseek)
在 Django Admin 中自定义表单有以下几种方式:

1. ModelForm 基础方式

# forms.py
from django import forms
from .models import YourModel

class YourModelForm(forms.ModelForm):
    # 添加额外字段(不会保存到数据库)
    extra_field = forms.CharField(required=False)
    
    # 自定义字段验证
    def clean_name(self):
        name = self.cleaned_data['name']
        if 'test' in name:
            raise forms.ValidationError("名称不能包含 'test'")
        return name
    
    class Meta:
        model = YourModel
        fields = '__all__'
        widgets = {
            'description': forms.Textarea(attrs={'rows': 4}),
            'date': forms.DateInput(attrs={'type': 'date'}),
        }
        labels = {
            'name': '自定义标签',
        }
        help_texts = {
            'name': '请输入完整的名称',
        }

2. 在 Admin 中使用自定义表单

# admin.py
from django.contrib import admin
from .models import YourModel
from .forms import YourModelForm

@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
    # 指定自定义表单
    form = YourModelForm
    
    # 配置表单字段显示
    fields = ['name', 'description', 'extra_field', 'date']
    
    # 或者使用 fieldsets 进行分组
    fieldsets = (
        ('基本信息', {
            'fields': ('name', 'description'),
            'description': '请填写基本信息'
        }),
        ('其他信息', {
            'fields': ('extra_field', 'date'),
            'classes': ('collapse',),  # 可折叠
        }),
    )

3. 覆盖表单字段属性

# admin.py
class YourModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        
        # 动态修改字段属性
        form.base_fields['name'].label = '产品名称'
        form.base_fields['name'].help_text = '最多50个字符'
        form.base_fields['name'].widget.attrs.update({
            'class': 'special',
            'placeholder': '请输入名称'
        })
        
        # 根据对象状态动态设置
        if obj and obj.is_published:
            form.base_fields['name'].disabled = True
            
        return form

4. 内联表单 (Inline Forms)

# admin.py
from django.contrib import admin
from .models import ParentModel, ChildModel

class ChildModelInline(admin.TabularInline):  # 或 admin.StackedInline
    model = ChildModel
    form = YourChildModelForm  # 可选:自定义内联表单
    extra = 1  # 显示的空表单数量
    max_num = 5  # 最大数量限制
    
    # 内联表单字段
    fields = ['name', 'quantity']
    readonly_fields = ['created_at']

@admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
    inlines = [ChildModelInline]

5. 表单集 (FormSet) 自定义

# admin.py
from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()
        # 自定义表单集验证
        total_quantity = sum(
            form.cleaned_data.get('quantity', 0) 
            for form in self.forms
            if not form.cleaned_data.get('DELETE', False)
        )
        if total_quantity > 100:
            raise forms.ValidationError("总数量不能超过100")

class ChildModelInline(admin.TabularInline):
    formset = CustomInlineFormSet
    # ... 其他配置

6. 完整的例子

# models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    in_stock = models.BooleanField(default=True)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

# forms.py
from django import forms
from .models import Product

class ProductAdminForm(forms.ModelForm):
    discount_price = forms.DecimalField(
        label='折扣价格',
        required=False,
        max_digits=10,
        decimal_places=2,
        help_text='如果填写,将覆盖原价'
    )
    
    class Meta:
        model = Product
        fields = '__all__'
        widgets = {
            'description': forms.Textarea(attrs={
                'rows': 3,
                'cols': 80,
                'class': 'my-textarea'
            }),
        }
    
    def save(self, commit=True):
        # 在保存前处理额外逻辑
        instance = super().save(commit=False)
        discount_price = self.cleaned_data.get('discount_price')
        if discount_price:
            instance.price = discount_price
        if commit:
            instance.save()
        return instance

# admin.py
from django.contrib import admin
from .models import Product, Category
from .forms import ProductAdminForm

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ['name', 'product_count']
    
    def product_count(self, obj):
        return obj.product_set.count()

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm
    
    # 列表页显示
    list_display = ['name', 'category', 'price', 'in_stock', 'created_at']
    list_filter = ['category', 'in_stock', 'created_at']
    search_fields = ['name', 'description']
    list_editable = ['price', 'in_stock']
    
    # 表单页配置
    fieldsets = (
        ('产品信息', {
            'fields': ('name', 'category', 'description'),
            'classes': ('wide',),
        }),
        ('价格和库存', {
            'fields': ('price', 'discount_price', 'in_stock'),
            'description': '价格信息设置',
        }),
    )
    
    # 只读字段
    readonly_fields = ['created_at']
    
    # 自动完成
    autocomplete_fields = ['category']
    
    # 保存按钮配置
    save_on_top = True
    save_as = True
    
    # 权限控制
    def has_delete_permission(self, request, obj=None):
        if obj and obj.price > 1000:
            return request.user.is_superuser
        return super().has_delete_permission(request, obj)

7. 使用装饰器注册

# admin.py
from django.contrib import admin

@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
    form = YourModelForm
    
    # 动态表单字段
    def get_fields(self, request, obj=None):
        if request.user.is_superuser:
            return ['field1', 'field2', 'secret_field']
        return ['field1', 'field2']
    
    # 保存时处理
    def save_model(self, request, obj, form, change):
        if not change:  # 如果是新建
            obj.created_by = request.user
        obj.updated_by = request.user
        super().save_model(request, obj, form, change)

主要特点:

  1. ModelForm 继承:自定义表单通常继承自 forms.ModelForm
  2. Meta 类配置:指定模型、字段、控件等
  3. 表单验证:通过 clean_<fieldname>() 方法添加字段级验证
  4. Admin 集成:在 admin.ModelAdmin 中通过 form 属性指定
  5. 字段分组:使用 fieldsets 实现更好的表单布局
  6. 动态表单:通过 get_form() 方法动态修改表单行为

这些方法可以根据实际需求组合使用,创建出功能强大且用户友好的 Django Admin 表单界面。

标签: none