Django之form表单

Posted mjc69213

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django之form表单相关的知识,希望对你有一定的参考价值。

一、使用form类创建一个表单

先定义好一个RegForm类:

forms.py

from django import forms # 导入forms类
class NameForm(forms.Form):
    your_name = forms.CharField(label=姓名, min_length=3)

 views.py

from django.shortcuts import render, HttpResponse
from .forms import NameForm
from 
def get_name(request):
    # 如果这是一个POST请求,我们就需要处理表单数据
    if request.method == POST:
        # 创建一个表单实例,并且使用表单数据填充request请求:
        form = NameForm(request.POST)
        # 检查数据有效性:
        if form.is_valid():
            # 在需要时,可以在form.cleaned_date中处理数据
            # ...
            # 重定向到一个新的URL:
            print(form) # 看一下这个form到底是什么 <tr><th><label for="id_your_name">姓名:</label></th><td><input type="text" name="your_name" value="eee" minlength="3" required id="id_your_name" /></td></tr>
            print(form.cleaned_data) # 看一下验证通过后的数据 {‘your_name‘: ‘eee‘}
            return HttpResponse(ok)

    # 如果是GET或者其它请求方法,我们将创建一个空的表单。
    else:
        form = NameForm()

    return render(request, name.html, {form: form})

 

name.html

Django会根据模型类的字段和属性,在HTML中自动生成对应表单标签和标签属性。生成的标签会被放置到{{ form }}所在的位置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>name</title>
</head>
<body>
<form action="" method="post" novalidate>{% csrf_token %}
  {{ form }}
    <input type="submit" value="提交">
    </form>
</body>
</html>

二、使用表单模板

(一)表单模板的选项

不要忘了,表单的输出 包含submit 标签,和表单的<form> 按钮。 你必须自己提供它们。

对于<input>/<label> 对,还有几个输出选项:

  • {{ form.as_table }} 以表格的形式将它们渲染在<tr> 标签中
  • {{ form.as_p }} 将它们渲染在<p> 标签中
  • {{ form.as_ul }} 将它们渲染在<li> 标签中

注意,你必须自己提供<ul> 或<table> 元素。

下面是我们的ContactForm 实际的输出{{ form.as_p }}

<p><label for="id_subject">Subject:</label>
    <input id="id_subject" type="text" name="subject" maxlength="100" required /></p>
<p><label for="id_message">Message:</label>
    <textarea name="message" id="id_message" required></textarea></p>
<p><label for="id_sender">Sender:</label>
    <input type="email" name="sender" id="id_sender" required /></p>
<p><label for="id_cc_myself">Cc myself:</label>
    <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>

注意,每个表单字段具有一个ID 属性并设置为id_<field-name>,它被一起的label 标签引用。

(二)手动渲染子段

在上面的例子中,Django会根据{{ form.as_p }}自动生成表单,我们也可以自己来具体编写表单的HTML;(例如,允许我们重新排序字段)。 每个字段都是表单的一个属性,可以使用{{ form.name }} 访问,并将在Django 模板中正确地渲染。 像这样:

    <div class="form-group">{{ form.errors }}</div>
    <div class="form-group">
        {{ form.subject.errors }}
        <label for="{{ form.subject.id_for_label }}">邮箱:</label> // 因为label自定义了目标表单的id,改变了原来的label.
        {{ form.subject }}
    </div>
    <div class="form-group">
        {{ form.message.errors }}
        <label for="{{ form.message.id_for_label }}">消息:</label> 
        {{ form.message }}
    </div>
    <div class="form-group">
        {{ form.sender.errors }}
        <label for="{{ form.sender.id_for_label }}">邮箱地址:</label>
        {{ form.sender }}
    </div>
    <div class="form-group">
        {{ form.cc_myself }}
        <label for="{{ form.cc_myself.id_for_label }}">是否记住:</label>
        {{ form.cc_myself }}
    </div>

 

