Django 是一个高级 Python Web 框架,其内置的模型与表单系统极大地简化了数据库操作与用户输入处理。本文将全面介绍 Django 4 中模型(Model)与表单(Form)的核心概念、使用方法及最佳实践,帮助您构建健壮、可维护的 Web 应用。
虽然现在已经有很多项目切换到了vue等前后端分离,但是小型项目直接使用django表单还是高效。

目录

  1. Django 模型
    • 1.1 模型定义与字段类型
    • 1.2 关系字段
    • 1.3 Meta 选项
    • 1.4 模型方法
    • 1.5 模型管理器与查询集
  2. Django 表单
    • 2.1 表单类与字段
    • 2.2 表单验证
    • 2.3 表单渲染
    • 2.4 处理表单数据
  3. 模型表单(ModelForm)
    • 3.1 基础使用
    • 3.2 字段定制
    • 3.3 保存逻辑
    • 3.4 高级用法
  4. 表单集(Formsets)
    • 4.1 基础表单集
    • 4.2 模型表单集
  5. 文件上传处理
  6. 自定义验证与清理
  7. 表单与模型的关系
  8. 性能与安全最佳实践
  9. 总结

1. Django 模型

模型是 Django 中与数据库交互的核心,它定义了一个 Python 类,映射为数据库中的一张表。

1.1 模型定义与字段类型

每个模型继承自 django.db.models.Model,其属性对应数据库字段。Django 提供了丰富的字段类型,覆盖常见需求:

from django.db import models

class Article(models.Model):
    # 基本字段
    title = models.CharField(max_length=200, verbose_name="标题")
    content = models.TextField(verbose_name="内容")
    pub_date = models.DateTimeField(auto_now_add=True, verbose_name="发布日期")
    update_date = models.DateTimeField(auto_now=True, verbose_name="更新日期")
    is_published = models.BooleanField(default=False, verbose_name="是否发布")
    views = models.PositiveIntegerField(default=0, verbose_name="浏览量")
    price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)

    # 枚举字段(Django 3.0+)
    class Status(models.TextChoices):
        DRAFT = 'DF', '草稿'
        PUBLISHED = 'PB', '已发布'
        ARCHIVED = 'AR', '已归档'

    status = models.CharField(
        max_length=2,
        choices=Status.choices,
        default=Status.DRAFT,
        verbose_name="状态"
    )

常用字段类型

  • CharField:短文本,必须指定 max_length
  • TextField:长文本
  • IntegerField / PositiveIntegerField / SmallIntegerField
  • BooleanField:布尔值
  • DateTimeField / DateField / TimeField
  • DecimalField:精确小数
  • EmailField:邮箱,自动校验格式
  • URLField:URL
  • FileField / ImageField:文件上传
  • JSONField(Django 3.1+):存储 JSON 数据

字段选项

  • null:是否允许数据库为空
  • blank:表单验证时是否允许为空
  • default:默认值
  • unique:唯一约束
  • db_index:是否创建索引
  • choices:枚举选项
  • verbose_name:人类可读名称

1.2 关系字段

Django 支持三种关系:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')  # 多对一
    # 多对多
    tags = models.ManyToManyField('Tag', blank=True)
    # 一对一
    detail = models.OneToOneField('BookDetail', on_delete=models.CASCADE, null=True)

class Tag(models.Model):
    name = models.CharField(max_length=50)

class BookDetail(models.Model):
    isbn = models.CharField(max_length=13)
    pages = models.IntegerField()
  • ForeignKey:外键,on_delete 指定删除时的行为(CASCADEPROTECTSET_NULL 等)
  • ManyToManyField:多对多,自动生成中间表
  • OneToOneField:一对一

1.3 Meta 选项

在模型内部定义 class Meta 以配置模型元数据:

class Article(models.Model):
    # ...

    class Meta:
        db_table = 'blog_article'          # 自定义表名
        ordering = ['-pub_date']            # 默认排序
        indexes = [                         # 复合索引
            models.Index(fields=['pub_date', 'status']),
        ]
        verbose_name = '文章'
        verbose_name_plural = '文章'
        unique_together = ['title', 'pub_date']   # 联合唯一(Django 4.0 推荐使用 UniqueConstraint)
        constraints = [                     # 自定义约束
            models.UniqueConstraint(fields=['title', 'pub_date'], name='unique_title_per_day'),
        ]

1.4 模型方法

在模型中添加自定义方法,实现业务逻辑:

class Article(models.Model):
    # ...

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('article_detail', args=[self.pk])

    def is_editable(self):
        return self.status != self.Status.PUBLISHED

    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

1.5 模型管理器与查询集

默认管理器为 objects,可自定义:

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status='PB')

class Article(models.Model):
    # ...
    objects = models.Manager()          # 默认管理器
    published = PublishedManager()      # 自定义管理器

查询集(QuerySet)是惰性执行的,常用方法:

  • filter() / exclude():过滤
  • get():获取单个对象
  • create() / update():创建/更新
  • annotate():聚合
  • select_related() / prefetch_related():优化关联查询

2. Django 表单

Django 的表单系统负责处理用户输入,包括生成 HTML、验证数据、清理数据等。

2.1 表单类与字段

表单类继承自 django.forms.Form,其字段映射为 HTML 输入元素:

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100, label='姓名')
    email = forms.EmailField(label='邮箱')
    message = forms.CharField(widget=forms.Textarea, label='消息')
    agree = forms.BooleanField(required=True, label='同意条款')

常用表单字段

  • CharField:文本输入
  • EmailField:邮箱输入
  • IntegerField:数字输入
  • ChoiceField / ModelChoiceField:下拉选择
  • MultipleChoiceField:多选
  • BooleanField:复选框
  • DateField / DateTimeField:日期/时间
  • FileField / ImageField:文件上传

常用小部件(Widget)

  • TextInput / PasswordInput / HiddenInput
  • Textarea
  • CheckboxInput / RadioSelect
  • Select / SelectMultiple
  • FileInput
  • DateTimeInput(需配合第三方库如 django.forms.widgetsdjango-flatpickr 实现更好的 UI)

2.2 表单验证

验证分为三个层次:

  1. 字段级验证(clean_<fieldname> 方法)
  2. 表单级验证(clean 方法)
  3. 通用验证器(validators 参数)
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator

class ContactForm(forms.Form):
    name = forms.CharField(
        max_length=100,
        validators=[MinLengthValidator(2, '姓名至少2个字符')]
    )
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    def clean_name(self):
        data = self.cleaned_data['name']
        if 'spam' in data.lower():
            raise ValidationError('姓名不能包含敏感词')
        return data

    def clean(self):
        cleaned_data = super().clean()
        email = cleaned_data.get('email')
        message = cleaned_data.get('message')
        if email and message and 'spam' in message.lower():
            self.add_error('message', '消息不能包含广告内容')
        return cleaned_data

2.3 表单渲染

在模板中,表单可以多种方式渲染:

<!-- 手动渲染每个字段 -->
<form method="post">
  {% csrf_token %}
  {{ form.non_field_errors }}
  <div>
    {{ form.name.label_tag }}
    {{ form.name }}
    {{ form.name.errors }}
  </div>
  ...
</form>

<!-- 循环渲染 -->
<form method="post">
  {% csrf_token %}
  {% for field in form %}
    <div>
      {{ field.label_tag }}
      {{ field }}
      {{ field.errors }}
    </div>
  {% endfor %}
</form>

<!-- 使用 as_p / as_ul / as_table 快速渲染 -->
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
</form>

2.4 处理表单数据

视图函数中处理表单的典型模式:

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

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # 处理验证后的数据
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']
            # 发送邮件、保存数据库等
            return redirect('success')
    else:
        form = ContactForm()
    return render(request, 'contact.html', {'form': form})

