二十二、Django之Form组件
1.一、计算机初识2.二、数据概述3.六、ip地址与子网划分4.五、网络通信实现5.四、字符编码6.三、网络基础7.十四、MySQL与Django之Model基础8.十三、Django Admin9.十二、Django视图函数和模版相关10.十一、Django url控制系统11.十、Django静态文件12.九、Django环境搭建(基于anaconda环境)13.八、Python开发环境管理14.七、ip地址配置15.二十五、JSON跨域16.二十四、文件上传17.二十三、Django Serializes
18.二十二、Django之Form组件
19.二十一、分页20.JS笔记21.二十、基于Bootstrap和FontAwesome制作页面22.十九、Ajax和iFrame23.十八、Django之Http24.十七、Cookie和Session25.十六、Django的ORM(二)26.十五、Django的ORM27.实践中前端的一些笔记28.二十六、登录相关29.二十九、RBAC+动态菜单30.二十八、XSS31.JS第三方插件32.二十七、简单的验证码实现33.三十二、Django实践的笔记34.三十一、动态Form35.三十、KingadminDjango的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}))
合集:
Python全栈(Django)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库