完整的<label> 元素还可以使用label_tag() 生成。 像这样:

    <div class="form-group">
        {{ form.subject.errors }}
        {{ form.subject.label_tag }}
        {{ form.subject }}
    </div>
    <div class="form-group">
        {{ form.message.errors }}
        {{ form.message.label_tag }}
        {{ form.message }}
    </div>
    <div class="form-group">
        {{ form.sender.errors }}
        {{ form.sender.label_tag }}
        {{ form.sender }}
    </div>
    <div class="form-group">
        {{ form.cc_myself.errors }}
        {{ form.cc_myself.label_tag }}
        {{ form.cc_myself }}
    </div>

(三)表单的错误信息

1、{{ form.non_field_errors }} 查找每个字段的错误

2、{{ form.name.errors }} 显示表单错误的一个清单,并渲染成一个ul

(四)循环表单的字段

如果你的表单使用相同的HTML,你可以使用{% for %} 循环迭代每个字段来减少重复的代码

{% for fied in form %}
    <div class="form-group">
    {{ fied.errors }}
    {{ fied.label_tag }}
    {{ fied }}
    </div>
{% endfor %}

 

{{ field }} 中有用的属性包括:

{{ field.label }}
字段的label,例如Email address
{{ field.label_tag }}

包含在HTML <label> 标签中的字段Label。 它包含表单的label_suffix。 例如,默认的label_suffix 是一个冒号

<label for="id_email">Email address:</label> 
{{ field.id_for_label }}
用于这个字段的ID(在上面的例子中是id_email)。 如果你正在手动构造label,自定义label用来代替label_tag。 
{{ field.value }}
字段的值。 例如[email protected]
{{ field.html_name }}
输入元素的name 属性中将使用的名称。 它将考虑到表单的前缀。
{{ field.help_text }}
与该字段关联的帮助文档。
{{ field.errors }}
输出一个<ul class="errorlist">,包含这个字段的验证错误信息。 你可以使用{% for error in field.errors %}自定义错误的显示。 这种情况下,循环中的每个对象只是一个包含错误信息的简单字符串。
{{ field.is_hidden }}
{% if field.is_hidden %}
   {# Do something special #}
{% endif %}

 

如果字段是隐藏字段,则为False,否则为True。 作为模板变量,它不是很有用处,但是可以用于条件测试,例如:

{{ field.field }}

表单类中的Field 实例,通过BoundField 封装。 您可以使用它来访问Field属性,例如{{ char_field.field.max_length }}

三、常用的字段与插件

1、initial input框里面的初始值

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

 2、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="密码")

 3、password input输入框密码为不可见

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={‘class‘: ‘c1‘}, render_value=True)
    )

 4、radioselect 

技术分享图片

class LoginForm(forms.Form):
    
    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )
##############
{‘gender‘: ‘2‘}

5、单选Select

技术分享图片

class LoginForm(forms.Form):
    ...
gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.Select()
)

 6、多选Select

技术分享图片

gender = forms.ChoiceField(
        choices=((1, ), (2, ), (3, 未知)),
        initial=[1,3],
        label=性别,
        widget=forms.widgets.SelectMultiple(attrs={class:checkbox}),

    )

  7、单选checkbox

技术分享图片

keep = forms.fields.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

 8、多选checkbox

技术分享图片