3. 模型表单(ModelForm)

ModelForm 将模型与表单绑定,自动根据模型生成表单字段,并简化保存操作。

3.1 基础使用

from django.forms import ModelForm
from .models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'status']   # 指定包含的字段
        # fields = '__all__'   # 包含所有字段
        # exclude = ['views']  # 排除某些字段

视图处理与普通表单类似,但保存时调用 save() 方法:

def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()   # 直接保存到数据库
            return redirect('article_detail', pk=article.pk)
    else:
        form = ArticleForm()
    return render(request, 'article_form.html', {'form': form})

3.2 字段定制

ModelForm 中可以自定义字段或重写字段属性:

class ArticleForm(ModelForm):
    # 添加自定义字段
    agree_terms = forms.BooleanField(label='同意条款', required=True)

    class Meta:
        model = Article
        fields = ['title', 'content', 'status', 'agree_terms']
        widgets = {
            'content': forms.Textarea(attrs={'rows': 10, 'cols': 80}),
        }
        labels = {
            'title': '文章标题',
        }
        help_texts = {
            'status': '草稿状态不可被搜索',
        }

3.3 保存逻辑

save() 方法接受 commit=False 参数,允许在保存前修改对象:

def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)   # 先不保存到数据库
            article.author = request.user       # 设置外键
            article.save()                      # 现在保存
            form.save_m2m()                     # 保存多对多关系(如果有)
            return redirect(...)

3.4 高级用法

  • 继承 ModelForm 并重写 clean 方法:实现跨字段验证
  • 使用 formfield_callback:动态修改字段属性
  • ModelForm 与内联表单集:处理关联模型

4. 表单集(Formsets)

表单集用于处理同一页面上多个表单的集合,如批量编辑多个对象。

4.1 基础表单集

使用 formset_factory 创建普通表单集:

from django.forms import formset_factory

class BookForm(forms.Form):
    title = forms.CharField()
    author = forms.CharField()

BookFormSet = formset_factory(BookForm, extra=2, max_num=5)

def books_view(request):
    if request.method == 'POST':
        formset = BookFormSet(request.POST)
        if formset.is_valid():
            for form in formset:
                # 处理每个表单数据
                pass
    else:
        formset = BookFormSet()
    return render(request, 'books.html', {'formset': formset})

4.2 模型表单集

使用 modelformset_factory 为模型生成表单集:

from django.forms import modelformset_factory
from .models import Book

BookFormSet = modelformset_factory(Book, fields=('title', 'author'), extra=1)

def manage_books(request):
    queryset = Book.objects.filter(author=request.user)
    formset = BookFormSet(request.POST or None, queryset=queryset)
    if formset.is_valid():
        formset.save()
        return redirect('book_list')
    return render(request, 'manage_books.html', {'formset': formset})

内联表单集(Inline Formset) 用于处理一对多关系:

from django.forms import inlineformset_factory

BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title', 'isbn'), extra=2)

def author_edit(request, pk):
    author = get_object_or_404(Author, pk=pk)
    formset = BookInlineFormSet(request.POST or None, instance=author)
    if formset.is_valid():
        formset.save()
        return redirect('author_detail', pk=author.pk)
    return render(request, 'author_edit.html', {'formset': formset})

5. 文件上传处理

5.1 模型中的文件字段

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    avatar = models.ImageField(upload_to='avatars/%Y/%m/%d/', blank=True, null=True)
    resume = models.FileField(upload_to='resumes/')

5.2 表单中的文件字段

在表单中,ImageFieldFileField 会自动处理:

class ProfileForm(ModelForm):
    class Meta:
        model = Profile
        fields = ['avatar', 'resume']

视图需要接收 request.FILES

def profile_edit(request):
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect(...)
    else:
        form = ProfileForm()
    return render(request, 'profile.html', {'form': form})

模板中必须设置 enctype="multipart/form-data"

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  {{ form }}
  <button type="submit">上传</button>
