57

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

有谁知道一个简单的方法来做到这一点?

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

4

5 回答 5

85

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

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]
于 2009-05-18T14:06:07.777 回答
22

丹尼尔的回答非常好,它在一个项目上对我有用,但后来我意识到由于 Django 表单的工作方式,如果您使用 can_delete 并在保存时选中删除框,则可以验证没有任何订单(在此案子)。

我花了一段时间试图弄清楚如何防止这种情况发生。第一种情况很简单——不要将要删除的表单包括在计数中。第二种情况更棘手......如果所有删除框都被选中,那么clean就没有被调用。

不幸的是,代码并不完全简单。访问属性时调用的clean方法被调用。删除子表单时不会访问此属性,因此永远不会调用此属性。我不是 Django 专家,所以这可能是一种糟糕的方法,但它似乎有效。full_cleanerrorfull_clean

这是修改后的类:

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')
于 2009-12-10T23:11:20.637 回答
4
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  
于 2010-06-03T15:21:44.103 回答
4

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

试试这个它也有效:)

于 2011-07-20T15:04:55.140 回答
3

The situation became a little bit better but still needs some work around. Django provides validate_min and min_num attributes nowadays, and if min_num is taken from Inline during formset instantiation, validate_min can be only passed as init formset argument. So my solution looks something like this:

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]
于 2018-04-06T07:32:30.133 回答