hobby = forms.ChoiceField(
        label=爱好,
        choices=((1, 足球), (2, 双色球), (3, 台球)),
        initial=[1, 2],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

 9、关于choice的注意事项:

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields

 
class MyForm(Form):
 
    user = fields.ChoiceField(
        # choices=((1, ‘上海‘), (2, ‘北京‘),),
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields[‘user‘].choices = ((1, ‘上海‘), (2, ‘北京‘),)
        #
        self.fields[user].choices = models.Classes.objects.all().values_list(id,caption) 
#fields 是当前类里所有的字段组成的有序字典类似于这样
OrderedDict([(‘subject‘, <django.forms.fields.CharField object at 0x10bb758d0>), (‘message‘, <django.forms.fields.CharField object at 0x10bb75d30>), (‘sender‘, <django.forms.fields.EmailField object at 0x10bb75da0>), (‘cc_myself‘, <django.forms.fields.BooleanField object at 0x10bb75e10>), (‘gender‘, <django.forms.fields.ChoiceField object at 0x10bb75eb8>), (‘keep‘, <django.forms.fields.ChoiceField object at 0x10bb75f28>), (‘hobby‘, <django.forms.fields.ChoiceField object at 0x10bb75f98>)])

 

方式二:

from django import forms
from django.forms import fields
from django.forms import models as form_model # 必须先导入这个包
# 或者这样导入包
# from django.forms.models import ModelChoiceField
class FInfo(forms.Form): authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
  # authors = ModelChoiceField( queryset=models.
NNewType.objects.all()
)

 10、Django 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类型

 

四、校检

(一)验证过程

 

技术分享图片 

 

(二)流程详解

  1. 函数full_clean()依次调用每个field的clean()函数,该函数针对field的max_length,unique等约束进行验证,如果验证成功则返回值,否则抛出ValidationError错误。如果有值返回,则放入form的cleaned_data字典中。
  2. 如果每个field的内置clean()函数没有抛出ValidationError错误,则调用以clean_开头,以field名字结尾的自定义field验证函数。验证成功和失败的处理方式同步骤1。
  3. 最后,调用form的clean()函数——注意,这里是form的clean(),而不是field的clean()——如果clean没有错误,那么它将返回cleaned_data字典。
  4. 如果到这一步没有ValidationError抛出,那么cleaned_data字典就填满了有效数据。否则cleaned_data不存在,form的另外一个字典errors填上验证错误。在template中,每个field获取自己错误的方式是:{{ form.username.errors }}。
  5. 最后,如果有错误is_valid()返回False,否则返回True。
  6. 怎么去记:通过看源码去找:

    先找is_valid,找到errors,找到full_clean,所有的钩子基于full_clean执行的

注意一点:自定义验证机制时:clean()和clean_&()的最后必须返回验证完毕或修改后的值.

 (三)form验证中自定义验证机制

需求

  1. 用户输入的是否为cc,如果是,提示用户
  2. 验证二次输入的密码是否匹配,如果不一致,提示用户

看下views.py中的代码:

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


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 LoginForm(forms.Form):
    user = forms.CharField(required=True, error_messages={required: 用户名不能为空.})
    pwd = forms.CharField(required=True,
                          min_length=6,
                          max_length=10,
                          error_messages={required: 密码不能为空., min_length: "至少6位"})

    pwd2 = forms.CharField(required=True,
                          min_length=6,
                          max_length=10,
                          error_messages={required: 密码不能为空., min_length: "至少6位"})


    num = forms.IntegerField(error_messages={required: 数字不能空., invalid: 必须输入数字})

    phone = forms.CharField(validators=[mobile_validate, ], )

    def clean_user(self):
        user = self.cleaned_data.get(user)
        if user == cc:
            raise forms.ValidationError(用户名是我的!)

        return user

    def clean(self):
        cleaned_data = self.cleaned_data
        pwd = cleaned_data[pwd]

        pwd2 = cleaned_data[pwd2]
        print(pwd,pwd2)
        if pwd != pwd2:
            raise forms.ValidationError(二次输入密码不匹配)
        return cleaned_data #注意此处一定要return clean_data,否则会报错
        
        


def login(request):
    if request.POST:
        objPost = LoginForm(request.POST)
        ret = objPost.is_valid()
        if ret:
            print(objPost.clean())
        else:
            from django.forms.utils import ErrorDict
            print(objPost.non_field_errors())

            pass
        return render(request, login.html, {obj1: objPost})
    else:
        objGet = LoginForm()
        return render(request, login.html, {obj1: objGet})
   
...

HTML 页面中,如果想取clean()报错的信息,因其本身是一个迭代器,所以我们可以循环返回数据的non_field_errors取值,比如:

        {%  if obj1.non_field_errors %}
                {% for item in obj1.non_field_errors %}
                    <span class="error_msg">{{ item }}</span>
                {% endfor %}
            {% endif %}

 

html代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .error_msg{
            color: red;
        }
    </style>
</head>
<body>
    <form action="/login/" method="POST">
        <div>用户名:
            {{ obj1.user }}
            {%  if obj1.errors.user %}
                <span class="error_msg">{{ obj1.errors.user.0 }}</span>
            {% endif %}
        </div>
        <div>密码:
            {{ obj1.pwd }}
            {%  if obj1.errors.pwd %}
                <span class="error_msg">{{ obj1.errors.pwd.0 }}</span>
            {% endif %}
        </div>
        <div>确认密码:
            {{ obj1.pwd2 }}
            {%  if obj1.errors.pwd2 %}
                <span class="error_msg">{{ obj1.errors.pwd2.0 }}</span>
            {% endif %}
        </div>
        <div>数字:
            {{ obj1.num }}
            {%  if obj1.errors.num %}
                <span class="error_msg">{{ obj1.errors.num.0 }}</span>
            {% endif %}
        </div>
        <div>电话:
            {{ obj1.phone }}
            {%  if obj1.errors.phone %}
                <span class="error_msg">{{ obj1.errors.phone.0 }}</span>
            {% endif %}
        </div>
        <div>
            {%  if obj1.non_field_errors %}
                {% for item in obj1.non_field_errors %}
                    <span class="error_msg">{{ item }}</span>
                {% endfor %}
            {% endif %}
        </div>
        <input type="submit" value="提交"/>

    </form>


</body>
</html>

 

五、ModelForm

Model + Form ==> ModelForm。model和form的结合体,所以有以下功能:

  • 验证
  • 数据库操作

model有操作数据库的字段,form验证也有那几个字段,虽然耦合度降低,但是代码是有重复的。如果利用model里的字段,那是不是form里的字段就不用写了。

1、Model + Form (之前的操作)

models.py

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.EmailField()
    user_type = models.ForeignKey(to=‘UserType‘,to_field=‘id‘)

forms.py

from django import forms
from django.forms import fields

class UserInfoForm(forms.Form):
    # username = models.CharField(max_length=32)    <-- models
    username = fields.CharField(max_length=32)
    # email = models.EmailField()    <-- models
    email = fields.EmailField()
    # user_type = models.ForeignKey(to=‘UserType‘,to_field=‘id‘)    <-- models
    user_type = fields.ChoiceField(
        choices=models.UserType.objects.values_list(‘id‘,‘caption‘)
    )

    # 下面的操作是让数据在网页上实时更新。
    def __init__(self, *args, **kwargs):
        super(UserInfoForm,self).__init__(*args, **kwargs)
        self.fields[‘user_type‘].choices = models.UserType.objects.values_list(‘id‘,‘caption‘)

index.html

<body>
    <form action="/index/" method="POST" novalidate="novalidate">
        {% csrf_token %}
        {{ obj.as_p }}
        <input type="submit" value="提交">
    </form>
</body>

novalidate 注: HTML5输入类型和浏览器验证

如果表单中包含URLField、EmailField和其他整数字段类似,Django将使用url、email和number这样的HTML5输入类型。默认情况下,浏览器可能会对这些字段进行他们自身的验证,这些验证可能比Django的验证更严格。如果你想禁用这个行为,请设置form标签的novalidate属性,或者制定一个不同的字段,如TextInput。

2、ModelForm 基本操作

下面通过modelform实现

forms.py

class UserInfoModelForm(forms.ModelForm):

    class Meta:
        model = models.UserInfo    # 与models建立了依赖关系
        fields = "__all__"

views.py

def index(request):
    if request.method == "GET":
        obj = UserInfoModelForm()
        return render(request,"index.html",{‘obj‘:obj})
    elif request.method == "POST":
        obj = UserInfoModelForm(request.POST)
        print(obj.is_valid())  # 这是方法,别忘记了加括号
        print(obj.cleaned_data)
        print(obj.errors)
        return render(request,"index.html",{‘obj‘:obj})

自定制字段名

如何定义http上定义的字段呢,自定义写成中文的?之前的用法是在Form里写上label。Model Form定义要用verbose_name

models.py

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name=‘用户‘)
    email = models.EmailField(verbose_name=‘邮箱‘)
    user_type = models.ForeignKey(to=‘UserType‘,to_field=‘id‘, verbose_name=‘类型‘)