</form>

5.3 文件存储与访问

  • upload_to 可以是一个可调用对象,动态生成路径。
  • 上传文件后,可以通过 model.file.url 获取访问 URL,需要在 settings.py 中配置 MEDIA_URLMEDIA_ROOT

6. 自定义验证与清理

除了字段级和表单级验证,Django 还提供:

6.1 验证器

可复用的验证函数:

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError('数值必须为偶数')

class MyForm(forms.Form):
    even_number = forms.IntegerField(validators=[validate_even])

6.2 模型验证

模型本身也支持验证,通过重写 clean 方法实现:

class Article(models.Model):
    # ...
    def clean(self):
        if self.status == 'PB' and not self.pub_date:
            raise ValidationError('发布状态的文章必须有发布日期')

模型验证在调用 full_clean() 时触发,但通常在 ModelForm 保存时自动调用。

6.3 清理与验证顺序

  1. 字段验证(运行 to_python 和字段级验证)
  2. 字段的 clean_<field> 方法
  3. 表单的 clean 方法
  4. 模型的 clean 方法(仅限 ModelForm)

7. 表单与模型的关系

7.1 数据流转

  • 用户提交数据 → 表单接收 → 验证 → 清理 → 生成 cleaned_data → 保存到模型(或执行其他操作)
  • ModelForm 自动映射表单字段到模型字段,并处理关系字段(外键、多对多)。

7.2 选择字段与关系

  • ModelChoiceField:用于外键选择,可设置 querysetempty_label
  • ModelMultipleChoiceField:用于多对多选择

示例:

class ArticleForm(ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(), empty_label="请选择作者")
    tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple)

    class Meta:
        model = Article
        fields = ['title', 'content', 'author', 'tags']

7.3 保存关联对象

使用 commit=False 后,需要显式保存多对多关系:

article = form.save(commit=False)
article.author = request.user
article.save()
form.save_m2m()   # 保存 tags 等 ManyToMany 字段

8. 性能与安全最佳实践

8.1 性能优化

  • 使用 select_related / prefetch_related:在获取表单集时预加载关联对象,减少查询次数。
  • 限制字段数量:避免在表单中包含大量字段,尤其是大字段(如 TextField)可考虑延迟加载。
  • 使用 only() / defer():在不需要所有字段时,限制查询的字段。
  • 表单集分页:对于大量数据的编辑,使用分页处理。

8.2 安全防护

  • CSRF 保护:始终在模板中包含 {% csrf_token %}
  • 验证用户输入:利用 Django 内置验证器,并自定义严格验证逻辑。
  • 文件上传安全:限制上传文件类型(通过验证器或 validate_image_file),设置 FILE_UPLOAD_MAX_MEMORY_SIZE
  • SQL 注入与 XSS:Django 模板自动转义,但需小心使用 safe 过滤器;使用参数化查询(Django ORM 自动处理)。
  • 权限控制:在视图和表单中验证用户权限(如 request.user.has_perm)。

8.3 用户体验

  • 使用 django-crispy-forms:快速美化表单。
  • 前端验证:结合 HTML5 属性和 JavaScript 提供即时反馈,但不能替代后端验证。
  • 友好的错误信息:自定义 error_messages 字段属性,提供清晰提示。

9. 总结

Django 4 的表单与模型系统提供了强大而灵活的工具,帮助开发者快速构建安全、健壮的应用。掌握以下核心要点:

  • 模型:定义数据结构,使用字段类型、关系、元选项和自定义方法。
  • 表单:处理用户输入,包含字段、验证、渲染和视图集成。
  • 模型表单:自动映射模型与表单,简化保存逻辑。
  • 表单集:处理批量数据输入。
  • 文件上传:通过 FileFieldImageField 轻松处理。
  • 验证:多层次验证确保数据完整性。
  • 安全与性能:遵循最佳实践,保障应用稳定运行。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