Django之Form和modelForm

Form介绍

我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。

与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。

Django form组件就实现了上面所述的功能。

总结一下,其实form组件的主要功能如下:

生成页面可用的HTML标签

对用户提交的数据进行校验

保留上次输入内容

普通方式手写注册功能

# 注册
def register(request):
    error_msg = ""
    if request.method == "POST":
        username = request.POST.get("name")
        pwd = request.POST.get("pwd")
        # 对注册信息做校验
        if len(username) < 6:
            # 用户长度小于6位
            error_msg = "用户名长度不能小于6位"
        else:
            # 将用户名和密码存到数据库
            return HttpResponse("注册成功")
    return render(request, "register.html", {"error_msg": error_msg})
views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="/reg/" method="post">
    {% csrf_token %}
    <p>
        用户名:
        <input type="text" name="name">
    </p>
    <p>
        密码:
        <input type="password" name="pwd">
    </p>
    <p>
        <input type="submit" value="注册">
        <p style="color: red">{{ error_msg }}</p>
    </p>
</form>
</body>
</html>
login.html

使用form组件实现注册功能

{{ form_obj.as_p }}  将form_obj的所有字段都生成了标签

{{ form_obj.title }} form_obj.字段名称  自动生成标签

 

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


# Create your views here.


def index(request):
    if request.method == 'GET':
        form_obj = MyForm()
        return render(request, 'index.html', {'form_obj': form_obj})


class MyForm(forms.Form):
    title = forms.CharField(
        max_length=32, min_length=2,
        label='书名',
        widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )
    price = forms.IntegerField(
        label='价格',
        widget=forms.widgets.NumberInput(attrs={'class': 'form-control'})
    )
    publishDate = forms.DateField(
        label='出版日期',
        widget=forms.widgets.DateInput(attrs={'class': 'form-control', 'type': 'date'})
    )
    sex = forms.ChoiceField(
        choices=(
            ('1', ''),
            ('2', ''),
        ),
        label="性别",
        widget=forms.widgets.Select(attrs={'class': 'form-control'})
        # widget=forms.widgets.RadioSelect()
    )
    authors = forms.MultipleChoiceField(
        choices=(
            ('1', '周杰伦'),
            ('2', '周星驰'),
            ('3', '周树人'),
            ('4', '周华健'),
        ),
        label="作者",
        # widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'})
        widget=forms.widgets.CheckboxSelectMultiple()
    )
views.py
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
</head>
<body>

<h1>书籍增加</h1>
{#{{ form_obj.as_p }}#}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="form-group">
                <label for="{{ form_obj.title.id_for_label }}">{{ form_obj.title.label }}</label>
                {{ form_obj.title }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.price.id_for_label }}">{{ form_obj.price.label }}</label>
                {{ form_obj.price }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.publishDate.id_for_label }}">{{ form_obj.publishDate.label }}</label>
                {{ form_obj.publishDate }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.sex.id_for_label }}">{{ form_obj.sex.label }}</label>
                {{ form_obj.sex }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.authors.id_for_label }}">{{ form_obj.authors.label }}</label>
                {{ form_obj.authors }}
            </div>
        </div>
    </div>
</div>


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</body>
</html>
index.html
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index, name="index"),
]
urls.py

运行结果:

 

上面只是简单的实现了页面创建(form生成标签)

验证方法

 注意事项:

这种情况是因为浏览器的功能,需要在form表单中加上novalidate

然后在加上{{ field.errors.0 }},就可以获得后端提示,由于错误不止一个,所以.0来取。

所有标签都会有默认不为空。也就是默认加上了required=True,就是必须要填的,自己可以设置required=False,来改。

另外还有一个initial='xxx',可以设置默认值。生成input标签会默认生成xxx。

上面的错误都是英文的,我们可以通过error_messages来定制错误。改成中文的。

需要把提示文字改成红色的也是可以的,由于这个提示只是文本信息,自己给index页面的{{ field.errors.0 }}加上个span标签

设置文本颜色:<span class="text-danger">{{ field.errors.0 }}</span>。

边框变红的需要使用has-error,不过需要判断,出错后再这样显示{% if field.errors.0 %} has-error {% endif %},加在class里面。

help_text文本提示信息:

就是个提示信息。

在里面显示的话,使用placeholder。

 

 

 

