Python 在自定义Django Crispy表单中忽略ValidationError
我有一个相当复杂的Django表单,它影响3个模型,其中一部分包括一个内联表单集。我找到了一个很好的解决方案来构建表单。我扩展了该解决方案,并以与添加表单集类似的方式添加了第三个模型(使用定制的Django Crispy表单,并使用Crispy表单布局特性插入它) 我的问题是,在两个插入的表单(表单集和小型子表单)中的任何一个上引发的任何验证错误都会被忽略-主表单正确发布,引发的验证错误在表单中显示为错误,允许用户更正任何错误,其数据正确保存到数据库中。如果子表单和表单集有效,则其数据也会正确保存。但是,如果子表单和表单集中的数据无效,表单将不会显示错误,从而给用户纠正错误的机会,并且数据将被忽略,也不会保存到数据库中-尽管主模型的数据保存得很好 我的问题是,如何让表单刷新,并在添加的子表单和表单集中显示错误,从而允许用户更正错误 下面的大部分代码来自上面提到的非常好的帖子,并添加了第三个模型 型号:Python 在自定义Django Crispy表单中忽略ValidationError,python,django,forms,validation,django-crispy-forms,Python,Django,Forms,Validation,Django Crispy Forms,我有一个相当复杂的Django表单,它影响3个模型,其中一部分包括一个内联表单集。我找到了一个很好的解决方案来构建表单。我扩展了该解决方案,并以与添加表单集类似的方式添加了第三个模型(使用定制的Django Crispy表单,并使用Crispy表单布局特性插入它) 我的问题是,在两个插入的表单(表单集和小型子表单)中的任何一个上引发的任何验证错误都会被忽略-主表单正确发布,引发的验证错误在表单中显示为错误,允许用户更正任何错误,其数据正确保存到数据库中。如果子表单和表单集有效,则其数据也会正确保
from django.db import models
from django.contrib.auth.models import User
class Collection(models.Model):
subject = models.CharField(max_length=300, blank=True)
owner = models.CharField(max_length=300, blank=True)
note = models.TextField(blank=True)
created_by = models.ForeignKey(User,
related_name="collections", blank=True, null=True,
on_delete=models.SET_NULL)
def __str__(self):
return str(self.id)
class CollectionTitle(models.Model):
"""
A Class for Collection titles.
"""
collection = models.ForeignKey(Collection,
related_name="has_titles", on_delete=models.CASCADE)
name = models.CharField(max_length=500, verbose_name="Title")
language = models.CharField(max_length=3)
Class CollectionTxn(models.Model):
"""
A Class for Collection transactions.
"""
collection = models.ForeignKey(Collection,
related_name="has_txn", on_delete=models.CASCADE)
number_received= models.IntegerField()
date_received= models.DateField()
class Meta:
'''
If 2 rows are entered with the same information, a validation error is raised, but it just
doesn't save the data at all instead of refreshing the form showing the error.
'''
unique_together = ('number_received', 'date_received')
forms.py:
from django import forms
from .models import Collection, CollectionTitle
from django.forms.models import inlineformset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Fieldset, Div, Row, HTML, ButtonHolder, Submit
from .custom_layout_object import Formset, Subform
import re
class CollectionTitleForm(forms.ModelForm):
class Meta:
model = CollectionTitle
exclude = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
formtag_prefix = re.sub('-[0-9]+$', '', kwargs.get('prefix', ''))
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Field('name'),
Field('language'),
Field('DELETE'),
css_class='formset_row-{}'.format(formtag_prefix)
)
)
CollectionTitleFormSet = inlineformset_factory(
Collection, CollectionTitle, form=CollectionTitleForm,
fields=['name', 'language'], extra=1, can_delete=True
)
class CollectionForm(forms.ModelForm):
class Meta:
model = Collection
exclude = ['created_by', ]
def __init__(self, *args, **kwargs):
super(CollectionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-3 create-label'
self.helper.field_class = 'col-md-9'
self.helper.layout = Layout(
Div(
Field('subject'),
Field('owner'),
Fieldset('Add titles',
Formset('titles')),
Field('note'),
Subform('transactions'),
HTML("<br>"),
ButtonHolder(Submit('submit', 'Save')),
)
)
class CollectionTxnForm(forms.ModelForm):
class Meta:
model = CollectionTxn
exclude = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['collection'].widget = HiddenInput()
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Field('number_received'),
Field('date_received'),
)
)
自定义_布局_object.py
from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string
class Formset(LayoutObject):
template = "mycollections/formset.html"
def __init__(self, formset_name_in_context, template=None):
self.formset_name_in_context = formset_name_in_context
self.fields = []
if template:
self.template = template
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
formset = context[self.formset_name_in_context]
return render_to_string(self.template, {'formset': formset})
class SubForm(LayoutObject):
template = "mycollections/subform.html"
def __init__(self, subform_name_in_context, template=None):
self.subform_name_in_context = subform_name_in_context
self.fields = []
if template:
self.template = template
def render(self, subform, form_style, context, template_pack=TEMPLATE_PACK):
subform = context[self.subform_name_in_context]
return render_to_string(self.template, {'subform': subform})
formset.html
{% load crispy_forms_tags %}
{% load staticfiles %}
<style type="text/css">
.delete-row {
align-self: center;
}
</style>
{{ formset.management_form|crispy }}
{% for form in formset.forms %}
{% for hidden in form.hidden_fields %}
{{ hidden|as_crispy_field }}
{% endfor %}
{% crispy form %}
{% endfor %}
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'mycollections/libraries/django-dynamic-formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row-{{ formset.prefix }}').formset({
addText: 'add another',
deleteText: 'remove',
prefix: '{{ formset.prefix }}',
});
</script>
集合_create.html
{% extends "mycollections/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<div class="card">
<div class="card-header">
Create collection
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
</div>
{% endblock content %}
{%extends“mycollections/base.html”%}
{%load crispy_forms_tags%}
{%block content%}
创建集合
{%crispy form%}
{%endblock内容%}
基本上,对于与添加到布局中的表单集和子表单相关联的字段,仍然会引发验证错误,但它们不会冒泡到表单级别以显示错误,它们只是被忽略,并且永远不会保存数据。“main”模型运行良好,并为其字段显示validationerrors。如果没有无效数据,则主窗体、子窗体和窗体集的数据都会正确保存。如果表单集或子表单中存在无效数据,则用户永远无法更正该数据
如果您能帮助我在何处添加所需的代码,以便在表单集或子表单中输入的任何无效数据都会导致表单刷新显示错误,而不是忽略或不保存无效数据,我们将不胜感激。经过大量调试和分析代码(包括我自己的代码和Crispy代码),我已经解决了自己的问题。只需检查子表单和表单集验证,如果无效,则重新呈现表单 下面是视图中的新form_valid()方法,用于执行以下操作:
def form_valid(self, form):
context = self.get_context_data()
titles = context['titles']
transactions = context['transactions']
with transaction.atomic():
form.instance.created_by = self.request.user
if titles.is_valid() and transactions_is_valid():
self.object = form.save() #only save form if other subforms validate
titles.instance = self.object
# Any other field processing goes here
titles.save()
transactions.save()
else:
# If any subform or subformset is invalid, re-render the form showing errors
context.update({'titles': titles})
context.update({'transactions': transactions})
return self.render_to_response(context)
return super(CollectionCreate, self).form_valid(form)
这样,表单集和子表单中的任何清理方法,或者如果存在任何其他错误(例如,如果表单集的两行不能相同,因为在模型中声明了unique_together()),则表单将刷新,显示所有三个组合表单/表单集上的所有错误,从而允许用户更正这些错误
现在-使用Ajax这样页面就不会刷新:)modelform只对自身进行验证,而不是对其他额外的表单进行验证。我们想知道,使用clean方法更改数据库是否是一种好的做法?我一直认为应该使用save方法来实现这一点?是的,干净的方法可能不应该进行数据库更改。在本例中,更改是在视图的form_valid中进行的,而不是在任何clean方法中进行的。提到clean方法只是为了说明单个字段上的clean方法通常可以用于任何形式的验证(而不是保存)。由于需要同时对三个模型进行更改,因此我们使用transaction_atomic(),以便在出现任何错误时回滚更改。我认为这在单个模型的保存方法中可能会很尴尬,但我想知道您可能如何实现这一点。啊,您是对的。我误解了以班级为基础的观点。伟大的解决方案!
{% extends "mycollections/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<div class="card">
<div class="card-header">
Create collection
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
</div>
{% endblock content %}
def form_valid(self, form):
context = self.get_context_data()
titles = context['titles']
transactions = context['transactions']
with transaction.atomic():
form.instance.created_by = self.request.user
if titles.is_valid() and transactions_is_valid():
self.object = form.save() #only save form if other subforms validate
titles.instance = self.object
# Any other field processing goes here
titles.save()
transactions.save()
else:
# If any subform or subformset is invalid, re-render the form showing errors
context.update({'titles': titles})
context.update({'transactions': transactions})
return self.render_to_response(context)
return super(CollectionCreate, self).form_valid(form)