如果不在model里定义,在modelForm里实现,利用labels

class UserInfoModelForm(forms.ModelForm):

    class Meta:
        model = models.UserInfo
        fields = "__all__"
        labels = {
            ‘username‘:‘用户名‘,
            ‘email‘:‘邮箱‘,
        }

展示指定的列

fields = "__all__"上面展示所有的,可不可以展示指定的列

        fields = [‘username‘,‘email‘]   # 显示指定列
        exclude = [‘username‘]          # 排除指定列

为什么modelForm里也能做验证?

form里面有is_validcleaned_dataerrors

# Form验证:
    UserInfoForm -> Form -> BaseForm( 包含is_valid等方法)

# ModelForm验证:
    UserInfoModelForm -> ModelForm -> BaseModelForm -> BaseForm

3、ModelForm组件

ModelForm
    a.  class Meta:
            model,                           # 对应Model的
            fields=None,                     # 字段
            exclude=None,                    # 排除字段
            labels=None,                     # 提示信息
            help_texts=None,                 # 帮助提示信息
            widgets=None,                    # 自定义插件
            error_messages=None,             # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)
            field_classes=None               # 自定义字段类 (也可以自定义字段)
            localized_fields=(‘birth_date‘,) # 本地化,如:根据不同时区显示数据
            如:
                数据库中
                    2016-12-27 04:10:57
                setting中的配置
                    TIME_ZONE = ‘Asia/Shanghai‘
                    USE_TZ = True
                则显示:
                    2016-12-27 12:10:57
    b. 验证执行过程
        is_valid -> full_clean -> 钩子 -> 整体错误

    c. 字典字段验证
        def clean_字段名(self):
            # 可以抛出异常
            # from django.core.exceptions import ValidationError
            return "新值"
    d. 用于验证
        model_form_obj = XXOOModelForm()
        model_form_obj.is_valid()
        model_form_obj.errors.as_json()
        model_form_obj.clean()
        model_form_obj.cleaned_data
    e. 用于创建
        model_form_obj = XXOOModelForm(request.POST)
        #### 页面显示,并提交 #####
        # 默认保存多对多
            obj = form.save(commit=True)
        # 不做任何操作,内部定义 save_m2m(用于保存多对多)
            obj = form.save(commit=False)
            obj.save()      # 保存单表信息
            obj.save_m2m()  # 保存关联多对多信息

    f. 用于更新和初始化
        obj = model.tb.objects.get(id=1)
        model_form_obj = XXOOModelForm(request.POST,instance=obj)
        ...

        PS: 单纯初始化
            model_form_obj = XXOOModelForm(initial={...})

