第八章表单与模型

表单主要分为两种表单

django表单功能由Form类实现,主要分为两种
1.django.forms.Form
2.django.forms.ModelForm # 结合模型生成的数据表单

表单form标签中的action用于设置用户提交的表单数据应由哪个路由来接收和处理,若为空,则提交由当前的路由来接收和处理。否则则跳转到属性action所指向的路由地址
提交请求由method决定

网页表单的构成

    {% if v.errors %} <!--v为表单类的实例化-->
        <p>
            数据出错啦,错误信息:{{ v.errors }}
        </p>
    {% else %}
        <form action="" method="post">
        {% csrf_token %}
            <table>
                <!--将表单对象生成网页表单-->
                {{ v.as_table }} as_table <!--表单的表示纯用模板变量么-->
            </table>
            <input type="submit" value="提交">
        </form>
    {% endif %}

定义表单类,新建表单文件

定义表单类需要导入模型类

from django import forms
from .models import *
class VocationForm(forms.Form):
    job = forms.CharField(max_length=20, label='职位')
    title = forms.CharField(max_length=20, label='职称')
    payment = forms.IntegerField(label='薪资') # 难道IntegerField设置数字成可自增自减
    # 设置下拉框的值
    # 查询模型PersonInfo的数据
    value = PersonInfo.objects.values('name') # 获取模型字段中的姓名
    # 将数据以为列表格式表示,列表元素为元组格式
    choices = [(i+1, v['name']) for i, v in enumerate(value)]  # 这里又为什么只显示v['name']
    # 表单字段设为ChoiceField类型,用生成下拉框
    person = forms.ChoiceField(choices=choices, label='姓名') # choices=choices下拉框

表单类中定义的字段和html标签的比较

字段label转化为label标签, forms.CharField转化为
input type="text",job的命名转换为<input>控件的参数name,表单字段的max_length变为input控件的maxlength参数

# 表单类VocationForm的表单字段job
job = forms.CharField(max_length=20, label='职位')
# html标签
<tr><th><label for="id_job">职位:</label></th><td><input type="text" name="job" maxlength="20" required id="id_job"></td></tr>

form源码分析

表单的定义过程、表单的字段类型和表单字段的参数类型是表单的核心功能。
表单的定义过程
继承自两个类,具体可看源码位置:django/forms/forms.py

class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):

表单的属性和方法;源码位置:django/forms/forms.py

#BaseForm中定义的属性
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
#表单的方法
errors():验证表单数据是否存在异常,若存在,则获取异常信息,异常信息可设为字典或json格式
is_valid():验证表单数据是否存在异常,若存在,则返回false,否则返回true
as_table():将表单字段以html的<table>标签生成网页表单
as_ul():将表单字段以html的<ul>标签生成网页表单
as_p():将表单字段以html的<p>标签生成网页表单
has_changed():对比用于提交的表单数据与表单初始化数据是否发送变化。

表单字段的参数类型;源码文件位置:django/forms/fields.py

#可在文件中查看
__all__ = (
    'Field', 'CharField', 'IntegerField',
    'DateField', 'TimeField', 'DateTimeField', 'DurationField',
    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    'SplitDateTimeField', 'GenericIPAddressField', 'FilePathField',
    'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField', 'UUIDField',
)

# 列举几个常用表单;反正都是为了在html页面上显示出这几类框:文本框;数值框;文本上传框;下拉框;复选框
CharField:文本框,参数max_length和min_length分别设置文本长度
IntegerField:数值框,参数max_value设置最大值,min_value设置最小值
FileField:文本上传框,参数max_length设置上传文件名的最大长度,参数allow_empty_file设置是否允许文件内容为空。
ChoiceField:下拉框,参数choice与元组形式表示,用于设置下拉框的选项列表
BooleanField:复选框,设有选项true和false,如果字段带有required=true,复选框就默认为true.

实例

from django import forms
from .models import *
from django.core.exceptions import ValidationError
# 自定义数据验证函数
def payment_validate(value): 
    if value > 30000:
        raise ValidationError('请输入合理的薪资')