Form常用字段和插件

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

上面也都有用到的~

initial

初始值,input框里面的初始值。

class LoginForm(forms.Form):
    username = forms.CharField(  
        min_length=8,
        label="用户名",
        initial="张三"  # 设置默认值
    )
    pwd = forms.CharField(min_length=6, label="密码")

error_messages

重写错误信息。

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密码")

password

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据
    )

radioSelect

单radio值为字符串

class LoginForm(forms.Form):
    username = forms.CharField(  #其他选择框或者输入框,基本都是在这个CharField的基础上通过插件来搞的
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密码")
    gender = forms.fields.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

单选Select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.ChoiceField(  #注意,单选框用的是ChoiceField,并且里面的插件是Select,不然验证的时候会报错, Select a valid choice的错误。
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

多选Select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField( #多选框的时候用MultipleChoiceField,并且里面的插件用的是SelectMultiple,不然验证的时候会报错。
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

单选checkbox

class From1(forms.Form):
    keep = forms.ChoiceField(
        choices=(
            ('True',1),
            ('False',0)
        ),
        label="是否记住密码",
        initial='1',
        widget=forms.CheckboxInput(),
    )

def test(request):

    if request.method == "GET":
        form = From1()
        return render(request, 'test.html',{'form':form})
    else:
        form = From1(request.POST)
        if form.is_valid():
            print(form.cleaned_data)
            return redirect('index')
        else:
            return render(request,'test.html',{'form':form})

选中:'True'   #form只是帮我们做校验,校验选择内容的时候,就是看在没在我们的choices里面,里面有这个值,表示合法,没有就不合法
    没选中:'False'
    ---保存到数据库里面  keep:'True'
    if keep == 'True':
        session 设置有效期7天
    else:
        pass

 

多选checkbox

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

date类型

from django import forms
from django.forms import widgets
class BookForm(forms.Form):
    date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'}))  #必须指定type,不然不能渲染成选择时间的input框

choice字段注意事项

在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。

方式一

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from app01 import models

class MyForm(forms.Form):
    publish = forms.ModelChoiceField(
        label='出版社',
        queryset=models.Publish.objects.all(),
        widget=forms.widgets.Select(attrs={'class': 'form-control'})
    )

方式二

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from app01 import models

class MyForm(forms.Form):
    publish = forms.ChoiceField()

    # 指定choices值
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # print(self.fields)
        self.fields['publish'].choices = models.Publish.objects.all().values_list('pk', 'name')

 

批量添加样式

可通过重写form类的init方法来实现。(类似上面)

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from app01 import models

def index(request):
    if request.method == 'GET':
        form_obj = MyForm()
        return render(request, 'index.html', {'form_obj': form_obj})


class MyForm(forms.Form):
    title = forms.CharField(
        max_length=32, min_length=2,
        label='书名',
        widget=forms.widgets.TextInput()
    )
    price = forms.IntegerField(
        label='价格',
        widget=forms.widgets.NumberInput()
    )
    publishDate = forms.DateField(
        label='出版日期',
        widget=forms.widgets.DateInput(attrs={'type': 'date'})
    )

    publish = forms.ModelChoiceField(
        label='出版社',
        queryset=models.Publish.objects.all(),
        widget=forms.widgets.Select()
    )

    authors = forms.ModelMultipleChoiceField(
        label='作者',
        queryset=models.Author.objects.all(),
        widget=forms.widgets.SelectMultiple()
    )

    #批量添加样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

上面是批量添加了'class': 'form-control'属性。效果和之前的一样。

前端index页面:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
</head>
<body>

<h1>书籍增加</h1>
{#{{ form_obj.as_p }}#}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="form-group">
                <label for="{{ form_obj.title.id_for_label }}">{{ form_obj.title.label }}</label>
                {{ form_obj.title }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.price.id_for_label }}">{{ form_obj.price.label }}</label>
                {{ form_obj.price }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.publishDate.id_for_label }}">{{ form_obj.publishDate.label }}</label>
                {{ form_obj.publishDate }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.publish.id_for_label }}">{{ form_obj.publish.label }}</label>
                {{ form_obj.publish }}
            </div>
            <div class="form-group">
                <label for="{{ form_obj.authors.id_for_label }}">{{ form_obj.authors.label }}</label>
                {{ form_obj.authors }}
            </div>

        </div>
    </div>
</div>


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</body>
</html>

 这样看上面的index页面很多重复的,所以,for循环简化下

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
</head>
<body>

<h1>书籍增加</h1>
{#{{ form_obj.as_p }}#}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            {% for field in form_obj %}
                <div class="form-group">
                    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                    {{ field }}
                </div>
            {% endfor %}


        </div>
    </div>
</div>


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</body>
</html>

ok

小总结

 

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from app01 import models

def index(request):
    if request.method == 'GET':
        form_obj = MyForm()
        return render(request, 'index.html', {'form_obj': form_obj})
    else:
        data = request.POST
        form_obj = MyForm(data)
        if form_obj.is_valid():  # 验证每个字段传过来的数据是不是正确的
            data = form_obj.cleaned_data
            print(data)
            author_data = data.pop('authors')
            print(author_data)
            book_obj = models.Book.objects.create(**data)
            book_obj.authors.add(*author_data)
            return HttpResponse('ok')
        else:
            print(form_obj.errors)  # 查看错误
        return render(request, 'index.html', {'form_obj': form_obj})


class MyForm(forms.Form):
    title = forms.CharField(
        max_length=32, min_length=2,
        label='书名',
        widget=forms.widgets.TextInput(attrs={'placeholder':'请输入2-32位的书籍名称~'}),
        error_messages={
            "min_length": '长度不能小于2位',
            "required": '该字段不能为空!',
            "max_length": '字段过长,不能超过32位!'
        },
        help_text='请输入2-32位的书籍名称~'
    )
    price = forms.IntegerField(
        label='价格',
        widget=forms.widgets.NumberInput()
    )
    publishDate = forms.DateField(
        label='出版日期',
        widget=forms.widgets.DateInput(attrs={'type': 'date'})
    )

    publish = forms.ModelChoiceField(
        label='出版社',
        queryset=models.Publish.objects.all(),
        widget=forms.widgets.Select()
    )

    authors = forms.ModelMultipleChoiceField(
        label='作者',
        queryset=models.Author.objects.all(),
        widget=forms.widgets.SelectMultiple()
    )

    # 批量添加样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })
views.py
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
</head>
<body>

<h1>书籍增加</h1>
{#{{ form_obj.as_p }}#}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="{% url 'index' %}" method="post" novalidate>
                {% for field in form_obj %}
                    <div class="form-group {% if field.errors.0 %} has-error {% endif %} ">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="text-success">{{ field.help_text }}</span>
                        <span class="text-danger">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}
                <input type="submit" class="btn btn-primary pull-right" value="提交">
            </form>
        </div>
    </div>
</div>


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</body>
</html>
index.html

是可以添加到数据库的。

Form的内置字段

 内置字段:

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型
内置字段

校验器

RegexValidator验证器

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from django.core.validators import RegexValidator

class TestForm(forms.Form):
    name = forms.CharField(
        max_length=32,
        validators=[RegexValidator(r'^a', '必须以a开头'), ], # 可以写多个
    )

def home(request):
    if request.method == 'GET':
        form_obj = TestForm()
        return render(request, 'home.html', {'form_obj': form_obj})
    else:
        data = request.POST
        print(data)
        form_obj = TestForm(data)
        if form_obj.is_valid():
            print(form_obj.cleaned_data)
        else:
            return render(request, 'home.html', {'form_obj': form_obj})

自定义验证函数

import re
from django.shortcuts import render, HttpResponse, redirect
from django import forms
from django.core.exceptions import ValidationError

def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')  #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误

class TestForm(forms.Form):
    name = forms.CharField(
        max_length=32,
        # validators=[RegexValidator(r'^a', '必须以a开头'), ],
        validators=[mobile_validate,],
    )

Hook钩子

除了上面两种方式,我们还可以在Form类中定义钩子函数,来实现自定义的验证功能。

局部钩子

我们在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from django.core.exceptions import ValidationError

class TestForm(forms.Form):
    name = forms.CharField(max_length=32,)
    def clean_name(self):
        value = self.cleaned_data.get('name')
        if '666' in value:
            raise ValidationError('光喊6是不行的!!')
        else:
            return value

def home(request):
    if request.method == 'GET':
        form_obj = TestForm()
        return render(request, 'home.html', {'form_obj': form_obj})
    else:
        data = request.POST
        form_obj = TestForm(data)
        print(form_obj.is_valid())
        print(form_obj.cleaned_data)
        if form_obj.is_valid():
            print('self',form_obj.cleaned_data)
            return HttpResponse('ok')
        else:
            return render(request, 'home.html', {'form_obj': form_obj})

全局钩子

我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验。

from django.shortcuts import render, HttpResponse, redirect
from django import forms
from django.core.exceptions import ValidationError

class TestForm(forms.Form):
    name = forms.CharField(max_length=32,)
    password = forms.CharField()  # 密码
    r_password = forms.CharField()  # 确认密码

# 全局钩子
    def clean(self):
        p1 = self.cleaned_data.get('password')
        p2 = self.clea通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。ned_data.get('r_password')
        if p1 == p2:
            return self.cleaned_data  # return所有的cleaned_data
        else:
            self.add_error('r_password', '两次输入的密码不一致')
            # raise ValidationError('两次输入的密码不一致!')
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
</head>
<body>

<form action="{% url 'home' %}" method="post" novalidate>
    {% csrf_token %}
    {{ form_obj.name.label }}
    {{ form_obj.name }}
    {{ form_obj.name.errors.0 }}
    <div>
        {{ form_obj.password.label }}
        {{ form_obj.password }}
        {{ form_obj.password.errors.0 }}
    </div>
    <div>
        {{ form_obj.r_password.label }}
        {{ form_obj.r_password }}
        {{ form_obj.r_password.errors.0 }}
    </div>
    <input type="submit" value="提交">
</form>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</body>
</html>
home.html

 ModelForm

  通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。

  基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。

modelForm定义

form与model的\结合,会根据你model中的字段转换成对应的form字段,并且并你生成标签等操作。

举例:

models中的内容:

class Book(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid")
    authors=models.ManyToManyField(to='Author',)
    def __str__(self):
        return self.title

modelform类的写法

class BookForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = "__all__"
        labels = {
            "title": "书名",
            "price": "价格"
        }
        widgets = {
            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
            "publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
        }

class Meta下常用参数:

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息
error_messages = {
    'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写,...是省略的意思,复制黏贴我代码的时候别忘了删了...
}

批量添加样式:和form的一样

from django import forms
class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.Book  #一个表对应一个modelform
        # fields = ['title','']  #选择你需要验证的字段
        fields = '__all__'      #选择所有字段
        # exclude = ['title',]  #排除某些字段
        labels = {   #注意写法
            'title':'书名',
            'price':'价格',
        }
        error_messages = {  #定制错误信息
            'title':{
                'required':'不能为空',
                'max_length':'太长了',
            },
            ...
            
        }
        widgets = {  #字段定制插件
            'publishDate':forms.widgets.DateInput(attrs={'type':'date'}),
            # 'publishDate2':forms.widgets.DateInput(attrs={'type':'date'}),
        }
    
    def clean_title(self): #局部钩子
        val = self.cleaned_data.get('titile')
        return val
    def clean(self):pass 全局钩子
    
    #照样可以重写init方法来进行批量操作
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                'class': 'form-control',
            })
 
创建modelform类
def edit_book(request,n):
    book_obj = models.Book.objects.filter(pk=n)
    if request.method == 'GET':
        book_obj = book_obj.first()
        form = MyModelForm(instance=book_obj) #实例化modelform对象,其中参数是你要求改的那个model对象,如果不加instance=book_obj就只生成标签,没有数据
        
        return render(request,'edit_book.html',{'n':n,'form':form})

    else:
        data = request.POST
        form = MyModelForm(data,instance=book_obj.first()) #如果没有写instance=这个参数,那么save方法会翻译成create,添加
        if form.is_valid():
            form.save()  #update--book_obj.update(**data)
            return redirect('show')
        else:
            return render(request,'edit_book.html',{'form':form,'n':n})
使用modelform类
form
{% for field in form %}
    <div class="form-group {% if field.errors.0 %} has-error {% endif %}">
        <label for="{{ field.id_for_label }}"> {{ field.label }}</label>

        {{ field }}
        <span class="text-success">{{ field.help_text }}</span>
        <span class="text-danger">{{ field.errors.0 }}</span>
    </div>
{% endfor %}
HMTL文件的写法

ModelForm的验证

与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。

我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。

如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。

save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。

ModelForm的子类可以接受现有的模型实例作为关键字参数instance;

如果提供此功能,则save()将更新该实例。

如果没有提供,save() 将创建模型的一个新实例

>>> from myapp.models import Book
>>> from myapp.forms import BookForm

# 根据POST数据创建一个新的form对象
>>> form_obj = BookForm(request.POST)

# 创建书籍对象
>>> new_ book = form_obj.save()

# 基于一个书籍对象创建form对象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()

我们通过form组件来保存书籍表数据的时候的写法:

def index(request):
    if request.method == 'GET':
        form_obj = BookForm()

        return render(request,'index.html',{'form_obj':form_obj})

    else:
        form_obj = BookForm(request.POST)
        if form_obj.is_valid():
            # authors_obj = form_obj.cleaned_data.pop('authors')
            # new_book_obj = models.Book.objects.create(**form_obj.cleaned_data)
            # new_book_obj.authors.add(*authors_obj)
            form_obj.save()  #因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存
            return redirect('show')

        else:
            print(form_obj.errors)
            return render(request,'index.html',{'form_obj':form_obj})
form组件

之前页面的写法

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>

<h1>编辑页面</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="">
                <div class="form-group">
                    <label for="title">书名</label>
                    <input type="text" class="form-control" id="title" placeholder="title" value="{{ book_obj.title }}">

                </div>
                <div class="form-group">
                    <label for="publishDate">出版日期</label>
                    <input type="text" class="form-control" id="publishDate" placeholder="publishDate" value="{{ book_obj.publishDate|date:'Y-m-d' }}">

                </div>
                <div class="form-group">
                    <label for="price">价格</label>
                    <input type="number" class="form-control" id="price" placeholder="price" value="{{ book_obj.price }}">

                </div>
                <div class="form-group">
                    <label for="publish">书名</label>
                    <select name="publish" id="publish" class="form-control">
                        {% for publish in all_publish %}
                                {% if publish == book_obj.publish %}
                                    <option value="{{ publish.id }}" selected>{{ publish.name }}</option>
                                {% else %}
                                    <option value="{{ publish.id }}">{{ publish.name }}</option>
                                {% endif %}
                        {% endfor %}

                    </select>

                </div>
                <div class="form-group">
                    <label for="authors">书名</label>
                    <select name="authors" id="authors" multiple class="form-control">
                        {% for author in all_authors %}
                            {% if author in book_obj.authors.all %}
                                <option value="{{ author.id }}" selected>{{ author.name }}</option>
                            {% else %}
                                 <option value="{{ author.id }}" >{{ author.name }}</option>
                            {% endif %}
                        {% endfor %}

                    </select>

                </div>
            </form>

        </div>
    </div>
</div>


</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
自己写input标签等
def edit_book(request,n):

    book_obj = models.Book.objects.filter(pk=n).first()
    if request.method == 'GET':
        all_authors = models.Author.objects.all() #
        all_publish = models.Publish.objects.all()

        return render(request,'edit_book.html',{'book_obj':book_obj,'all_authors':all_authors,'all_publish':all_publish})
views.py

改成使用modelform的版本:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>

<h1>编辑页面</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="{% url 'edit_book' n %}" novalidate method="post">
                {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="text-danger">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <input type="submit" class="btn btn-primary pull-right">
                </div>

            </form>

        </div>
    </div>
</div>


</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
modelform版本
def edit_book(request,n):

    book_obj = models.Book.objects.filter(pk=n).first()
    if request.method == 'GET':
        # all_authors = models.Author.objects.all() #
        # all_publish = models.Publish.objects.all()

        form = BookForm(instance=book_obj)

        return render(request,'edit_book.html',{'form':form,'n':n}) #传递的这个n参数是给form表单提交数据的是的action的url用的,因为它需要一个参数来识别是更新的哪条记录

    else:
        form = BookForm(request.POST,instance=book_obj) #必须指定instance,不然我们调用save方法的是又变成了添加操作
        if form.is_valid():
            form.save()
            return redirect('show')
        else:
            return render(request,'edit_book.html',{'form':form,'n':n})
views.py

 

posted @ 2019-06-04 15:22  blog_wu  阅读(250)  评论(0编辑  收藏  举报