注意:导入模块名(fields、widgets)和字段名重复,所以导入时要起个别名。

from django import forms
from django.forms import fields as Ffields
from django.forms import widgets as Fwidgets
class UserInfoModelForm(forms.ModelForm):

    is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())

    class Meta:
        model = models.UserInfo
        fields = ‘__all__‘
        # fields =  [‘username‘,‘email‘]
        # exclude = [‘username‘]
        labels = {
            ‘username‘: ‘用户名‘,
            ‘email‘: ‘邮箱‘,
        }
        help_texts = {
            ‘username‘: ‘...‘
        }
        widgets = {
            ‘username‘: Fwidgets.Textarea(attrs={‘class‘: ‘c1‘})
        }
        error_messages = {
            ‘__all__‘:{    # 整体错误信息

            },
            ‘email‘: {
                ‘required‘: ‘邮箱不能为空‘,
                ‘invalid‘: ‘邮箱格式错误..‘,
            }
        }
        field_classes = {  # 定义字段的类是什么
            # ‘email‘: Ffields.URLField  # 这里只能填类,加上括号就是对象了。
        }

        # localized_fields=(‘ctime‘,)  # 哪些字段做本地化

4、ModelForm 数据库操作

1.1、创建数据save

如果数据验证是ok的,那么save,就直接在数据库中创建完数据了

        if obj.is_valid():
            obj.save()      # 创建数据

在如下一对多、多对多关系中:

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserGroup(models.Model):
    name = models.CharField(max_length=32)

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.EmailField()
    user_type = models.ForeignKey(to=‘UserType‘,to_field=‘id‘)
    u2g = models.ManyToManyField(UserGroup)

