92👍
The best way to do this is to define a custom formset, with a clean method that validates that at least one invoice order exists.
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]
22👍
Daniel’s answer is excellent and it worked for me on one project, but then I realized due to the way Django forms work, if you are using can_delete and check the delete box while saving, it’s possible to validate w/o any orders (in this case).
I spent a while trying to figure out how to prevent that from happening. The first situation was easy – don’t include the forms that are going to get deleted in the count. The second situation was trickier…if all the delete boxes are checked, then clean
wasn’t being called.
The code isn’t exactly straightforward, unfortunately. The clean
method is called from full_clean
which is called when the error
property is accessed. This property is not accessed when a subform is being deleted, so full_clean
is never called. I’m no Django expert, so this might be a terrible way of doing it, but it seems to work.
Here’s the modified class:
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')
- [Django]-In the Django admin interface, is there a way to duplicate an item?
- [Django]-How can I create a deep clone of a DB object in Django?
- [Django]-Django Migrations Add Field with Default as Function of Model
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
- [Django]-Django BooleanField as radio buttons?
- [Django]-Django REST framework serializer without a model
- [Django]-"<Message: title>" needs to have a value for field "id" before this many-to-many relationship can be used.
5👍
@Daniel Roseman solution is fine but i have some modification with some less code to do this same.
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]
try this it also works 🙂
- [Django]-Specifying limit and offset in Django QuerySet wont work
- [Django]-TemplateDoesNotExist – Django Error
- [Django]-How to server HTTP/2 Protocol with django
4👍
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]
- [Django]-Django removing object from ManyToMany relationship
- [Django]-Is there a list of Pytz Timezones?
- [Django]-Django custom field validator vs. clean
0👍
Simple solution, using built-in attrb in the form class, just override the defaults
class InlineFormSet(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super().__init__(error_class=BootStrapCssErrorList, *args, **kwargs)
form.empty_permitted = False for form in self.forms
InvoiceItemFormSet = forms.inlineformset_factory(
Invoice, InvoiceItem, form=InvoiceCreateItemForm, formset=InlineFormSet,
fields=('fish_name', 'pre_bag', 'total_bags', '_total_fishes', 'price', '_sub_total'),
extra=0, min_num=1, can_delete=True, validate_min=True
)
This code is inline formset factory from django forms, Use validate_min=True
for validate minimum row are valid or not. and add a empty_permitted=False
in BaseInlineFormset from django forms.
- [Django]-Django limit_choices_to for multiple fields with "or" condition
- [Django]-Django modelform NOT required field
- [Django]-Django Model Field Default Based Off Another Field in Same Model