二十二、Django之Form组件

Django的Form:
1、对用户请求的验证
2、生成HTML代码

a、创建一个类
b、类中创建字段(包含正则表达式)
c、Get
   a) Obj = Fr()
      obj.user=> 自动生成HTML
d、POST
   a) Obj = Fr(request,POST)
      i. If obj.is_valid():
            Obj.cleaned_data
         Else:
            Obj.errors
           Return .....obj

1、Form的使用

from django.shortcuts import render, HttpResponse
from django import forms
from django.forms import fields
from django.forms import widgets

class TestForm(forms.Form):
    user=fields.CharField(
        required=True, #是否必填
        max_length=12, #最大长度
        min_length=3, #最小长度
        error_messages={
            'required':'用户名不能为空',
            'max_length':'太长了',
            'min_length':'太短了',
        }, #错误提示
        # widget=widgets.Select(), #定制HTML插件
        label='用户名',
        initial='xx',
        help_text='helptext',
        # show_hidden_initial=True,
        # validators=[], #自定制验证规则
        # disabled=False,
        label_suffix='->',
    )
    age=fields.IntegerField(
        label='年龄',
        max_value=12,
        min_value=5,
    )
    email=fields.EmailField(
        label='邮箱',
    )

    img=fields.ImageField()

    city=fields.ChoiceField(
        choices=[(1,'北京'),(2,'上海'),(3,'深圳')],
        initial=3, #默认值
    )

    city2=fields.CharField(
        widget=widgets.Select(choices=[(1,'北京'),(2,'上海'),(3,'深圳')])
    )

    city3 = fields.IntegerField(
        widget=widgets.Select(choices=[(1, '北京'), (2, '上海'), (3, '深圳')])
    )

    hobby=fields.MultipleChoiceField(
        choices=[(1,'跑'),(2,'跳'),(3,'游'),(4,'飞')],
        initial=[1,3,4],
    )

    anotherCity=fields.TypedChoiceField(
        coerce=lambda x:int(x), # 设置coerce函数,将输出的值转换成需要的类型
        choices=[(1, '北京'), (2, '上海'), (3, '深圳')],
        initial=3,  # 默认值
    )

    fp=fields.FilePathField(
        path='app01'
    )

def test(request):
    if request.method=='GET':
        obj=TestForm()
        return  render(request,'test.html',{'obj':obj})
    else:
        obj=TestForm(request.POST,request.FILES)
        obj.is_valid()
        print(obj.cleaned_data)
        return render(request,'test.html',{'obj':obj})
<body>
  <form action="/test/" method="post" novalidate enctype="multipart/form-data">
    <p>{{ obj.user.label }}{{ obj.user }}</p>
    <p>{{ obj.age.label }}{{ obj.age }}{{ obj.errors.age.0 }}</p>
    <p>{{ obj.email.label }}{{ obj.email }}</p>
    <p>{{ obj.img.label }}{{ obj.img }}</p>
    <p>{{ obj.city.label }}{{ obj.city }}</p>
    <p>{{ obj.city2.label }}{{ obj.city2 }}</p>
    <p>{{ obj.city3.label }}{{ obj.city3 }}</p>
    <p>{{ obj.hobby.label }}{{ obj.hobby }}</p>
    <p>{{ obj.anotherCity.label }}{{ obj.anotherCity }}</p>
    <p>{{ obj.fp.label }}{{ obj.fp }}</p>
    <input type="submit" value="提交">
  </form>
</body>

form元素的novalidate标识:取消浏览器对数据的验证,交由后台验证数据。
models.UserInfo.objects.create(fm_obj.cleaned_data) 😗*

# Form与Model的字段名保持一致
from django.db import models

class UserInfo(models.Model):
    username=models.CharField(max_length=32)
    email=models.EmailField(max_length=32)
-----------
from  django import forms as dforms
from django.forms import fields

class UserForm(dforms.Form):
    username=fields.CharField()
    email=fields.EmailField()

-----------
# form数据与model数据就方便转换
def add_user(request):
    if request.method == 'GET':
        fr_obj = UserForm()
        return render(request,'add_user.html',{'fm_obj':fr_obj})
    else:
        fm_obj = UserForm(request.POST)
        if fm_obj.is_valid():
            models.UserInfo.objects.create(**fm_obj.cleaned_data)
            return redirect('/users/')
        else:
            return render(request,'add_user.html',{'fm_obj':fm_obj})