class VocationForm(forms.Form): # form.Form的字段和models中的字段类似
    job = forms.CharField(max_length=20, label='职位') 
    # 设置字段参数widget、error_messages
    # 参数widwget必须是个forms.widgets对象,而widwget的对象必须和表单字段类型相互对应,对应该则分为4大类,该定义的函数中可以传入css样式的class,id等属性
    title = forms.CharField(max_length=20, label='职称', #下面这句话啥意思
                            # 以修饰的css样式为先,但它这里还能更改html表单的类型,TextInput表述文本框,若是charfield显示下拉框,优先使用文本框
                            widget=forms.widgets.TextInput(attrs={'class': 'c1'}), # 设置表单的css样式,TextInput表示文本输入框,要和charfield输入样式相同
                            error_messages={'required': '职称不能为空'},) # 设置验证失败后的参数信息,字典的键为表单的参数名称,字典的值为错误信息
    # 设置字段参数validators;validators参数一定要列表形式么;该参数是自定义验证函数
    payment = forms.IntegerField(label='薪资',validators=[payment_validate]) # IntegerField字段还可以添加额外验证函数
    # 设置下拉框的值
    # 查询模型PersonInfo的数据
    value = PersonInfo.objects.values('name')
    # 将数据以为列表格式表示,列表元素为元组格式
    choices = [(i+1, v['name']) for i, v in enumerate(value)] # enumerate()生成(索引,值)的列表
    # 表单字段设为ChoiceField类型,用生成下拉框
    person = forms.ChoiceField(choices=choices, label='姓名')

    # 自定义表单字段title的数据清洗(修改该字段的返回值)
    # 最好函数名要与获取的表单字段名一样,方便阅读
    def clean_job(self): # 函数应该是自定义的,并不是,返回值的话会返回给该字段
        # 获取字段title的值
        # 类可以调用方法中的属性,是由于有self么
        # 提取的是表单字段的属性
        data = self.cleaned_data['job'] # cleaned_data是默认的属性
        return '初级' + data  # 通过views.py返回给终端

ModelForm源码分析

djano/forms/models.py
主要类
class BaseModelForm:中
表单类ModelForm于模型之间没有直接的数据交互,模型表单与模型之间的数据交互是由函数modelform_factory实现的,该函数将自定义的模型表单与模型进行绑定,从而实现两者之间的数据交互。

模型表单的主要方法

clean():重写父类BaseForm的clean()方法,并将属性_validate_unique设为True。
validate_unique():验证表单数据是否存在异常
_save_m2m():将带有多对多关系的模型表单保存到数据库里
save():将模型表单的数据保存到数据库里。如果参数commit为True,就直接保存在数据库;否则生成数据库实例对象。

函数modelform_factory与类ModelForm定义在同一个源码文件中

模型表单的主要属性

model:必需属性,用于绑定Model对象
fields=None,:可选属性,设置模型内那些字段转换成表单字段,默认值为None,代表所有的模型字段,也可以将属性值设为"__all__",同样表示所有的模型字段。若只需部分模型字段,则将模型字段写入一个列表或一个元组里,再把该列表或元组作为属性值。
exclude=None:与field相反,禁止模型字段转换成表单字段。属性值以列表或元组表示,若设置了该属性,则属性fields无需设置。
widgets=None:可选属性,设置表单字段的参数widget,属性值以字典表示,字典的键为模型字段
localized_fields=None:可选参数,将模型字段设为本地化的表单字段,常用于日期类型的模型字段。                     
help_texts=None:可选属性,设置表单字段的参数help_text.
error_messages=None:可选属性,设置表单字段的参数error_messages
field_classes=None:可选属性,将模型字段重新定义,默认情况下,模型字段与表单字段遵从Django内置的转换规则。

模型字段与表单字段的转换规则

待补

实例

from django import forms
from .models import *

