Django中的内联表单验证

use*_*791 49 django django-forms

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

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

required=True模型字段上的正常验证如()似乎在此实例中不起作用.

Dan*_*man 72

执行此操作的最佳方法是使用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]
Run Code Online (Sandbox Code Playgroud)

  • 我发现如果选中删除框,则可以使用0个订单进行验证.请参阅我的答案以获得解决该问题的修订课程. (3认同)
  • 你可能想在开头或结尾调用父类的clean方法:`super(InvoiceOrderInlineFormset,self).clean()` (3认同)

Dan*_*een 21

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

我花了一些时间试图弄清楚如何防止这种情况发生.第一种情况很简单 - 不包括将在计数中删除的表单.第二种情况比较棘手......如果检查了所有删除框,则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')
Run Code Online (Sandbox Code Playgroud)


Kur*_*urt 5

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  
Run Code Online (Sandbox Code Playgroud)


Ahs*_*san 5

@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]
Run Code Online (Sandbox Code Playgroud)

试试这个它也有效:)