django-Form

参考博客:http://www.cnblogs.com/wupeiqi/articles/5246483.html

django的form主要有以下几个功能:

  • 生成html标签
  • 表单数据验证(显示错误信息)
  • HTML-form保存上次提交的信息
  • 初始化显示页面的内容

基本应用

1.创建form类

from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
    user = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})
    )
 
    gender = fields.ChoiceField(
        choices=((1, ''), (2, ''),),
        initial=2,
        widget=widgets.RadioSelect
    )
 
    city = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
 
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

2.View函数处理

from django.shortcuts import render, redirect
from .forms import MyForm
 
 
def index(request):
    if request.method == "GET":
        obj = MyForm()
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        obj = MyForm(request.POST, request.FILES)
        if obj.is_valid():
            values = obj.clean()
            print(values)
        else:
            errors = obj.errors
            print(errors)
        return render(request, 'index.html', {'form': obj})
    else:
        return redirect('http://www.google.com')

3.生成HTML

<form action="/" method="POST" enctype="multipart/form-data">
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.gender }} {{ form.gender.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
    <p>{{ form.pwd }} {{ form.pwd.errors }}</p>
    <input type="submit"/>
</form>

 

From基本知识点

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

1.django内置字段

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    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类型
    ...

 2.django内置插件

TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

常用选择插件

# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单radio,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )
 
# 单select,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单select,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.Select
# )
 
# 多选select,值为列表
# user = fields.MultipleChoiceField(
#     choices=((1,'上海'),(2,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )
 
 
# 单checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )
 
 
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )
常用选择插件

Form之select操作

1. form操作动态select数据

from django import forms
from django.forms import fields,widgets
class LoginCheck(forms.Form):
    user = fields.CharField(
          widget = widgets.TextInput  
          )

    pwd = fields.CharField(
           min_length=6
           max_length=18
           widget= widgets.PasswordInput
            )       

    user_type = fields.ChoiceField(
           #choices =((1,'超级用户'),(2,"普通用户"),)
           choices = models.UserType.objects.values_list('id','name')
           widget= widgets.Select  
) 


def index(request):
      dic = {
       "user":"张三",                  
       "pwd":123,
       "user_type":1
       }
      obj = LoginCheck(initial=dic);#注意此处是个字典,数据的初始化操作
      
      return render(request,"index.html",{'obj':obj})

HTML
    <p>{{obj.user}}</p>
    <p>{{obj.pwd}}</p>
    <P>{{obj.user_type}}</P>

   以上方式可以动态的从数据库中获取数据,但是要注意的是,如果数据库中的数据更新时,需要重启系统才会进行更新,重新登入网页时不会更新的,主要因为变量,user,pwd,user_type是类变量,当解释器解释时,类变量会放到内存的一块空间存储,名声实例时,不会更新类变量,所以只能通过重启系统来进行数据库数据的更新读取。为了解决此问题。我们可以在实例声明时进行以下操作来手动进行更新数据库中的数据,操作如下:

class form_acion(forms.From):
    user_type = fields.ChoiceField(
        choices=models.Usertype.objects.values_list('id', 'name'),
        widget=widgets.Select,
    )
    .......


def index(request):
         
     dic={
     “user_type”:1
     }
     obj = form_action(initial=dic)
     obj.fields['user_type'].choices=models.Usertype.objects.values_list('id','name')

     render(request,'index.html',{"obj":obj}

实例声明时更新数据
1.实例化动态加载select

  上述方式虽然可以解决动态加载问题,但是每次需要在函数中来添加,这样增加了工作量,我们可以将函数放置于__init__构造函数中,构造函数__init__函数会把类里的字段封装到obj.fields里面(里面有user、pwd、user_type三个字段)方式如下:

#字段,如果给不参数widget赋值,默认是Select,单列
user_type = fields.ChoiceField(
        choices=models.Usertype.objects.values_list('id', 'user_type'),
        widget=widgets.Select,
    )
#字段,给参数widget赋值SelectMultiple,多列
user_type = fields.ChoiceField(
        choices=models.Usertype.objects.values_list('id', 'user_type'),
        widget=widgets.SelectMultiple,
    )
#字段,给参数widget赋值Select,单列
user_type = fields.MultipleChoiceField(
        choices=models.Usertype.objects.values_list('id', 'user_type'),
        widget=widgets.Select
    )
#字段 如果不给参数widget赋值,默认是SelectMultiple,多列
user_type = fields.MultipleChoiceField(
        choices=models.Usertype.objects.values_list('id', 'user_type'),
        widget=widgets.SelectMultiple
    )


def __init__(self, *args, **kwargs):
     super(P, self).__init__(*args, **kwargs)
#写法1
self.fields['user_type'].choices=models.Usertype.objects.values_list('id', 'user_type')
#写法2
self.fields['user_type'].widget.choices=models.Usertype.objects.values_list('id', 'user_type')
2.__init__构造函数中动态加载select数据

  django也为我们提供了动态加载的方式,但是django实现的并不完善,使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django.forms import models as form_models

#方式1,单选
user_type = form_models.ModelChoiceField(queryset=models.Usertype.objects.all(), empty_label='用户类型')
#方式2,多选
user_type = form_models.ModelMultipleChoiceField(queryset=models.Usertype.objects.all())
model-form动态加载select数据

ModelChoiceField参数格式

ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段(html源码中value不同)
    limit_choices_to=None      # ModelForm中对queryset二次筛选

2.Form-内置钩子(数据验证)

普通数据验证index(request)

elif request.method =='POST':
        obj = F(request.POST)# 将post的数据放入验证
        r = obj.is_valid()#判断校验结果
        print(r)
        if r:
            print(obj.cleaned_data)#返回值是个字典{'pwd': '123', 'email': '123123@daid.com', 'usr': 'root'}
        else:
            print(obj.errors)#拿到所有错误信息,返回值<ul class="errorlist"><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul>
                            #可以将错误返回到前端
            print(obj.errors.as_json())#json转换后的值
HTML
<form action="/index" method="post">
    {% csrf_token %}
   {#由于obj.errors.usr,pwd,user_type 是个列表,{"pwd": [{"code": "min_length", 
    "message": "\u5bc6\u7801\u957f\u5ea6\u4e0d\u80fd\u5c0f\u4e8e6\u4e2a\u5b57\u7b26"}]},索引使用.0#}
    <p>{{obj.usr}}{{ obj.errors.usr.0 }}</p>
    <p>{{obj.pwd}}{{ obj.errors.pwd.0 }}</p>
    <p>{{obj.user_type}}{{ obj.errors.user_type.0 }}</p>
    <P><input type="submit" value="登入"></P>