class VocationForm(forms.ModelForm):
    # 添加模型外的表单字段;在模型已有的字段下再添加字段;添加的html位置在表单最下方
    LEVEL = (('L1', '初级'),
             ('L2', '中级'),
             ('L3', '高级'),)
    level = forms.ChoiceField(choices=LEVEL, label='级别') # 看来choices的前缀L1,L2之类的就是索引
    # 模型与表单设置
    class Meta:
        # 绑定模型
        model = Vocation
        # 以下属性都是函数modelform_factory的属性
        # fields属性用于设置转换字段,'__all__'是将全部模型字段转换成表单字段
        # fields = '__all__'
        # fields = ['job', 'title', 'payment', 'person']
        # exclude用于禁止模型字段转换表单字段
        exclude = []
        # labels设置HTML元素控件的label标签
        labels = {
            'job': '职位',
            'title': '职称',
            'payment': '薪资',
            'person': '姓名'
        }
        # 定义widgets,设置表单字段的CSS样式
        widgets = {
            'job': forms.widgets.TextInput(attrs={'class': 'c1'}),
        }
        # 重新定义字段类型
        # 一般情况下模型字段会自动转换成表单字段
        field_classes = {
            'job': forms.CharField
        }
        # 帮助提示信息
        help_texts = {
            'job': '请输入职位名称'
        }
        # 自定义错误信息
        error_messages = {
            # __all__设置全部错误信息
            '__all__': {'required': '请输入内容',
                        'invalid': '请检查输入内容'},
            # 设置某个字段的错误信息
            'title': {'required': '请输入职称',
                      'invalid': '请检查职称是否正确'}
        }
    # 自定义表单字段payment的数据清洗
    def clean_payment(self):
        # 获取字段payment的值
        data = self.cleaned_data['payment'] + 1
        return data

表单与模型表单小结

表单forms的属性和方法的源文件

django/forms/forms.py
主要类class BaseForm: (类中)
属性调用不加(),函数调用加()


表单字段类型和模型字段类型类似
源码位置django/forms/fields.py

视图里使用form

属性
prefix

prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
            # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix

initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据,把数据库中的数据写到表单上

无论是统计字符串还是列表
xx.count(传入统计的参数)

filter查询字段为0的情况下,就无法使用update,需要使用get_or_create(d),除主键外,只要有一个数据不同就插入
create(
d),直接插入

详情实例待补

#这么多没写,不做笔记,一点也不记得了
from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
    # GET请求
    if request.method == 'GET':
        id = request.GET.get('id', '')
        if id:
            d = Vocation.objects.filter(id=id).values() # 获取数据库数据
            d = list(d)[0]
            print(d)
            d['person'] = d['person_id'] # 增加person键值对
            # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
            i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
            # 将参数i传入表单VocationForm执行实例化
            print(i)
            v = VocationForm(**i) # 这是把数据库中的数据写到表单上
        else: # 不带get请求参数,并设置参数prefix,这参数的作用是
            # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
            # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
            v = VocationForm(prefix='vv')
        return render(request, 'index.html', locals())
    # POST请求
    else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
        # 由于在GET请求设置了参数prefix
        # 实例化时必须设置参数prefix,否则无法获取POST的数据
        v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
        if v.is_valid():
            # 获取网页控件name的数据
            # 方法一
            title = v['title']
            # 方法二
            # cleaned_data将控件name的数据进行清洗
            ctitle = v.cleaned_data
            print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
            # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
            id = request.GET.get('id', '')
            d = v.cleaned_data # 获取数据
            # count统计的话一定要传递参数
            print(d['person']) # 这是多的
            # 外键要特别加
            d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
            # 删除字典中的person字段
            del d['person']
            # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
            # Vocation.objects.create(**d) # 更新数据需以字典显示
            # 不这样更新,这样更新太难受了,常报错
            # 是这个Vocation这个模型有毒把
            # result = Vocation.objects.filter(id=id) # 提取所有的id字段
            # result = False
            # if not result:
            #     v.save()
            # else:
            #     return int(value)
