心蓝的博客 心蓝的博客
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档

心蓝

人生苦短,我用python
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • 零基础

  • 专项

  • web框架

    • django框架

      • web框架介绍
      • python虚拟环境
      • 创建django项目与应用
      • django中的路由系统
      • web框架设计模式
      • 模板
      • 静态文件引用
      • web应用开发模式
      • 请求和响应
      • 视图
      • ORM与模型
      • 数据库操作
      • 项目实战一
      • 表单
        • 创建表单
        • 在模板中使用表单
          • 渲染表单对象
          • 手动渲染字段
          • 渲染表单错误信息
          • 遍历表单字段
        • 部件
          • 指定部件
          • 样式化部件实例
        • 表单的校验
          • 指定字段校验
          • 验证相互依赖的字段
        • 模型表单
          • save()
        • 学生创建,更新视图案例
          • 表单
          • 视图
          • 模板
      • RESTful API
      • 项目实战二
      • djangoadmin
    • drf

  • python
  • web框架
  • django框架
心蓝
2022-12-22
目录

表单

# 表单

在之前的案例中,每次我们需要提交表单数据的时候。我们都需要去手动编辑html表单,根据不同的字段,字段名,进行编码。做了很多重复的部分,所以django提供了一个专门用来处理表单的类,django.forms.Form。

通过它,我们不仅能够自动生成前端页面,也可以用来验证数据的合法性。我们通过改写添加修改学生的表单来学习它。

# 创建表单

在app根目录下,创建一个forms.py的模块,代码如下:

from django import forms
from .models import Channel


class StudentForm(forms.Form):
    name = forms.CharField(label='姓名', max_length=20)
    age = forms.IntegerField(label='年龄', required=False)
    sex = forms.ChoiceField(label='性别', choices=((1, '男'), (0, '女')))
    phone = forms.CharField(label='手机号码', required=False, max_length=20)
    channel = forms.ModelChoiceField(label='渠道', required=False, queryset=Channel.objects.all())
1
2
3
4
5
6
7
8
9
10

每一个模型表单,都是forms.Form的一个子类,类属性与模型的类属性类似,都表示不同类型的字段。不同的字段,将会渲染成不同的input类型。字段名与每一个input标签的name属性对应。

每一个字段都是一个字段类的实例,其中label参数渲染成label标签的内容。max_length用来限制用户输入字符长度。required参数表示该字段是否必填,默认为True,要指定一个字段是不必填的,设置required=False。

# 在模板中使用表单

只需要将表单实例放到模板上下文就可以通过模板变量使用表单。

# 渲染表单对象

修改学生添加页面视图如下:

from .forms import StudentForm


class StudentCreateView(View):
    """
    学生添加视图
    """
    def get(self, request):
        """学生添加页面"""
        # 1. 获取渠道对象
        channels = Channel.objects.all()
        form = StudentForm()
        return render(request, 'crm/student_detail.html', context={'channels': channels, 'form': form})
1
2
3
4
5
6
7
8
9
10
11
12
13

在视图中,实例化了一个表单对象,然后传递变量form给了模板。那么在模板中通过{{ form }}将会渲染对应的<label>和<input>元素,下面是StduentForm实例用{{ form }}的输出:

<tr><th><label for="id_name">姓名:</label></th><td><input type="text" name="name" maxlength="20" required id="id_name"></td></tr>
<tr><th><label for="id_age">年龄:</label></th><td><input type="number" name="age" id="id_age"></td></tr>
<tr><th><label for="id_sex">性别:</label></th><td><select name="sex" id="id_sex">
  <option value="1">男</option>
  <option value="0">女</option>
  <option value="1">百度</option>
  <option value="2">抖音</option>
  <option value="3">b站</option>
</select></td></tr>
1
2
3
4
5
6
7
8
9

我们看到表单对象默认渲染了表格格式的字段,所以需要在模板中提供外层<form>标签和submit控件。

那么在模板中可以按照如下方式渲染:

<form >
    <talbe>
    	{{ form }}
    </talbe>
    <input type="submit" value="添加" />
</form>    
1
2
3
4
5
6

