首页 文章

Django中的内联表单验证

提问于
浏览
47

我想在管理员更改表格中强制执行整个内联表单集 . 因此,在我目前的情况下,当我点击发票表单上的保存时(在管理员中),内联订单表格为空白 . 我想阻止人们创建没有订单关联的发票 .

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

模型字段上的( required=True )等正常验证似乎不适用于此实例 .

5 回答

  • 67

    执行此操作的最佳方法是使用clean方法定义自定义formset,该方法验证至少存在一个发票订单 .

    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]
    
  • 18

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

    我花了一些时间试图弄清楚如何防止这种情况发生 . 第一种情况很简单 - 不包括将在计数中删除的表单 . 第二种情况比较棘手......如果检查了所有删除框,则没有调用 clean .

    遗憾的是,代码并不简单 . 从 full_clean 调用 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')
    
  • 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]
    

    试试这个它也有效:)

  • 2
    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
    
  • 4

    我们在2018年有很多很棒的新闻!唐纳德特朗普今天是美国的坚持者,我们仍在努力解决所需的内联问题 .

    情况变得更好但仍需要一些解决方法 . Django现在提供 validate_minmin_num attrs,如果 min_num 取自 Inline durring formset instantinating, validate_min 只能作为init formset参数传递 . 所以解决方案看起来像这样:

    class MinValidatedInlineMixIn:
        validate_min = True
        def get_formset(self, *args, **kwargs):
            return super().get_formset(validate_min=self.validate_min, *args, **kwargs)
    
    class InvoiceOrderInline(MinValidatedInlineMixIn, admin.StackedInline):
        model = InvoiceOrder
        min_num = 1
        validate_min = True
    
    class InvoiceAdmin(admin.ModelAdmin):
        inlines = [InvoiceOrderInline]
    

相关问题