Django ModelForm外键选项过滤及提交校验失败的解决方案
本文探讨Django中ModelForm外键字段的动态过滤与提交验证问题。通过在Form的__init__方法中修改choices可实现前端选项过滤,但直接复用该Form处理POST请求会导致数据校验失败。解决方案是创建不含初始化的“影子”Form类专门用于接收和验证提交数据,从而分离显示逻辑与验证逻辑。
问题描述
在Django模型开发中,经常需要使用ForeignKey字段来关联其他数据表。
class MeasureTask(models.Model):
taskname = models.CharField(max_length=LEN_FULLNAME, verbose_name="任务名称")
road = models.ForeignKey(Road, on_delete=models.CASCADE, verbose_name="设计路段")
# 路面层,附加一个参数 ,指定这个层的厚度,相对于底层的厚度
# road_level = models.ForeignKey(RoadLevel, on_delete=models.CASCADE, verbose_name="路面层")
level_thick = models.IntegerField(default=0, verbose_name="层厚(mm)")
# ...
当使用Django的ModelForm将模型转换为网页表单时,相关代码如下:
class MeasureTaskNewForm(forms.ModelForm):
class Meta:
model = MeasureTask
fields = ('taskname', 'staff', 'start_mileage', 'end_mileage',
'road', 'level_thick', 'step', 'equip', 'comment')
若不对关联字段进行额外处理,该字段在网页中会自动渲染为包含所有可选值的select控件,效果如图所示:

在实际业务场景中,通常需要对关联字段进行筛选过滤。期望的显示效果如下:

解决方式
可以在Form类中定义一个初始化函数来实现这一功能:
class MeasureTaskNewForm(forms.ModelForm):
class Meta:
model = MeasureTask
fields = ('taskname', 'staff', 'start_mileage', 'end_mileage',
'road', 'level_thick', 'step', 'equip', 'comment')
# 对参数作初始化设置,导致返回之后的Form验证失败
def __init__(self, road_choices=None, *args, **kwargs):
super(MeasureTaskNewForm, self).__init__(*args, **kwargs)
if road_choices:
self.fields['road'].choices = road_choices
使用该类的具体方式如下,需注意传入参数的数据类型:
# 对关联数据过滤
roads = Road.objects.filter(project=p_item)
# 生成值,分别对应于 html 中 select->option 设置
choices = roads.values_list('id', 'name')
dataform = MeasureTaskNewForm(road_choices=choices)
# dataform = MeasureTaskNewFormShadow()
return render(request, "mdata/html/measure_task_add.html", locals())
最终生成的HTML代码如下:
<select name="road" id="id_road" class="form-control">
<option value="1">北四环主线</option>
<option value="5">匝道A</option>
</select>
此时生成的表单页面显示正常,但当表单提交并返回数据时,如果仍使用同一个经过初始化的Form来接收Request数据,则会引发数据校验失败的问题。
if request.method == "POST":
dataform = MeasureTaskNewForm(request.POST)
# 这里将出现校验失败的问题
if dataform.is_valid():
dataform.save()
return redirect('mdata:measure_task', pid=p_item.id)
为解决此问题,需要额外创建一个不包含初始化函数的表单类,专门用于接收提交的数据。
# 影子表单模型
class MeasureTaskNewFormShadow(forms.ModelForm):
class Meta:
model = MeasureTask
fields = ('taskname', 'staff', 'start_mileage', 'end_mileage',
'road', 'level_thick', 'step', 'equip', 'comment')
if request.method == "POST":
dataform = MeasureTaskNewFormShadow(request.POST)
if dataform.is_valid():
dataform.save()
return redirect('mdata:measure_task', pid=p_item.id)
目前看来可能存在更优的解决方案,尝试修改Form的初始化函数未果。
参考资料
https://qastack.cn/programming/813418/django-set-field-value-after-a-form-is-initialized
http://hk.uwenku.com/question/p-vdjpsmjn-bes.html
https://www.itranslater.com/qa/details/2325790729974580224
到此这篇关于Django表单外键选项初始化的文章就介绍到这了,更多相关Django表单初始化内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