对于表单字段的渲染,还有如下格式:

  • {{ form.as_table }} 字段会渲染成表格元素<tr>
  • {{ form.as_p }} 字段会渲染成<p>标签
  • {{ form.as_ul }} 字段会渲染成<li>标签

注意

记得提供外层的<table>或<ul>元素

# 手动渲染字段

直接渲染表单对象,不是太灵活,我们可以手动处理。每个字段都可以用{{ form.name_of_field }}作为表单的一个属性,并被相应的渲染在模板中。例如:

{{ form.non_field_errors }}
<div class="fieldWrapper">
    {{ form.subject.errors }}
    <label for="{{ form.subject.id_for_label }}">Email subject:</label>
    {{ form.subject }}
</div>
<div class="fieldWrapper">
    {{ form.message.errors }}
    <label for="{{ form.message.id_for_label }}">Your message:</label>
    {{ form.message }}
</div>
<div class="fieldWrapper">
    {{ form.sender.errors }}
    <label for="{{ form.sender.id_for_label }}">Your email address:</label>
    {{ form.sender }}
</div>
<div class="fieldWrapper">
    {{ form.cc_myself.errors }}
    <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
    {{ form.cc_myself }}
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

完整的<label>元素还可以使用label_tag()来生成。例如:

<div class="fieldWrapper">
    {{ form.subject.errors }}
    {{ form.subject.label_tag }}
    {{ form.subject }}
</div>
1
2
3
4
5

# 渲染表单错误信息

表单的错误信息分两种,一种是{{ form.name_of_field.errors }}显示对应字段的错误信息列表,它默认被渲染成为无序列表,看起来如下:

<ul class="errorlist">
    <li>Sender is required.</li>
</ul>
1
2
3

该列表有一个CSS class errorlist ,允许自定义样式。如果想要进一步定义错误信息的显示,可以通过遍历来实现:

{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}
1
2
3
4
5
6
7

第二种是{{ form.non_field_errors }}显示非字段验证错误信息,它渲染后看起来如下:

<ul class="errorlist nonfield">
    <li>Generic validation error</li>
</ul>
1
2
3

该列表会额外带上一个classnonfield以便与字段验证错误信息区分。

# 遍历表单字段

如果表单字段使用相同的结构,可以对表单对象进行迭代:

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
        <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
    </div>
{% endfor %}
1
2
3
4
5
6
7
8
9

有用的字段属性:

  • {{ field.lable }}

字段的label,比如Email address

  • {{ field.label_tag }}

该字段的label标签,它包含表单的label_suffix,默认是个冒号,例如:

<label for="id_email">Email address:</label>
1
  • {{ field.id_for_label }}

该字段的id,用于手动构建label

  • {{ field.value }}

该字段的值

  • {{ field.html_name }}

字段名称,用于输入元素的name属性中。如果设置了表单前置,它也会被加进去。

  • {{ field.help_text }}

与该字段关联的帮助文本

  • {{ field.errors }}

输出错误信息列表

  • {{ field.is_hidden }}

如果该字段是隐藏字段,这个属性是True,否则为False

# 部件

每一个表单字段,都会有一个对应的HTML元素与之对应。部件用来处理HTML渲染,以及从对应的GET/POST字典中提取数据。

# 指定部件

每一个表单字段,django都会使用一个默认的部件来显示数据类型。要想知道哪个字段使用哪个部件,请查看内置Field类 (opens new window)。

有时候我们可能需要修改默认的部件,通过字段参数widget来处理。例如:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)
1
2
3
4
5
6

字段comment将会使用Textarea部件,而不是默认的TextInput部件。

# 样式化部件实例

默认情况下,部件渲染的表单标签没有css类,没有额外属性。可以通过attrs参数进行设置:

class CommentForm(forms.Form):
    name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
    url = forms.URLField()
    comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))
1
2
3
4

也可以在表单定义中修改部件:

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField()

    name.widget.attrs.update({'class': 'special'})
    comment.widget.attrs.update(size='40')
1
2
3
4
5
6
7

或者如果该字段没有直接在表单上声明(比如模型表单字段),可以使用Form.fields属性:

class CommentForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['name'].widget.attrs.update({'class': 'special'})
        self.fields['comment'].widget.attrs.update(size='40')
