在Django中内嵌表单validation

我想在一个pipe理员变更表单中填写一个完整的内联表单。 所以在我目前的情况下,当我点击保存在发票表单(在pipe理员)内联订单表单是空的。 我想停止创build没有订单关联的人员的发票。

任何人都知道一个简单的方法呢?

在这个例子中,模型字段上的( required=True )这样的正常validation似乎不起作用。

做到这一点的最好方法是定义一个自定义的formset,用一个干净的方法来validation至less存在一个发票顺序。

 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] 

Daniel的答案非常好,它在一个项目中对我很有帮助,但是后来我意识到由于Django的工作方式,如果使用can_delete并在保存时检查删除框,可以validation没有任何订单(在此案件)。

我花了一段时间试图找出如何防止这种情况发生。 第一种情况很简单 – 不要包括将被删除的表格。 第二种情况是棘手的…如果所有的删除框被选中,那么clean没有被调用。

代码不完全简单,不幸的是。 clean方法是从full_clean中调用的,当访问error属性的时候会调用它。 删除子窗体时不访问此属性,因此不会调用full_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') 

@丹尼尔·罗斯曼解决scheme是好的,但我有一些更less的代码来做同样的修改。

 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] 

试试这个也适用:)

 class MandatoryInlineFormSet(BaseInlineFormSet): def is_valid(self): return super(MandatoryInlineFormSet, 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 of these.') class MandatoryTabularInline(admin.TabularInline): formset = MandatoryInlineFormSet class MandatoryStackedInline(admin.StackedInline): formset = MandatoryInlineFormSet class CommentInlineFormSet( MandatoryInlineFormSet ): def clean_rating(self,form): """ rating must be 0..5 by .5 increments """ rating = float( form.cleaned_data['rating'] ) if rating < 0 or rating > 5: raise ValidationError("rating must be between 0-5") if ( rating / 0.5 ) != int( rating / 0.5 ): raise ValidationError("rating must have .0 or .5 decimal") def clean( self ): super(CommentInlineFormSet, self).clean() for form in self.forms: self.clean_rating(form) class CommentInline( MandatoryTabularInline ): formset = CommentInlineFormSet model = Comment extra = 1