Проверка встроенной формы в Django
Я хотел бы сделать полный встроенный набор форм внутри формы изменения admin обязательным. Таким образом, в моем текущем сценарии, когда я ударил сохранение в форме счета (в Admin), встроенная форма заказа пуста. Я бы хотел остановить людей, создающих счета-фактуры без каких-либо заказов.
Кто-нибудь знает простой способ сделать это?
Обычная проверка, подобная (required=True
) в поле модели, как представляется, не работает в этом случае.
Ответы
Ответ 1
Лучший способ сделать это - определить пользовательский набор форм с помощью чистого метода, который проверяет, что существует хотя бы один порядок выставления счета.
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]
Ответ 2
Ответ Дэниэла превосходный, и он работал у меня в одном проекте, но затем я понял из-за того, как работают Django-формы, если вы используете can_delete и проверяете флажок 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')
Ответ 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]
попробуйте это, он также работает:)
Ответ 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
Ответ 5
Ситуация стала немного лучше, но все еще нужно немного поработать. Django предоставляет min_num
validate_min
и min_num
и если min_num
берется из Inline
во время мгновенного набора форм, 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]