1
2
3
4
5

Django会将这些属性包含在渲染的输出中:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special" required></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40" required></td></tr>
1
2
3
4
5

# 表单的校验

django中的表单除了渲染html外,还有一个很重要的作用就是校验数据。

看添加学生的视图案例:

class StudentCreateView(View):
    """
    学生添加视图
    """

    def post(self, request):
        """添加学生"""
        form = StudentForm(request.POST)
        if form.is_valid():
            obj = Student.objects.create(**form.cleaned_data)
            return redirect(reverse('student-list'))
        return render(request, 'crm/student_detail.html', context={'form': form})
1
2
3
4
5
6
7
8
9
10
11
12

实例化表单时,可以将GET/POST参数传入,然后调用表单对象的is_valid()方法进行校验。如果校验通过,这个方法会返回True,否则返回False。

校验通过后通过cleaned_data属性访问干净的数据。

# 指定字段校验

定义表单时,可以定义方法clean_<fieldname>()方法对指定的字段进行校验,该方法不接受参数。在方法中通过self.cleaned_data获取该字段的值。

如果校验不通过需要触发一个ValidationError的异常,校验通过请return该值。

在学生创建的逻辑中,我们没有验证电话号码的格式,在表单中编写一个校验方法如下:

import re

from django import forms
from django.core.exceptions import ValidationError

from .models import Channel, Student

class StudentForm(forms.ModelForm):
    class Meta:
        model = Student  # 指定要生成表单的模型
        exclude = ['c_time']    # 指定不需要生成的字段

    def clean_phone(self):
        phone = self.cleaned_data.get('phone')
        if phone is not None:
            if not re.match(r'1[3-9]\d{9}$', phone):
                raise ValidationError('手机号码格式不正确!')
        return phone
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 验证相互依赖的字段

有时候需要同时校验多个字段,比如注册时,校验密码和重复密码。这时复写clean()方法是一个很好的办法:

from django import forms
from django.core.exceptions import ValidationError

class RegistorForm(forms.Form):
    # Everything as before.
    
    def clean(self):
        cleaned_data = super().clean()
		password = cleaned_data.get('password')
        password_confirm = cleaned_data.get('password_confirm')
        if not password == password_confirm:
            raise ValidationError('输入的密码不一致!')
        return cleaned_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14

在表单的clean()方法被调用前,上一节中的单字段校验方法都会先被运行。clean()方法中如果出现验证错误,在模板中使用{{form.non_field_errors}}显示。

# 模型表单

django提供了一个辅助类,可以从一个模型创建一个Form类,而不需要重复定义字段。

修改学生表单如下:

from django import forms
from .models import Channel, Student


class StudentForm(forms.ModelForm):
    class Meta:
        model = Student  # 指定要生成表单的模型
        exclude = ['c_time']    # 指定不需要生成的字段
1
2
3
4
5
6
7
8

每一个模型表单都是forms.ModelForm的一个子类,和普通表单不同。由于模型已经定义了字段,在模型表单中,只需要在Meta类中指定模型和字段。

字段可以通过属性fields=['field1', 'field2', ..]指定需要的字段,fields='all'表示生成所有的字段, 也可以通过exclude = ['field1', 'field2', ..]排除字段。

# save()

模型表单与普通的表单还有一个不同就是save()方法。在校验过的表单实例上调用save()方法,会自动调用对应的模型在数据库中创建数据或修改数据。

学生添加案例:

class StudentCreateView(View):
    """
    学生添加视图
    """

    def post(self, request):
        """添加学生"""
        # 实例化表单
        form = StudentForm(request.POST)
        # 校验
        if form.is_valid():
            # 保存数据
            form.save()
            return redirect(reverse('student-list'))
        return render(request, 'crm/student_detail.html', context={'form': form})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面的代码中,如果表单校验通过,执行form.save()会创建返回Student实例并保存到数据库。

学生更新案例:

class StudentUpdateView(View):
    """
    学生更新视图
    """

    def get_obj(self, pk):
        obj = get_object_or_404(Student, pk=pk)
        return obj

    def get(self, request, pk):
        # 1. 获取修改对象
        obj = self.get_obj(pk)
        # 2. 实例化表单对象,并填充模型对象
        form = StudentForm(instance=obj)
        # 2. 渲染并返回修改页面
        return render(request, 'crm/student_detail.html', context={'form': form})

    def post(self, request, pk):
        # 1. 获取修改对象
        obj = self.get_obj(pk)
        # 2. 实例化表单对象,填充前端传递的数据和模型对象
        form = StudentForm(request.POST, instance=obj)
        # 3. 校验
        if form.is_valid():
            form.save()     # 保存更新
            return redirect(reverse('student-list'))
        return render(request, 'crm/student_detail.html', context={'form': form})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

上面的代码中实例化表单时传递POST参数,同时把要更新的模型对象传给instance参数,在校验通过后,执行form.save()会使用校验后的参数更新模型对象。

# 学生创建,更新视图案例

# 表单

# crm/froms.py
import re

from django import forms
from django.core.exceptions import ValidationError

from .models import Student


class StudentForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name in self.fields:
            self.fields[name].widget.attrs.update({'class': 'form-control'})

    class Meta:
        model = Student  # 指定要生成表单的模型
        exclude = ['c_time']    # 指定不需要生成的字段

    def clean_phone(self):
        phone = self.cleaned_data.get('phone')
        if not re.match(r'1[3-9]\d{9}$', phone):
            raise ValidationError('手机号码格式不正确!')
        return phone
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 视图

# crm/views.py
class StudentCreateView(View):
    """
    学生添加视图
    """
    def get(self, request):
        """学生添加页面"""
        # 1. 获取渠道对象
        channels = Channel.objects.all()
        form = StudentForm()
        return render(request, 'crm/student_detail.html', context={'channels': channels, 'form': form})

    def post(self, request):
        """添加学生"""
        form = StudentForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('student-list'))
        return render(request, 'crm/student_detail.html', context={'form': form})


class StudentUpdateView(View):
    """
    学生更新视图
    """

    def get_obj(self, pk):
        obj = get_object_or_404(Student, pk=pk)
        return obj

    def get(self, request, pk):
        # 1. 获取修改对象
        obj = self.get_obj(pk)
        # 2. 实例化表单对象,并填充模型对象
        form = StudentForm(instance=obj)
        # 2. 渲染并返回修改页面
        return render(request, 'crm/student_detail.html', context={'form': form})

    def post(self, request, pk):
        # 1. 获取修改对象
        obj = self.get_obj(pk)
        # 2. 实例化表单对象,填充前端传递的数据和模型对象
        form = StudentForm(request.POST, instance=obj)
        # 3. 校验
        if form.is_valid():
            form.save()     # 保存更新
            return redirect(reverse('student-list'))
        return render(request, 'crm/student_detail.html', context={'form': form})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 模板

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <title>{% if obj %}修改{% else %}添加{% endif %}学生</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">

    <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
    <!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
    <![endif]-->
</head>
<body>
<div class="container">
    <div style="width: 800px">
        <h1>学生{% if obj %}修改{% else %}添加{% endif %}页面</h1>
        <form class="form-horizontal" method="post">
            {% for field in form %}
                <div class="form-group {% if field.errors %}has-error{% endif %}">
                    <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
                    <div class="col-sm-10">
                        {{ field }}
                        {% for error in field.errors %}
                            <span class="help-block">{{ error }}</span>
                        {% endfor %}
                    </div>
                </div>
            {% endfor %}

            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="submit" class="btn btn-default">{% if form.instance %}修改{% else %}
                        添加{% endif %}</button>
                </div>
            </div>
        </form>
    </div>
</div>


<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"
        integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
        crossorigin="anonymous"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
        integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
        crossorigin="anonymous"></script>
</body>
</html>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。

#django
上次更新: 2022/12/26, 16:59:39
项目实战一
RESTful API

← 项目实战一 RESTful API→

最近更新
01
requests让接口测试如此简单 原创
03-31
02
最简明的python正则教程
03-30
03
pycharm激活码
12-30
更多文章>
Theme by Vdoing | Copyright © 2019-2025 心蓝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式