# ValueError: invalid literal for int() with base 10: ''
            # Vocation.objects.filter(id=id).update(**d)
            # 可能是多了一个person字段
            Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
            return HttpResponse('提交成功')
        else:
            # 获取错误信息,并以json格式输出
            error_msg = v.errors.as_json()
            print(error_msg)
            return render(request, 'index.html', locals())

视图里使用ModelForm

表单类Form和模型实现数据交互最主要的问题是表单字段和模型字段的匹配性,如果将表单类Form改为ModelForm,就无需考虑字段匹配性的问题。
最好使用模型表单,表单类提取数据保存太难受了,我的报错问题:

ValueError: invalid literal for int() with base 10: ''

# commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
                # commit=True则直接将表单数据保存到数据库中

实例

from django.shortcuts import render
from django.http import HttpResponse
from .form import *
from .models import *
def index(request):
    # GET请求
    if request.method == 'GET':
        id = request.GET.get('id', '')
        if id:
            i = Vocation.objects.filter(id=id).first() #这里id提取的是int类型么
            # 将参数i传入表单VocationForm执行实例化
            v = VocationForm(instance=i, prefix='vv') # instance参数为空的话,则返回一个新的表单
        else:
            v = VocationForm(prefix='vv')
        return render(request, 'index.html', locals())
    # POST请求
    else:
        # 由于在GET请求设置了参数prefix
        # 实例化时设置参数prefix,否则无法获取POST的数据
        v = VocationForm(data=request.POST, prefix='vv') #实例化的模型字段
        # is_valid()会使字段payment自增加10
        if v.is_valid():
            # 根据请求参数id查询模型数据是否存在
            id = request.GET.get('id')
            result = Vocation.objects.filter(id=id) # 提取所有的id字段
            # 数据不存在,则新增数据
            if not result:
                # 数据保存方法一
                # 直接将数据保存到数据库
                # v.save()
                # 数据保存方法二
                # 将save的参数commit=False
                # 生成数据库对象v1,修改v1的属性值并保存
                # commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
                # commit=True则直接将表单数据保存到数据库中
                v1 = v.save(commit=False) # 直接保存字段
                v1.title = '初级' + v1.title
                v1.save()
                # 数据保存方法三
                # save_m2m()方法用于保存ManyToMany的数据模型
                # v.save_m2m()
                return HttpResponse('新增成功')
            # 数据存在,则修改数据
            else:
                d = v.cleaned_data
                d['title'] = '中级' + d['title']
                result.update(**d)
                print(d)
                return HttpResponse('修改成功')
        else:
            # 获取错误信息,并以json格式输出
            error_msg = v.errors.as_json()
            print(error_msg)
            return render(request, 'index.html', locals())

8.4视图里使用Form

from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
    # GET请求
    if request.method == 'GET':
        id = request.GET.get('id', '')
        if id:
            d = Vocation.objects.filter(id=id).values() # 获取数据库数据
            d = list(d)[0]
            print(d)
            d['person'] = d['person_id'] # 增加person键值对
            # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
            i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
            # 将参数i传入表单VocationForm执行实例化
            print(i)
            v = VocationForm(**i) # 这是把数据库中的数据写到表单上
        else: # 不带get请求参数,并设置参数prefix,这参数的作用是
            # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
            # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
            v = VocationForm(prefix='vv')
        return render(request, 'index.html', locals())
    # POST请求
    else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
        # 由于在GET请求设置了参数prefix
        # 实例化时必须设置参数prefix,否则无法获取POST的数据
        v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
        if v.is_valid():
            # 获取网页控件name的数据
            # 方法一
            title = v['title']
            # 方法二
            # cleaned_data将控件name的数据进行清洗
            ctitle = v.cleaned_data
            print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
            # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
            id = request.GET.get('id', '')
            d = v.cleaned_data # 获取数据
            # count统计的话一定要传递参数
            print(d['person']) # 这是多的
            # 外键要特别加
            d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
            # 删除字典中的person字段
            del d['person']
            # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
            # Vocation.objects.create(**d) # 更新数据需以字典显示
            # 不这样更新,这样更新太难受了,常报错
            # 是这个Vocation这个模型有毒把
            # result = Vocation.objects.filter(id=id) # 提取所有的id字段
            # result = False
            # if not result:
            #     v.save()
            # else:
            #     return int(value)
