Django中的内联表单验证

Django中的内联表单验证,django,django-forms,Django,Django Forms,我想在管理员变更表单中强制设置一个完整的内联表单集。因此,在我当前的场景中,当我在发票表单上点击save(在Admin中)时,内联订单表单为空。我想阻止人们创建没有关联订单的发票 有人知道一个简单的方法吗 在这种情况下,模型字段上的正常验证(如(required=True)似乎不起作用。最好的方法是定义一个自定义表单集,使用一个干净的方法验证至少存在一个发票订单 class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet):

我想在管理员变更表单中强制设置一个完整的内联表单集。因此,在我当前的场景中,当我在发票表单上点击save(在Admin中)时,内联订单表单为空。我想阻止人们创建没有关联订单的发票

有人知道一个简单的方法吗


在这种情况下,模型字段上的正常验证(如(
required=True
)似乎不起作用。

最好的方法是定义一个自定义表单集,使用一个干净的方法验证至少存在一个发票订单

class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        # get forms that actually have valid data
        count = 0
        for form in self.forms:
            try:
                if form.cleaned_data:
                    count += 1
            except AttributeError:
                # annoyingly, if a subform is invalid Django explicity raises
                # an AttributeError for cleaned_data
                pass
        if count < 1:
            raise forms.ValidationError('You must have at least one order')

class InvoiceOrderInline(admin.StackedInline):
    formset = InvoiceOrderInlineFormset


class InvoiceAdmin(admin.ModelAdmin):
    inlines = [InvoiceOrderInline]
class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet):
def清洁(自清洁):
#获取实际具有有效数据的表单
计数=0
对于self.forms中的表单:
尝试:
如果form.u数据:
计数+=1
除属性错误外:
#令人烦恼的是,如果子窗体无效,Django会显式地引发
#已清除数据的AttributeError
通过
如果计数小于1:
raise forms.ValidationError('您必须至少有一个订单')
类InvoiceOrderInline(admin.StackedInline):
formset=InvoiceOrderInlineFormset
类InvoiceAdmin(admin.ModelAdmin):
inlines=[InvoiceOrderInline]

Daniel的回答很好,在一个项目中对我很有效,但后来我意识到,由于Django表单的工作方式,如果您在保存时使用can_delete并选中delete框,则可以验证任何订单(在本例中)

我花了一段时间试图找出如何防止这种情况发生。第一种情况很简单-不要在计数中包含将被删除的表单。第二种情况比较棘手……如果选中了所有的删除框,则不会调用
clean

不幸的是,代码并不十分简单。
clean
方法是从
full\u clean
调用的,在访问
error
属性时调用该方法。删除子窗体时不会访问此属性,因此永远不会调用
full\u clean
。我不是Django专家,所以这可能是一种可怕的方法,但似乎有效

下面是修改后的类:

class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet):
    def is_valid(self):
        return super(InvoiceOrderInlineFormset, self).is_valid() and \
                    not any([bool(e) for e in self.errors])

    def clean(self):
        # get forms that actually have valid data
        count = 0
        for form in self.forms:
            try:
                if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
                    count += 1
            except AttributeError:
                # annoyingly, if a subform is invalid Django explicity raises
                # an AttributeError for cleaned_data
                pass
        if count < 1:
            raise forms.ValidationError('You must have at least one order')
class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet):
def有效(自):
return super(InvoiceOrderInlineFormset,self)。有效吗?()和\
没有任何([bool(e)表示self.errors中的e])
def清洁(自清洁):
#获取实际具有有效数据的表单
计数=0
对于self.forms中的表单:
尝试:
如果form.cleaned_数据而不是form.cleaned_数据.get('DELETE',False):
计数+=1
除属性错误外:
#令人烦恼的是,如果子窗体无效,Django会显式地引发
#已清除数据的AttributeError
通过
如果计数小于1:
raise forms.ValidationError('您必须至少有一个订单')
类强制InLineFormSet(BaseInlineFormSet):
def有效(自):
返回super(MandatoryInlineFormSet,self)。_是否有效()和\
没有任何([bool(e)表示self.errors中的e])
def清洁(自清洁):
#获取实际具有有效数据的表单
计数=0
对于self.forms中的表单:
尝试:
如果form.cleaned_数据而不是form.cleaned_数据.get('DELETE',False):
计数+=1
除属性错误外:
#令人烦恼的是,如果子窗体无效,Django会显式地引发
#已清除数据的AttributeError
通过
如果计数小于1:
raise forms.ValidationError('您必须至少拥有其中一个')
类mandatorytablerinline(admin.tablerinline):
formset=MandatoryInlineFormSet
类mandatoryStackedLine(admin.stackedLine):
formset=MandatoryInlineFormSet
类CommentInlineFormSet(MandatoryInlineFormSet):
def清洁_等级(自身、表格):
"""
额定值必须为0..5乘0.5的增量
"""
额定值=浮动(表.数据['额定值])
如果评级<0或评级>5:
raise ValidationError(“评级必须在0-5之间”)
如果(额定值/0.5)!=整数(额定值/0.5):
raise ValidationError(“评级必须有.0或.5小数点”)
def清洁(自清洁):
super(CommentInlineFormSet,self).clean()
对于self.forms中的表单:
自洁度(表格)
类CommentInline(MandatoryTablerinline):
formset=CommentInlineFormSet
模型=注释
额外=1

@Daniel Roseman解决方案很好,但我做了一些修改,减少了一些代码来实现这一点

class RequiredFormSet(forms.models.BaseInlineFormSet):
      def __init__(self, *args, **kwargs):
          super(RequiredFormSet, self).__init__(*args, **kwargs)
          self.forms[0].empty_permitted = False

class InvoiceOrderInline(admin.StackedInline):
      model = InvoiceOrder
      formset = RequiredFormSet


class InvoiceAdmin(admin.ModelAdmin):
     inlines = [InvoiceOrderInline]

试试这个也行:)

情况有所好转,但仍需要一些解决办法。Django现在提供了
validate\u min
min\u num
属性,如果在表单集实例化期间
min\u num
取自
Inline
,则
validate\u min
只能作为init formset参数传递。因此,我的解决方案如下所示:

class MinValidatedInlineMixIn:
验证_min=True
def get_formset(self,*args,**kwargs):
return super().get_formset(validate_min=self.validate_min,*args,**kwargs)
类InvoiceOrderInline(minValidateInLineMixin,admin.StackedInline):
型号=发票订单
最小数量=1
验证_min=True
类InvoiceAdmin(admin.ModelAdmin):
inlines=[InvoiceOrderInline]

我发现,如果选中删除框,则可以使用0个订单进行验证。请参阅我的答案,了解解决该问题的修订课程。非常感谢您的修复(以及Dan的增强)。作为对其他人的可能提示,我制作了一个“类MandatoryInlineFormSet(BaseInlineFormSet)”,然后从中派生出InvoiceAdminFormSet。在我的库存中
class RequiredFormSet(forms.models.BaseInlineFormSet):
      def __init__(self, *args, **kwargs):
          super(RequiredFormSet, self).__init__(*args, **kwargs)
          self.forms[0].empty_permitted = False

class InvoiceOrderInline(admin.StackedInline):
      model = InvoiceOrder
      formset = RequiredFormSet


class InvoiceAdmin(admin.ModelAdmin):
     inlines = [InvoiceOrderInline]