【问题标题】:Inline Form Validation in DjangoDjango 中的内联表单验证
【发布时间】:2010-10-27 00:53:15
【问题描述】:

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

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

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

【问题讨论】:

    标签: django django-forms


    【解决方案1】:

    情况有所好转,但仍需要一些工作。 Django现在提供validate_minmin_num属性,如果min_num在formset实例化过程中取自Inlinevalidate_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]
    

    【讨论】:

      【解决方案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  
      

      【讨论】:

      • 是否可以对 extra = 0 做同样的事情?
      • @Siva - 我刚刚检查过,是的,你可以有 extra=0。但是,如果您希望评论(在我的情况下)是强制性的,那么您可能应该给用户一个空白表格或不使其成为强制性的。
      【解决方案3】:

      @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]
      

      试试这个它也有效:)

      【讨论】:

      • 哎呀,并不是要赞成这个。当“删除”复选框被选中时它不起作用。
      • 不明白您的问题?此代码确保每个Invoice 必须有一个InvoiceOrder。而且当时没有删除复选框!
      【解决方案4】:

      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')
      

      【讨论】:

        【解决方案5】:

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

        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]
        

        【讨论】:

        • 我发现如果勾选了删除框,可以用0个订单进行验证。请参阅我对解决该问题的修订课程的回答。
        • 非常感谢您的修复(以及 Dan 的改进)。作为对其他人的可能提示,我制作了一个“类 MandatoryInlineFormSet(BaseInlineFormSet)”,然后从中派生了 InvoiceAdminFormSet。在我的 InvoiceAdminFormSet 中,我有一个 clean() 方法,它执行自定义验证,但首先回调 MandatoryInlineFromSet.clean()。
        • 删除时也为我工作:如果 form.cleaned_data: ------ 替换为 ------ 如果 form.cleaned_data 而不是 form.cleaned_data。获取('删除',假):
        • 你可能想在开头或结尾调用父类的clean方法:super(InvoiceOrderInlineFormset, self).clean()
        • 我正在尝试使用hasattr() 函数而不是上面的try: except ,但代码行为异常。它计算所有形式!你能告诉我为什么会这样吗
        猜你喜欢
        • 2023-04-09
        • 2016-03-15
        • 1970-01-01
        • 2013-11-15
        • 1970-01-01
        • 2011-09-17
        • 2013-09-03
        • 2011-05-17
        • 2011-06-02
        相关资源
        最近更新 更多