# ValueError: invalid literal for int() with base 10: ''
            # Vocation.objects.filter(id=id).update(**d)
            # 可能是多了一个person字段
            Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
            return HttpResponse('提交成功')
        else:
            # 获取错误信息,并以json格式输出
            error_msg = v.errors.as_json()
            print(error_msg)
            return render(request, 'index.html', locals())

8.5### 表单的数据提取判断等逻辑都值得好好推敲
表单类的定义可参考前面的表单类

from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
    # GET请求
    if request.method == 'GET':
        id = request.GET.get('id', '')
        if id:
            d = Vocation.objects.filter(id=id).values() # 获取数据库数据
            d = list(d)[0]
            print(d)
            d['person'] = d['person_id'] # 增加person键值对
            # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
            i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
            # 将参数i传入表单VocationForm执行实例化
            print(i)
            v = VocationForm(**i) # 这是把数据库中的数据写到表单上
        else: # 不带get请求参数,并设置参数prefix,这参数的作用是
            # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
            # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
            v = VocationForm(prefix='vv')
        return render(request, 'index.html', locals())
    # POST请求
    else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
        # 由于在GET请求设置了参数prefix
        # 实例化时必须设置参数prefix,否则无法获取POST的数据
        v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
        if v.is_valid():
            # 获取网页控件name的数据
            # 方法一
            title = v['title']
            # 方法二
            # cleaned_data将控件name的数据进行清洗
            ctitle = v.cleaned_data
            print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
            # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
            id = request.GET.get('id', '')
            d = v.cleaned_data # 获取数据
            # count统计的话一定要传递参数
            print(d['person']) # 这是多的
            # 外键要特别加
            d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
            # 删除字典中的person字段
            del d['person']
            # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
            # Vocation.objects.create(**d) # 更新数据需以字典显示
            # 不这样更新,这样更新太难受了,常报错
            # 是这个Vocation这个模型有毒把
            # result = Vocation.objects.filter(id=id) # 提取所有的id字段
            # result = False
            # if not result:
            #     v.save()
            # else:
            #     return int(value)
# ValueError: invalid literal for int() with base 10: ''
            # Vocation.objects.filter(id=id).update(**d)
            # 可能是多了一个person字段
            Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
            return HttpResponse('提交成功')
        else:
            # 获取错误信息,并以json格式输出
            error_msg = v.errors.as_json()
            print(error_msg)
            return render(request, 'index.html', locals())

实现表单数据与模型交互需要注意以下事项

表单字段最好与模型字段相同,否则两种再进行数据交互时,不许将两者的字段进行转化
使用同一表单并且需要多次实例化表单时,处理参数initial和data的数据不同之外,其他参数设置必须相同,否则无法接收上一个表单对象所传递的数据信息。
参数initial是表单实例化的初始化数据,它只适用于模型数据传递给表单,再由表单显示在网页上;参数data是在表单实例化后,再将数据传递给实例化对象,只适用于表单接收http请求的请求参数。
参数prefix设置表单的控件属性name和id的值;若在一个网页里使用同一个表单类生成多个不同的网页表单,参数prefix可区分每个网页表单,则在接收或设置某个表单数据时与其他的表单数据混淆。

模型表单ModelForm实现数据保存只有save()和save_m2m()两种方法。使用save()保存数据时,参数commit的值会影响数据的保存方式。如果参数Commit为True,就直接将表单数据保存到数据库;如果参数commit为False,就会生成一个数据库对象,然后可以对该对象进行增、删、改、查等数据操作。再将修改后的数据保存到数据库。
save()只适合将数据保存在非多对多关系的数据表中,而save_m2m()只适合将数据保存在多对多关系的数据表中。

原文地址:https://www.cnblogs.com/wkhzwmr/p/15657317.html