Django Form表单控件
form组件的常用字段
Django 表单中有许多不同类型的字段,每个字段都有不同的用途。以下是一些常见的 Django 表单字段及其区别:
CharField:用于输入字符串的字段。与 TextField 一样,但有最大长度限制。
IntegerField:用于输入整数的字段。可以指定最小值和最大值。
FloatField:用于输入浮点数的字段。可以指定最小值和最大值。
DecimalField:用于输入十进制数的字段。可以指定最小值和最大值,以及小数点位数。
BooleanField:用于选择是或否的字段。
ChoiceField 和 TypedChoiceField:用于选择预定义选项的字段。区别在于 ChoiceField 将所有选项的值视为字符串,而 TypedChoiceField 可以强制将选项的值转换为指定类型。
使用 ChoiceField,并指定了三个字符串选项。在表单提交时,每个选项的值都将是字符串类型。
TypedChoiceField 类似于 ChoiceField,不同之处在于它将每个选项的值视为指定类型。这可以确保表单处理时以正确的类型处理值。
TypedChoiceField 的 coerce 参数可以是以下类型之一:
int:将选项值强制转换为整数。
float:将选项值强制转换为浮点数。
decimal.Decimal:将选项值强制转换为十进制数。
str:将选项值保留为字符串类型。
自定义函数:可以指定一个自定义函数,该函数将选项值转换为特定类型,例如 coerce=my_func,其中 my_func 是一个自定义转换函数。
class MyForm(forms.Form):
MY_CHOICES = [
(1, '选项 1'),
(2, '选项 2'),
(3, '选项 3'),
]
my_field = forms.TypedChoiceField(choices=MY_CHOICES, coerce=int)
DateField 和 DateTimeField:分别用于选择日期和日期时间的字段。可以指定日期格式。
EmailField:用于输入电子邮件地址的字段。会进行基本的电子邮件格式验证。
FileField 和 ImageField:分别用于上传文件和图像的字段。可以指定其中一个或多个允许的文件类型。
MultipleChoiceField 和 ModelMultipleChoiceField:用于选择多个选项的字段。MultipleChoiceField 可以使用预定义选项,而 ModelMultipleChoiceField 可以从数据库中检索选项。
form组件的校验说明
先导包 from django import forms
,创建一个类继承forms.Form,类里面的字段需要和POST上来的字段一一对应。
class Userform(forms.Form):
user = forms.CharField()
pwd = forms.CharField(min_length=5)
email = forms.EmailField()
tel = forms.CharField()
def form_test(request):
if request.method=="POST":
#Userform 里像xxx这些多余的字段不会管他,只会挨个按照字段对value进行校验,只要这些字段都通过认证,下面的form.is_valid 返回true,否则返回false
form = Userform({"user":"alex","pwd":"123456","email":"aaa","xxx":"xxx"})
print(form.is_valid()) #False
#clean和cleaned_data会返回校验通过的字段
print(form.clean(),type(form.clean())) #{'user': 'alex'}
print(form.cleaned_data,type(form.cleaned_data)) #{'user': 'alex'}
# errors会返回校验未通过的字段,返回数据是字典{错误字段:[错误的返回值]},下面的打印字段是经过对这个字典转换成标签后的结果,所以我们可以对errors字段进行字典处理
print(form.errors)
# <ul class="errorlist"><li>pwd<ul class="errorlist"><li>Ensure this value has at least 5 characters (it has 3).</li></ul></li><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li><li>tel<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
#这些错误的标签可以按照字段提取对应的标签,第0个元素则是标签内的文本
print(form.errors.get("email")) #<ul class="errorlist"><li>Enter a valid email address.</li></ul>
print(form.errors.get("email")[0]) #Enter a valid email address.
return render(request,"login.html")
总结:
is_valid() 布尔类型,判断所有的字段是否都符合了Form组件的规则
cleaned_data 字典类型 符合Form组件规则的字段的key和value值会放入这个属性中
errors 字典类型 不符合Form组件规则的字段的key和value值会放入这个属性中
form组件的对POST请求的数据进行校验规则,字段需要和HTML标签字段相对应。
上面的例子中我们 form = Userform({"user":"alex","pwd":"123456","email":"aaa","xxx":"xxx"})
写死了,
我们从request.POST数据获取的数据也是个字段<QueryDict: {'user': ['ZCB-CWQ'], 'pwd': ['123441'], 'email': ['sxfxtf@sina.com'], 'tel': ['123']}>
,因此只要key值对应,就可以用form组件对里面的字段进行校验.
利用form组件的实例对象,便捷的实现和前端字段进行对应,利用实例对象的字段进行循环渲染 方式1
由于字段需要一一对应,那么通过生成form的实例对象字段的渲染可以减少代码,实现字段的对应 ,
利用form实例对象的字段进行循环渲染 方式2
这种方式主要是用for循环实例对象的字段进行渲染,但是input标签前面的text文本,需要先在类中每个字段定义label属性,然后在html中渲染实例字段的label值
利用form实例对象的字段进行循环渲染 方式3 这种更简单,渲染处只要一句话 {{ form_obj.as_p }}
,这个语法还支持as_table,as_ul
就会把form标签按照这个固定格式用一个P标签包裹一个label和input标签循环输出, 优点是代码量少,缺点是无法定制化显示标签
form组件的错误信息渲染和正确信息的保留
前面的几种情况form表单可以对数据的正确与否做出判断,但是页面刷新后会消失.
form组件的错误信息自定义
上面错误的提示也是默认的英文,我们需要自定义错误的信息,更适合国人看
为了测试效果好,在form标签加上 novalidate 这样可以关闭浏览器校验,直接显示Form的错误提示.生产环境建议去掉这个参数.
多一层校验减轻服务器压力.
<form method="post" novalidate> </form>
from django.forms import widgets
widget=widgets.TextInput() 设置input标签type=text
widget=widgets.CheckboxInput() 设置input标签type=check
widget=widgets.PasswordInput() 设置input标签type=Password
等等很多
自定义错误信息提示(似乎在django3版本上不适用,适用django2)
error_messages={"required":"required是为空的错误提示key","invalid":"这个是格式错误提示key"}
自定义标签内的属性 比如class
widget=widgets.TextInput(attrs={"class":"form-control"}) 设置input标签的class为form-control
代码示例: user = forms.CharField(min_length=1, label="用户名",error_messages={"required":"该字段不能为空","invalid":"该字段不符合要求"},widget=widgets.TextInput(attrs={"class":"form-control"}))
form组件的局部钩子和全局钩子
form组建的局部钩子都是从是它的is_valid()开始的.
局部钩子和全局钩子(第一版)
局部钩子代码示例:
全局钩子代码示例
form组件的局部钩子和全局钩子注册页面案例
局部钩子和全局钩子(第二版)
在full_clean这个方法中,有2个内置方法 self._clean_fields和self._clean_form
,前者就是局部钩子,用来对每个Form的字段进行自定义的规则过滤,后者是全局钩子,用来对Form的多个字段进行全局的规则判断,比如2次密码是否输入一致.
所以校验的总体规则是先局部校验,然后全局校验
局部钩子
1. 首先局部钩子代码如上图.先解释下self.fields.items()
,就是把自定义的form类的字段和规则做一个字典的对应
比如下面这个在item中就是{user:user字段的规则,pwd:pwd的字段规则,re_pwd:re_pwd的字段规则}
class log_form(forms.Form):
user = forms.CharField(min_length=1, label="用户名",error_messages={"required":"该字段不能为空","invalid":"该字段不符合要求"},widget=widgets.TextInput(attrs={"class":"form-control"}))
pwd = forms.CharField(min_length=4,label="密码", widget=widgets.TextInput(attrs={"class":"form-control"}))
re_pwd = forms.CharField(min_length=4, label="密码核对", widget=widgets.TextInput(attrs={"class":"form-control"}))
2. 接下来直接看390行代码,如果字段的规则通过了fuled.clean()方法,就还是赋值给value这个变量,然后391行,这个value值会被写入到self.cleaned_data这个字典中. 所以这个字典self.cleaned_data
记录的是符合字段规则的键值.
3. 392行代码就是局部校验的钩子,检测是否有clean_开头的,字段名结尾的内置属性,
如果有,则运行他.如果通过,按照394行代码,我们需要在自定义的局部钩子里反馈这个字段传进来的值,最终还是统一返回到self.cleaned_data
这个字典中去.
如果规则不通过,则报错ValidationError
,统一把错误内容整合到self.errors
里去
报错有2种方式
- 通过raise ValidationError("错误内容"),错误会整合到self.errors
- 通过self.add_error("错误字段","错误内容"), 错误也会整合到self.errors 这个方法可以直接将错误字段传入,在前端用form组件渲染的时候可以将对应字段错误的内容显示在对应字段标签中
{% for user in userform %}
<div class="row">
<div class="col-sm-3 control-label">
<label>{{ user.label }}</label>
</div>
<div class="col-sm-8">
{{ user }}
<span>{{ user.errors.0 }}</span> 这里就可以渲染字段的时候,同时把错误信息也渲染在这个span标签
</div>
</div>
{% endfor %}
然后通过html标签将上面的错误信息进行渲染
4. 局部钩子总结和代码示例
大致流程是
- form组件的字段名和字段规则作为k,v传入
self.fields.items()
- 符合规则返回到
self.cleaned_data
,不符合抛出异常整合到self.errors
- 检测是否有clean_开头的hook属性,有则运行,符合和不符合的处理逻辑同上.
def clean_user(self):
print("校验用户名")
name = self.cleaned_data.get("user") #获取user字段的内容
mark_obj=mark_info.objects.filter(mark_name=name).first() #ORM比对是否已存在注册
if mark_obj:
# print("%s已存在"%mark_obj.mark_name)
raise ValidationError("%s已存在"%mark_obj.mark_name)
else:
#如果通过,原封不动反馈name这个字段
return name
全局钩子
### 1. 全局规则代码中`self.clean()`是空的,也是用来覆盖父类进行自定义的.根据421行代码规则,自定义的全局钩子在匹配规则后直接返回`self.cleaned_data`,否则也是抛出异常汇总到`self.errors`.
### 2. 而此时为了在`self.erros`凸显全局和局部的错误信息,全局错误信息的key值是固定的`__all__`,而局部错误信息的key值是该字段的名字.
### 3. 全局钩子的总结和全局的代码示例
3.1 在完成上述局部钩子的前提下,通过局部校验的字段都会在`self.cleaned_data`,而未通过局部的都在`self.errors`中.
3.2 从`self.cleaned_data`获取各个字段的值进行全局匹配.而这个全局匹配的方法名是`self.clean`用来覆盖父类
```python
def clean(self):
#全局校验
pwd = self.cleaned_data.get("pwd")
re_pwd = self.cleaned_data.get("re_pwd")
#因为先进行局部校验,如果pwd和re_pwd为NONE,表示局部校验那一环节抛出异常了,
# 就无须进行全局校验了
if pwd and re_pwd:
if pwd==re_pwd:
print("2次密码一致")
return self.cleaned_data
else:
print("两次密码输入不一致",self.errors)
raise ValidationError("两次密码输入不一致")
局部/全局钩子和视图的代码
局部钩子带来的局部代码优化
我们从局部钩子的代码可以看到,钩子函数的返回值是被重新覆盖到self.cleaned_data中去的
所以使用form组件可以利用这个特性进行代码优化.
比如 注册功能中我们会在视图中获取post传入的账号密码,然后对账号进行新增,如果不用auth模块,则需要手动对密码进行加密后传入.
forms组件关于代码优化的思路
# 注册视图函数
def login(request):
if request.method == "POST":
userform = UserForm(request.POST)
print(userform)
if userform.is_valid():
user = userform.cleaned_data.get("user")
pwd = userform.cleaned_data.get("pwd")
pwd=md5(pwd) # 密码加密
user_obj = models.Customer.objects.create(username=user,password=pwd)
if user_obj:
request.session["user"] = user_obj.username
print("认证通过")
else:
userform.add_error("password","创建账号错误")
print("全局错误信息",userform.errors.get("__all__"))
return render(request, "login.html", {"userform": userform,"allerrors":userform.errors.get("__all__")})
else:
userform = UserForm()
return render(request, "login.html", {"userform": userform})
我们完全可以在钩子函数里实现密码加密,从而简化视图代码
# form函数代码
class UserForm(forms.Form):
acc_user = forms.CharField(label="用户名",required=True,min_length=3,
widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的用户名"}),
error_messages={'required': 'Please enter your name',"invalid":"用户名至少3位"},
validators=[RegexValidator("\w+","必须是文字")]
)
acc_password = forms.CharField(label="密码", widget=widgets.PasswordInput(
attrs={"class": "form-control", "placeholder": "form渲染的密码"}))
def clean_acc_user(self):
略
return user
def clean_acc_password(self):
print("校验密码")
pwd = self.cleaned_data.get("acc_password")
if len(pwd)<5:
raise ValidationError("密码必须大于5位","invalid")
return md5(pwd) #直接加密返回,覆盖self.cleaned_data
# 注册视图函数
def login(request):
if request.method == "POST":
userform = UserForm(request.POST)
print(userform)
if userform.is_valid():
user = userform.cleaned_data.get("user")
pwd = userform.cleaned_data.get("pwd")
pwd=md5(pwd) # 密码加密
user_obj = models.Customer.objects.create(username=user,password=pwd)
if user_obj:
request.session["user"] = user_obj.username
print("认证通过")
else:
userform.add_error("password","创建账号错误")
print("全局错误信息",userform.errors.get("__all__"))
return render(request, "login.html", {"userform": userform,"allerrors":userform.errors.get("__all__")})
else:
userform = UserForm()
return render(request, "login.html", {"userform": userform})
与此类似的优化逻辑还有,可以在局部钩子或者全局钩子里,验证通过后,直接钩子函数中写入session等逻辑操作,简化视图函数中的代码
forms组件中的ModelForm
我们在上面使用form.Form组件时候,是需要一个个字段自己编写的,并且为了操作简便性,字段和orm的字段要保持一致.
但是如果这个表的字段有几十个,那么一个个手写效率就很低下,所以在前端和数据库有交互的场景下,建议使用ModelForm
ModelForm的优点
Form功能:
编写字段
生成HTML标签 + 插件 + 和参数的配置
表单的验证
ModelForm功能
不编写字段,直接引用Model字段【优秀】
生成HTML标签 + 插件 + 和 参数的配置
表单的验证
保存(新增、更新)
ModelForm不用手写字段,以及ModelForm如何设置前端标签样式
class LevelForm(forms.Form):
title = forms.CharField(
required=True,label="标签",
widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的标题"})
)
discount = forms.IntegerField(
required=True,label="折扣",
widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的折扣"}),
help_text="折扣,比如70%"
)
class LevelForm(forms.ModelForm):
class Meta:
model = models.Level
fields=["title","discount"] #建议用这个,还可以控制页面排版的先后顺序
# fields="__all__" 这里为了不显示哪个active字段 就不用__all__
# exclude = ['active'] 也可以单独控制部显示某个字段
widgets={
"title":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
"discount":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的百分比"})
}
# 如何每个字段的样式都是一样的,也可以用循环生成. 这里使用__init__是为了调用父类方法,
# 将所有标签的字段以字典形式赋值给self.fields >> {'title':对象,"discount":对象}
class LevelForm(forms.ModelForm):
class Meta:
model = models.Level
fields = ['title', 'discount']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 父类会将所有标签的信息用字典形式赋值给self.fields >> {'title':对象,"discount":对象}
for name, field in self.fields.items():
field.widget.attrs['class'] = "form-control"
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
# 和Form一样,进行局部钩子校验
def clean_percent(self):
value = self.cleaned_data['percent']
return value
Form和ModelForm的如何展示已填写信息
场景1:Form的提交是会刷新页面的,那么当字段填写错误,页面刷新后,我们是希望将之前填写的数据都保存在页面中.
场景2: 编辑页面,我们也会希望
方法1: Form和ModelForm都适用,用initial参数,将页面数据当做初始数据展示
LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
方法2 : 适用ModelForm,用来展示数据库已有数据的内容
用instance参数传入orm对象, LevelForm_obj = LevelForm(instance=edit_obj)
2种方法的代码示例
# 视图函数
def level_edit(request, pk):
"""会员等级编辑页面"""
edit_obj = models.Level.objects.filter(pk=pk).first()
print(edit_obj.title)
if request.method == "GET":
print("进入编辑_get请求")
# 方法1 LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
LevelForm_obj = LevelForm(instance=edit_obj)
return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})
ModelForm的新增和更新保存
ModelForm可以用save方法进行新增和更新保存,如果传入data和instance,会进行比较2者之间不一致的内容,实现部分更新
#新增
form = LevelModelForm(data=request.POST)
form.save()
#更新
form = LevelModelForm(data=request.POST,instance=对象)
form.save()
代码示例展示
# 新增功能
def level_add(request):
LevelForm_obj = LevelForm()
if request.method == "GET":
return render(request, "level_add.html", {"LevelForm_obj": LevelForm_obj})
LevelForm_obj = LevelForm(request.POST)
if not LevelForm_obj.is_valid():
return render(request, "level_add.html", {"LevelForm_obj": LevelForm_obj})
# models.Level.objects.create(**LevelForm_obj.cleaned_data) #也可以使用orm方法新增
LevelForm_obj.save() #ModelForm可以用save进行保存
print("新增成功")
return redirect(reverse("level_list"))
# 更新保存功能
def level_edit(request, pk):
print("pk>>", pk)
edit_obj = models.Level.objects.filter(pk=pk).first()
print(edit_obj.title)
if request.method == "GET":
print("进入编辑_get请求")
# LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
LevelForm_obj = LevelForm(instance=edit_obj)
return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})
if request.method == "POST":
print("进入编辑_post请求")
# 更新保存
LevelForm_obj = LevelForm(data=request.POST, instance=edit_obj)
LevelForm_obj.save()
print("更新保存成功")
return redirect(reverse("level_list"))
form对象新增保存对象
ModelForm是有个save方法可以把form对象中表单的内容保存到数据库去的.但是也会有部分字段不是从前端页面传过来的.
比如要在字段中保存登录用户的id 是从request.user.id获取的
需要再form.instance属性添加
from django import forms
from .models import MyModel
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
def my_view(request, id):
# 获取要编辑的模型实例
user = request.user
# user = models.User.objects.get(id=request.user.id)
if request.method == 'POST':
# 创建表单对象并设置表单数据和实例
form = MyForm(request.POST, instance=user)
# 上面的写法等同于
# form = MyForm(request.POST)
#form.instance.creator = user #这里的creator是orm中的字段
# 还有一种写法
# form1 = MyForm(request.POST)
#form1.instance.creator = user
#form1.save(commit=False)
#form1.creator=user
#form1.save()
if form.is_valid():
# 处理表单提交
form.save()
return render(request, 'my_template.html', {'form': form})
关于使用了instance参数后再模版渲染的一个注意事项
场景: 我们使用ModelForm对一个编辑页面进行展示已有字段,并且部分更新.
假设更新的orm对象id为1 此时提交的post url 为 order/level/edit/1/ 对应下面的url id为1作为pk传入
我们在前端form标签的action需要实现对id为1的url进行后续更新的POST提交
注意下面的视图代码,form实例对象利用instance参数进行数据库内容的展示 LevelForm_obj = LevelForm(instance=edit_obj)
路由代码
urlpatterns = [
path('order/level/',views.level_list,name="level_list"),
path('order/level/add',views.level_add,name="level_add"),
path('order/level/edit/<str:pk>/',views.level_edit,name="level_edit"),
]
视图代码
def level_edit(request, pk):
edit_obj = models.Level.objects.filter(pk=pk).first()
if request.method == "GET":
print("进入编辑_get请求")
# LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
LevelForm_obj = LevelForm(instance=edit_obj)
return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})
html模版代码
<form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}">
{% for obj in LevelForm_obj %}
<label for="id_{{ obj.name }}">{{ obj.label }}</label>
{% if obj.help_text %}
<span style="font-weight: bold;color: gray"> ({{ obj.help_text }}) </span>
{% endif %}
{{ obj }}
<span>{{ obj.errors.0 }}</span>
{% endfor %}
<button type="submit" class="btn btn-success">保存</button>
</form>
form组件代码
class LevelForm(forms.ModelForm):
class Meta:
model = models.Level
fields=["title","discount"] #建议用这个,还可以控制页面排版的先后顺序
widgets={
"title":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
"discount":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的百分比"})
}
help_texts={
"discount":"折扣,比如70%"
}
模版代码
注意 <form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}">
中的 pk=LevelForm_obj.instance.pk
这里如果写成pk=LevelForm_obj.pk
页面渲染会报错 提示pk未传参.
因为modelForm是通过instance参数将orm对象传入,所以这个orm的id实际上是instance的属性获取到的,而不是LevelForm_obj.pk
,LevelForm_obj只是用来渲染的form组件实例,本身是不含有orm的任何数据.都是要通过instance传入
<form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}">
{% for obj in LevelForm_obj %}
<label for="id_{{ obj.name }}">{{ obj.label }}</label>
{% if obj.help_text %}
<span style="font-weight: bold;color: gray"> ({{ obj.help_text }}) </span>
{% endif %}
{{ obj }}
<span>{{ obj.errors.0 }}</span>
{% endfor %}
<button type="submit" class="btn btn-success">保存</button>
</form>
展示效果和对应代码关联
modelform的显示内容自定义
通常如果需要对前端内容做自定义显示,可以在modelForm组件中用limit_choices_to
参数进行默认的前端数据展示,可以添加多个参数,默认会对数据展示前进行过滤.
class Level(ActiveBaseModel):
title = models.CharField(verbose_name="VIP等级", max_length=30)
discount = models.IntegerField(verbose_name="折扣")
# def __str__(self):
# return self.title
class Customer(ActiveBaseModel):
role_Choices=((30,"BOSS"),(20,"ADMIN"),(1,"CUSTOMER"))
username = models.CharField(max_length=20, verbose_name="用户名称")
password = models.CharField(max_length=20, verbose_name="密码")
mobile = models.CharField(verbose_name="手机号", max_length=11)
balance = models.DecimalField(verbose_name="账户余额", default=0, max_digits=10, decimal_places=2)
level =models.ForeignKey(verbose_name="会员等级",to="Level",on_delete=models.CASCADE,default=1)
role_level = models.SmallIntegerField(choices=role_Choices,default=1,verbose_name="角色级别")
# limit_choices_to 可以用来对关联内容做限制展示
# level = models.ForeignKey(verbose_name="级别", to="Level", on_delete=models.CASCADE, limit_choices_to={'active': 1})
create_date=models.DateTimeField(verbose_name="创建日期",auto_now_add=True)
前端渲染字段信息的3种方式
这里以上面的Customer和Level类做例子
-
用
__str__
自定义类的输出内容class Level(ActiveBaseModel): title = models.CharField(verbose_name="VIP等级", max_length=30) discount = models.IntegerField(verbose_name="折扣") def __str__(self): return self.title
-
前端中用Customer的实例对象.level.title进行跨表展示,就是会多查询一次数据库
如果要提高查询效率 视图中用select_related()函数进行跨表查询 `queryset=models.Customer.object.filter(active=1).select_relateed()
这种查询方式如果需要加过滤条件显示 那么考虑添加limit_choice_to字段进行过滤显示,可以有多个字段,并且支持orm的过滤语法
比如
id__gt=1
-
在使用ModelForm时,调用父类的
__init__
方法会将form的字段都写到self.fileds中去我们可以对这个字段进行重新赋值达到过滤目的
class Customer_Add(forms.ModelForm): class Meta: model = models.Customer fields = ['username', 'password',"mobile","level","role_level"] def __init__(self): super().__init__() self.fields["level"].queryset = models.Level.objects.filter(active__gte=1)
modelForm遍历循环的时候自定义标签
我们在循环的时候可能对某个特定标签用法会不同.我们可以自定义一个父类
class BaseModelForm(forms.ModelForm):
exclude_filed_list = [] #子类自定义
class Meta:
model = models.Customer
fields = ['username', 'password', "mobile", "level", "role_level"]
def __init__(self):
super().__init__()
for name, field in self.fields.items():
if name in self.exclude_filed_list:
continue
field.widget.attrs['class'] = "form-control"
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
然后我们在视图代码中可以继承这个类,使得level字段可以使用自定义的标签样式
class Customer_Add(BaseModelForm):
# 重写这个参数,使得其在父类中可以生效
exclude_filed_list = ["level"]
class Meta:
model = models.Customer
fields = ['username', 'password', "mobile", "level", "role_level"]
widgets={
# 2. 自定义level字段为radio标签
"level":forms.RadioSelect()
# 通过attr设置自定义样式
#"title": forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
}
modelForm覆盖orm字段
orm定义好的字段有时候在前端展示的时候会遇到部分展示的使用场景,比如下面前端只需要展示充值和扣款字段,并不需要展示创建删除等字段
class TransactionRecord(ActiveBaseModel):
""" 交易记录 """
charge_type_choices = ((1, "充值"), (2, "扣款"), (3, "创建订单"), (4, "删除订单"), (5, "撤单"),)
charge_type = models.SmallIntegerField(verbose_name="类型", choices=charge_type_choices)
这里主要有2种方式,不同方式使用场景也不同
- 在form表单对象中重写charge_type字段
class Charge_Add_obj(forms.ModelForm):
charge_type=forms.ChoiceField(choices=((1, "充值"), (2, "扣款")),label="新的充值类型")
- 还是在父类遍历时候修改charge_type字段中的choices属性
class Charge_Add_obj(forms.ModelForm):
# charge_type=forms.ChoiceField(choices=((1, "充值"), (2, "扣款")),label="新的充值类型")
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.fields["charge_type"].choices=((1, "充值"), (2, "扣款"))
2者使用场景不同,第一种是静态的,表示程序运行后charge_type 就只有2个选项,如果要添加新的选项,必须重启django.
而第二种方法页面每次渲染,都会重新运行父类的方法读取self.fields字段,就算字段有新增,也可以在不重启django程序的前提下,动态渲染展示出第三个字段.