def edit_user(request,nid):
    if request.method == 'GET':
        data = models.UserInfo.objects.filter(id=nid).first()
        fm_obj = UserForm({'username':data.username,'email':data.email})
        return render(request, 'edit_user.html', {'obj': fm_obj, 'nid': nid})
    else:
        obj = UserForm(request.POST)
        if obj.is_valid():
            models.UserInfo.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect('/users/')
        else:
            return render(request,'edit_user.html',{'obj':obj,'nid':nid})

2、进阶

# 表单数据实时更新
from app01 import models
from django.forms.models import ModelChoiceField
class LoveForm(forms.Form):
    # price user_id 为静态字段,在程序加载时创建。
    # 因此程序启动之后,UserInfo数据库的数据如果有变化,user_id的选项并不会实时变化。
    # 必须重启后台程序。这样设计是不合理的。
    price=forms.IntegerField()
    user_id=forms.ChoiceField(
        #重写了__init__方法后,这里choices不需要赋值了
        # choices=models.UserInfo.objects.values_list('id','username')
    )

    # 继承初始化方法,在每次网络请求时创建Form对象的时候通过self.fields['user_id'].widget.choices重新从数据库取值
    def __init__(self,*args,**kwargs):
        super(LoveForm,self).__init__(*args,**kwargs)

        self.fields['user_id'].widget.choices=models.UserInfo.objects.values_list('id','username')
实时更新方式2(不推荐)
方式2:ModelChoiceField。
不推荐,耦合度高,需要写__str__方法来定选择框显示的内容
...
    user_id2=ModelChoiceField(
        queryset=models.UserInfo.objects.all(),
        to_field_name='id'
    )
...
class UserInfo(models.Model):
    ...
    def __str__(self):
        return self.username

3、Form的数据验证

class AjaxForm(forms.Form):
    username=forms.CharField()
    user_id=forms.ChoiceField(
        choices=[(0,'Java'),(1,'PHP'),(2,'Python'),(3,'GO')]
    )

#   自定义clean_字段名方法
    # 必须返回值self.cleadned_data['username']
    # 如果出错:raise ValidationError
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=v).count():
            # 报错
            # 自己详细错误信息
            from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
            raise ValidationError('用户名已存在')
        return v
#	数据整体验证 抛出的错误会放在__all__列表中,而非某字段名的列表中。
    def clean(self):
        value_dict=self.cleaned_data
        v1=value_dict.get('username')
        v2=value_dict.get('user_id')
        if v1=='root'and v2=='1':
            from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
            raise ValidationError('整体错误信息')
        return self.cleaned_data

def ajax(request):
    if request.method=='GET':
        obj=AjaxForm()
        return render(request,'ajax.html',{'obj':obj})
    else:
        import json
        obj=AjaxForm(request.POST)
        if obj.is_valid():
            return HttpResponse(json.dumps({'status':100,'message':obj.cleaned_data}))
        else:
            return HttpResponse(json.dumps({'status':111,'message':obj.errors}))

从Form的is_vaild()函数进入源码,可知有clean_***、clean等数据验证函数提供重写自定义。

4、as_p、as_ul、as_table

<body>
  // {{ form_obj.as_p }}
  // {{ form_obj.as_ul }}
  {{ form_obj.as_table }} 
</body>

这几种方式都可以直接生成页面。但推荐的是:

<body>
  <form action="/edit_user-{{ nid }}/" method="post" novalidate>
        <p>{{ obj.username }}{{ obj.errors.username.0 }}</p>
        <p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交"/>
    </form>
</body>

5、FileField、ImageField

使用这两个字段时,
form表单中 enctype="multipart/form-data",
view函数中 obj = MyForm(request.POST, request.FILES)

6、ComboField

多个验证
a = fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

7、FilePathField(但不建议使用):

关于ChoiceField

这个是下拉列表选择
fields.ChoiceField(choices=models.Article.type_choices)
这个只创建了ul 没有li
fields.ChoiceField(widget=widgets.RadioSelect(choices=models.Article.type_choices))
这个才会有radioSelect出来
fields.IntegerField(widget=widgets.RadioSelect(choices=models.Article.type_choices))

但如果这样
fields.ChoiceField(widget=widgets.RadioSelect)
在init()方法中
self.fields['a_type'].choices = models.Article.type_choices
这也有radioselect出来

一些实践中的记录

from  django import forms
from  django.forms import fields, widgets
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.core import validators
from repository import models

# Register your models here.

