2009-05-18 53 views
41

我想在管理员更改表单中制作一个完整的内联表单集。因此,在我当前的情况下,当我点击发票表单(在管理员)时,内联订单表单为空。我想阻止人们创建没有关联订单的发票。Django中的内联表单验证

任何人都知道一个简单的方法来做到这一点?

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

回答

63

要做到这一点,最好的方法是定义一个自定义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] 
+0

完美解决方案,谢谢 – user108791 2009-05-18 15:01:41

+3

我发现,如果删除复选框被选中,这是可能的0订单来验证。查看我的答案,找到解决该问题的修订课程。 – 2009-12-10 23:13:47

+0

非常感谢您为此解决方案(和丹增强)。 作为一个可能的提示给别人,我做了一个'类MandatoryInlineFormSet(BaseInlineFormSet)',然后从中派生了InvoiceAdminFormSet。在我的InvoiceAdminFormSet中,我有一个用于自定义验证的clean()方法,但首先调用MandatoryInlineFromSet.clean()。 – Kurt 2010-06-03 15:19:45

18

丹尼尔的回答非常好,它为我工作的一个项目,但后来我意识到,由于道路Django表单的工作,如果你正在使用can_delete,检查删除框,同时节省,这是可能的,以验证W¯¯/o任何命令(在这种情况下)。

我花了一段时间试图找出如何防止发生。第一种情况很简单 - 不包括将在计数中被删除的表格。第二种情况更棘手......如果全部删除框被选中,则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') 
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

@Daniel罗斯曼的解决方案是好的,但我有一些较少的代码做一些修改,以做到这一点相同。

​​

试试这个它也可以:)