这样的话,执行上面的obj.save()会在UserInfo表和多对多关系表里都增加数据。

views.py

def index(request):
    if request.method == "GET":
        obj = UserInfoModelForm()
        return render(request,‘index.html‘,{‘obj‘: obj})
    elif request.method == "POST":
        obj = UserInfoModelForm(request.POST)
        if obj.is_valid():
            obj.save()  # 等价以下三句
            # instance = obj.save(False)
            # instance.save()
            # obj.save_m2m()
        return render(request,‘index.html‘,{‘obj‘: obj})

1.2、save 做了哪些操作?

save源码里:

def save(self, commit=True):
    """"""
    if commit:
        self.instance.save()    # 指的当前model对象
        self._save_m2m()        # 指:保存m2m对象
    else:
        self.save_m2m = self._save_m2m
    return self.instance    # model 类的对象
    """"""

所以instance = obj.save(False)时,什么都不会操作。

if obj.is_valid():
    instance = obj.save(False)
    instance.save()     # 当前对象表数据创建
    obj.save_m2m()      # 多对多表数据创建
    # 上面这三句完成的是和上面 obj.save 一样的操作。拆开就可以自定制操作了

2、修改数据

修改表数据是,记得把instance信息也传进去,不然是新建数据,而不是对某行数据进行修改。

编辑用户信息,新url方式保留默认数据

urls.py

  url(r‘^user_list/‘, views.user_list),
    url(r‘^edit-(d+)/‘, views.user_edit),

views.py

def user_list(request):
    li = models.UserInfo.objects.all().select_related(‘user_type‘)  # 这里只能是外键,多对多字段也不可以
    return render(request,‘user_list.html‘,{‘li‘: li})

def user_edit(request, nid):
    # 获取当前id对象的用户信息
    # 显示用户已经存在数据
    if request.method == "GET":
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(instance=user_obj)   # 把默认数据传递进去
        return render(request,‘user_edit.html‘,{‘mf‘: mf, ‘nid‘: nid})
    elif request.method == ‘POST‘:
        # 数据修改的信息,给数据库的哪一行做修改?
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(request.POST,instance=user_obj)  # 指定给谁做修改
        if mf.is_valid():
            mf.save()
        else:
            print(mf.errors.as_json())
        return render(request,‘user_edit.html‘,{‘mf‘: mf, ‘nid‘: nid})

user_list.html

<body>
    <ul>
        {% for row in li %}
            <li>{{ row.username }} - {{ row.user_type.caption }} - <a href="/edit-{{ row.id }}/">编辑</a></li>
        {% endfor %}
    </ul>
</body>

user_edit.html

<body>
    <form method="POST" action="/edit-{{ nid }}/">
        {% csrf_token %}
    {{ mf.as_p }}
        <input type="submit" value="提交" />
    </form>
</body>

5、ModelForm钩子、额外字段

数据验证钩子

从上面的Form和ModelForm中,他们都是继承了BaseForm,而is_valid是在BaseForm中定义的,所以ModelForm也能和Form一样使用各种钩子

额外字段

像网页上的checkbox,一个月内免登陆,用提交到数据库么?这个只需要设置session和cookie就可以了。

views.py、

class UserInfoModelForm(forms.ModelForm):

    is_rmb = fields.CharField(widget=widgets.CheckboxInput())  # 额外字段

    class Meta:
        model = models.UserInfo
        fields = ‘__all__‘

6、总结

1. 生成HTML标签:class Meta: ...
    2. mf = xxxModelForm(instance=ModelObj) 生成默认值
    3. 额外的标签, is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())
    4. 各种验证 is_valid() -> 各种钩子...
    5.  mf.save()
        # 或
        instance = mf.save(False)
        instance.save()
        mf.save_m2m()

ModelForm因为model和form耦合太密切,所以一般写小程序用它。









 

以上是关于Django之form表单的主要内容,如果未能解决你的问题,请参考以下文章

Django之form表单

django之form表单与ModelForm表单

Django之Form功能

Django之Form表单

Django中Form表单之字段详解

django之form表单验证