</form>    

自定义验证方式:

  1.可以使用参数:validators=[], 自定义验证规则

  方式1

#模块导入
from django.core.validators import  RegexValidator

class ActionCheck(forms.Form):
  phone = fields.CharField(
     validators=[RegexValidator(r'^[0-9]+$', '电话必须是数字'), RegexValidator(r'^181[0-9]+$', "电话必须以181开头")]
  widget=widgets.TextInput()
  )
validatots自定义验证规则

  方式2

# 自定义验证规则
def mobile_validate(value):
    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 ActionCheck(forms.Form):
    phone = fields.CharField(
        validators=[mobile_validate, ],
        widget=widgets.TextInput()
validatots自定义验证规则2

  2.当格式验证没有问题,是否可以将数据直接写入数据库?当然不能,以上方式只为验证数据格式,但是无法验证数据库数据一致性,如果是添加账户密码,数据库中如有相同数据,上述方式没有办法验证,这时我们可以使用以下方法,对数据进行进一步验证。

class FormLogin(forms.Form):
    username = fields.CharField(
        error_messages={'require': "账号不能为空哦"},
        widget=widgets.TextInput()
    )

    password = fields.CharField(
        error_messages={'require': "密码不能为空哦"},
        widget=widgets.PasswordInput()
    )

    def clean_username(self):
        value = self.cleaned_data['username']
        #此处可以添加数据库读取数据操作
        if 'root' == value:
            return value
        else:
            raise ValidationError("账号错误", code='user_error')

    def clean_password(self):

        value = self.cleaned_data['password']
        #此处可以添加数据库读取操作
        if '123456' == value:
            return value
        else:
            raise ValidationError('密码错误', code='pwd_error')

#验证顺序:
#1、username
#2、clean_username
#3、password
#4、clean_password
Clean_字段名()方法

  上面的方法我们只能进行局部验证,要想进行全局验证,有以下方法:

  • clean:全局验证
  • _post_clean:全局验证,验证顺序在clean之后
class FormLogin(forms.Form):
    def clean(self):
        if self.cleaned_data.get('usernmae') and self.cleaned_data.get('password'):
            pass
            # return self.cleaned_data
        else:
            print('error')
            raise ValidationError("账号或者密码错误哦",code='user_pwd_eorror')

    def _post_clean(self):
        pass
clean和_post_clean

  验证顺序:

  • 第一个正则表达式,执行钩子函数clean_字段名
  • 第二个正则,钩子函数clean_字段名
  • 都执行完后执行clean方法
  • clean执行完后执行_post_clean

   追根朔源:is_vaild,找到errors,找到full_clean,所有钩子函数都是基于full_clean()执行的。

obj.errors
{
    'user':[{'code':'required','message':'xxxx'}],
    'pwd':[{'code':'required','message':'xxxx'}],
    # 上面每个字段的错误信息放在里面,那clean总的错误信息放在哪里?
    '__all__':[],  # 特殊的整体错误信息,放在这里
    # NON_FIELD_ERRORS:[], 和 __all__ 一个意思。
}
clean全局错误信息存放于__all__中

3.Form-数据序列化

  主要函数包括:

    as_json()--序列化成json格式

    as_data()--序列化成字典格式

  html数据提交方式主要有:submit,ajax两种,主要应用于(新url,模态框。。)

  以ajax提交为例:

class FormHobby(forms.Form):

    hobby = fields.CharField(
        widget=widgets.TextInput(
            attrs={"placeholder": "爱好"}
        )
    )
    def clean_hobby(self):
        print('hobby', self.cleaned_data)
        value = self.cleaned_data.get('hobby')
        v = models.Hobby.objects.filter(hobby_name=value)
        if v:
        # 如果v存在,则报错
            raise ValidationError('爱好已经存在')
        else:
            models.Hobby.objects.create(hobby_name=value)
            return value

def hobby_add(request):
    import json
    res = {'status': True, 'error': None, 'data': None}
    if request.method == 'POST':
        obj = FormHobby(request.POST)
        if obj.is_valid():#如果数据验证正确
            print(obj.cleaned_data)
        else:
            res['status'] = False
            res['error'] = obj.errors.as_json()
            print(obj.errors)
        return HttpResponse(json.dumps(res))
ajax提交-views
        $(function(){
            $('#i1').click(function(){
                    $('.modal,.cach').removeClass("hide")
                });

            $('#add_cancel').click(function(){
                    $('.modal,.cach').addClass("hide")
                });

            function csrfSafeMethod(method){
                    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
            }

            $.ajaxSetup({
                        beforeSend:function(xhr,settings){
                            if(!csrfSafeMethod(settings.type)&&!this.crossDomain) {
                                xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'))
                            }
                        }
                    }
            );

            $("#add_action").click(function(){
                    $.ajax({
                        url: '/hobby_add',
                        type:'POST',
                        data:$("#form_add").serialize(),
                        dataType:'JSON',
                        traditional:true,
                    success:function(obj){
                        if(obj.status == true)
                        {
                            location.reload()
                        }
                        else{
                            $("#shsh").text(JSON.parse(obj.error)['hobby'][0]['message']);
                        }
                    },
                        error:function(){}
                    });
                });
        });
HTML-ajax提交

  上述方式可以执行,使用的是as_json方式对错误信息进行格式化, 如果验证错误,前端需要进行两次json的反解才可以拿到错误数据,这样操作麻烦,工作量大。

  通过源码json.dumps,我们可以搜索追朔到cls = JSONDecoder,JSONDecoder一个类型校正类,用于排除json不支持的类,JSONDecoder有一个default用于做json格式化数据监测,源码如下:

 

    if cls is None:
        cls = JSONEncoder
    return cls(
        skipkeys=skipkeys, ensure_ascii=ensure_ascii,
        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
        separators=separators, default=default, sort_keys=sort_keys,
        **kw).encode(obj)
json格式化源码

  我们可以通过重写JSONDecoder的default方法来进行数据json格式化:

import json
from django.core.exceptions import ValidationError
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        print(field)
        if isinstance(field, ValidationError):
            return {'code': field.code, 'messages': field.messages}
        else:
            return json.JSONEncoder.default(self, field)
#isinstance(object, classinfo)如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False。
#example:isinstance(a,dict)  判断对象a是否为字典,如果为真,会打印True,如为假,打印False。

def hobby_add(request):
    import json
    res = {'status': True, 'error': None, 'data': None}
    if request.method == 'POST':
        obj = FormHobby(request.POST)
        if obj.is_valid():#如果数据验证正确
            print(obj.cleaned_data)
        else:
            res['status'] = False
            res['error'] = obj.errors.as_data()
            print('obj.errors.as_data()---->', obj.errors.as_data())
            print('obj.errors.as_json()---->', obj.errors.as_json())
            # for k,v in obj.errors.as_data().items():
            #     print(k,v)
            # print(obj.errors)
            #但是as_data是无法通过json来序列化的。
        result = json.dumps(res, cls=JsonCustomEncoder)
        return HttpResponse(json.dumps(result))
json格式化+as_data

  以上方式,经过自己验证,貌似还有问题......  

  既然我们提到了json的格式化,那我们就在这里对json自定制格式做一个扩展:

  日期格式自定制json化:

        import json
        from datetime import date
        from datetime import datetime

        class JsonCustomEncoder1(json.JSONEncoder):

            def default(self, field):

                if isinstance(field, datetime):
                    return field.strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(field, date):
                    return field.strftime('%Y-%m-%d')   # 转成字符串类型
                else:
                    return json.JSONEncoder.default(self, field)

        v = {'k':'123', "k1": datetime.now()}
        ds = json.dumps(v, cls=JsonCustomEncoder1)
        print(ds)
日期格式json格式化

  QuerySet自定制json化:

from django.core import serializers

v = models.tb.objects.all()
data = serializers.serialize("json", v)
QuerySet的json格式化方式1--django提供
import json 
from datetime import date 
from datetime import datetime 

class JsonCustomEncoder(json.JSONEncoder): 

    def default(self, field): 

        if isinstance(field, datetime): 
            return field.strftime('%Y-%m-%d %H:%M:%S') 
        elif isinstance(field, date): 
            return field.strftime('%Y-%m-%d') 
        else: 
            return json.JSONEncoder.default(self, field) 


v = models.tb.objects.values('id','name','ctime')
v = list(v)  # 把(类似列表的queryset类型)转换为列表
v = json.dumps(v,cls=JsonCustomEncoder)  # 这里cls只对ctime操作。
# 如果没有ctime这个类型,只写上边的三句就可以实现
QuerySet的json格式化方式2

  持续更新中......

  参考:https://www.cnblogs.com/lgeng/p/7366370.html

  参考:https://blog.csdn.net/fgf00/article/details/54614706

  参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html

  

 

posted @ 2019-04-04 21:42  土味程序员  阅读(169)  评论(0编辑  收藏  举报