class MyMetaclass(widgets.MediaDefiningClass):
    """Collect Fields declared on the base classes."""
    def __new__(mcs, name, bases, attrs):
        # Collect fields from current class and remove them from attrs.
        # mcs:这个Meta的名字
        # name:类名,如果是RegisterForm的,就是RegisterForm,这时bases是 :(<class 'web.forms.MyForm'>,)
        #			如果是MyForm的,就是MyForm,这时bases是 :(<class 'django.forms.forms.BaseForm'>,)
        print("======>",attrs)   # attrs是字典,包含RegisterForm或MyForm中声明的字段和函数
        attrs['declared_fields'] = {
            key: attrs.pop(key) for key, value in list(attrs.items())
            if isinstance(value, fields.Field)
        } # 这里是遍历attrs,如果当中的value是Field类的实例(即声明的字段中有Field字段),就
        #将这个value移出来,放到key='declared_fields'的字典value中

        new_class = super().__new__(mcs, name, bases, attrs) #如果是MyForm,就实例MyForm;如果是RegisterForm,就实例RegisterForm
        print("====>", new_class.__mro__)  #是一个集合
        # MyForm: (<class 'web.forms.MyForm'>, <class 'django.forms.forms.BaseForm'>, <class 'object'>)
        # RegisterForm: (<class 'web.forms.RegisterForm'>, <class 'web.forms.MyForm'>, <class 'django.forms.forms.BaseForm'>, <class 'object'>)

        # Walk through the MRO.
        declared_fields = {}
        for base in reversed(new_class.__mro__):
            # Collect fields from base class.
            if hasattr(base, 'declared_fields'):
                declared_fields.update(base.declared_fields) #拿到声明的Field字段

            # Field shadowing.
            for attr, value in base.__dict__.items():
                if value is None and attr in declared_fields: #检查有没有重复的field字段?
                    declared_fields.pop(attr)

        new_class.base_fields = declared_fields  # 将拿到的声明Field字段赋值给base_fields
        new_class.declared_fields = declared_fields # 问题:打印了__dict__来,所有声明的field字段都被放
        #在declared_fields中了,因为在外面还可以直接将这些字段直接 . 出来,不需要:.declared_fields.*** ?
        # python中的确不能.出来了。但html模版中可以呀!应该是模版只读出这个字段定义了什么Field!
        return new_class

class MyForm(forms.BaseForm, metaclass=MyMetaclass): #为了试验,仿照forms.Form继承BaseForm和metaclass=MyMetaclass,MyMetaClass也仿照原版
    pass

class RegisterForm(MyForm):   #一般继承forms.Form就可以了。这里为了试验,继承自定义的
    username = fields.CharField(max_length=16, label='用户名')
    password = fields.Field(
        validators=(RegexValidator(regex='^.*(?=.{8,})(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&*? ]).*$',
                                   message="密码需包含数字、字母和特殊字符"),
                    validators.MaxLengthValidator(16)
                    ),
        widget=widgets.PasswordInput(),
        label='密码',
    )
    # password = fields.CharField(required=True, max_length=64, label='密码')
    confirm_password = fields.CharField(label='确认密码')
    email = fields.EmailField(label='邮箱')

    def clean_username(self):
        v = self.cleaned_data['username']
        if models.UserInfo.objects.filter(name=v):
            raise ValidationError('用户名已存在')
        return v

    def clean(self):
        p1 = self.cleaned_data.get('password')
        if p1 == None: return self.cleaned_data
        if p1==self.cleaned_data.get('confirm_password'):
            return self.cleaned_data #教程中 不用return 直接pass
        else: # NON_FIELD_ERRORS 在clean中raise的错误为NON_FIELD_ERRORS
            raise ValidationError("二次密码输入不一致")

    def __new__(cls, *args, **kwargs):
        for field in cls.base_fields.values():
            field.widget.attrs.update({'class':"form-control"}) # 添加bootstrap样式
        return forms.Form.__new__(cls)

后台验证了Form后,拿errors的一个记录:
errors = reduce(lambda x,y:x+y,form.errors.values()) #vlues()是 【【】】,reduce后是【】

class ArticleForm(forms.Form):
    title = fields.CharField(widget=widgets.TextInput(attrs={'class':'form-control', 'placeholder':'文章标题'}))
    summary = fields.CharField(widget=widgets.Textarea(attrs={'class':'form-control', 'placeholder':'文章简介', 'rows':'3'}))
    detail = fields.CharField(widget=widgets.Textarea(attrs={'class':'kind-content form-control'}))
    article_type = fields.IntegerField(widget=widgets.RadioSelect(choices=models.Article.type_choices))
    category_id = fields.ChoiceField(choices=(), widget=widgets.RadioSelect)
    tags = fields.MultipleChoiceField(choices=(),widget=widgets.CheckboxSelectMultiple)

    def __init__(self, request, *args, **kwargs):
        super(ArticleForm, self).__init__(*args, **kwargs)
        blog_id = request.session['UserInfo']['blog__id']
        self.fields['category_id'].choices = models.Category.objects.filter(blog__id=blog_id).values_list("id","caption")
        self.fields['tags'].choices = models.Tag.objects.filter(blog__id=blog_id).values_list("id","caption")
def new_article(request):
    if request.method=="POST":
        form = ArticleForm(request,request.POST)
        if form.is_valid():
            form.cleaned_data["blog_id"] = request.session['UserInfo']['blog__id']
            content = form.cleaned_data.pop('detail') #将models.Article没有的字段pop出来
            tags = form.cleaned_data.pop('tags')
            art = models.Article.objects.create(**form.cleaned_data)  # 字典传入的所有键值对,model必须能够都接收
            models.ArticleDetail.objects.create(**{'content':content, "article":art})
            tag_list = []
            for tag_id in tags:
                tag_id = int(tag_id)
                tag_list.append(models.Tag.objects.filter(blog=art.blog, id=tag_id).first())
            art.tag.add(*tag_list) # 多对多:添加
            return redirect(reverse('ars_manage'))
        return render(request, 'backends/new_article.html',{'form':form})
    else:
        form = ArticleForm(request)
        return render(request, 'backends/new_article.html',{'form':form})
from django.forms import ModelForm
from django import forms
from web import models

class EnrollmentForm(ModelForm):
    def __new__(cls, *args, **kwargs):
        print("__new__",cls,args,kwargs)
        for field_name in cls.base_fields:
            filed_obj = cls.base_fields[field_name]
            filed_obj.widget.attrs.update({'class':'form-control'})
            if field_name in cls.Meta.readonly_fields:
                filed_obj.widget.attrs.update({'disabled': 'true'})
        return  ModelForm.__new__(cls)

    class Meta:
        model = models.StudentEnrollment
        #fields = ['name','consultant','status']
        fields = "__all__"
        exclude = ['contract_approved_date']
        readonly_fields = ['contract_agreed',]

    def clean(self):
        '''form defautl clean method'''
        print("cleaned_dtat:",self.cleaned_data)

        if self.errors:
            raise forms.ValidationError(("Please fix errors before re-submit."))
        if self.instance.id is not None :#means this is a change form ,should check the readonly fields
            for field in self.Meta.readonly_fields:
                old_field_val = getattr(self.instance,field) #数据库里的数据
                form_val = self.cleaned_data.get(field)
                print("filed differ compare:",old_field_val,form_val)
                if old_field_val != form_val: #添加指定字段的错误
                    self.add_error(field,"Readonly Field: field should be '{value}' ,not '{new_value}' ".\
                                   format(**{'value':old_field_val,'new_value':form_val}))


class CustomerForm(ModelForm):
    def __new__(cls, *args, **kwargs):
        print("__new__",cls,args,kwargs)
        for field_name in cls.base_fields:
            filed_obj = cls.base_fields[field_name]
            filed_obj.widget.attrs.update({'class':'form-control'})

            if field_name in cls.Meta.readonly_fields:
                filed_obj.widget.attrs.update({'disabled': 'true'})
        
        return  ModelForm.__new__(cls)

    class Meta:
        model = models.CustomerInfo
        #fields = ['name','consultant','status']
        fields = "__all__"
        exclude = ['consult_content','status','consult_courses']
        readonly_fields = ['contact_type','contact','consultant','referral_from','source']


    def clean(self):
        '''form defautl clean method'''
        print("cleaned_dtat:",self.cleaned_data)

        if self.errors:
            raise forms.ValidationError(("Please fix errors before re-submit."))
        if self.instance.id is not None :#means this is a change form ,should check the readonly fields
            for field in self.Meta.readonly_fields:
                old_field_val = getattr(self.instance,field) #数据库里的数据
                form_val = self.cleaned_data.get(field)
                print("filed differ compare:",old_field_val,form_val)
                if old_field_val != form_val:
                    self.add_error(field,"Readonly Field: field should be '{value}' ,not '{new_value}' ".\
                                         format(**{'value':old_field_val,'new_value':form_val}))
posted @   Bruce_JRZ  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示