In my model's clean method I validate if given in foreign key exhibitor is_premium and also validate that he does not have more then MAX_DISCOUNTS_PER_EXHIBITOR active objects.
It works perfectly fine in django admin. Both when adding as well when editing.
I would like to get it working in my custom (non-django-admin) views. I am using ModelForms.
first approach
I assign exhibitor in view, to initially commit=False saved object.
And it crashes with DoesNotExist: Discount has no exhibitor, because clean is executed, but exhibitor is not yet assigned. What should be the proper way of implementing it?
models.py
class Discount(models.Model):
exhibitor = models.ForeignKey(
'core_backend.Exhibitor', related_name='discounts')
is_active = models.BooleanField(default=False, verbose_name=u"Aktywny")
title = models.TextField(verbose_name=u"Tytuł")
def clean(self):
if not self.exhibitor.is_premium:
raise ValidationError(
u'Discounts only for premium')
count = Discount.objects.filter(
is_active=True,
exhibitor=self.exhibitor
).count()
if not self.pk:
# newly added
count = count + (1 if self.is_active else 0)
else:
# edited
discount = Discount.objects.get(pk=self.pk)
if discount.is_active and not self.is_active:
count = count - 1
elif not discount.is_active and self.is_active:
count = count + 1
if count > settings.MAX_DISCOUNTS_PER_EXHIBITOR:
raise ValidationError(
u'Max %s active discounts' % (
settings.MAX_DISCOUNTS_PER_EXHIBITOR
)
)
forms.py
class DiscountForm(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'description',
'is_activa',
)
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountForm(data=request.POST)
if form.is_valid():
discount = form.save(commit=False)
discount.exhibitor = exhibitor
discount.save()
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
Second approach
I was trying also different approach with separate form used for POST, but it crashes with same error:
forms.py
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'exhibitor',
'is_active',
)
def __init__(self, exhibitor, *args, **kwargs):
self.exhibitor = exhibitor
super(DiscountFormAdd, self).__init__(*args, **kwargs)
def save(self, commit=False):
discount = super(DiscountFormAdd, self).save(commit=False)
discount.exhibitor = self.exhibitor
if commit:
discount.save()
return discount
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountFormAdd(data=request.POST, exhibitor=exhibitor)
if form.is_valid():
discount = form.save(commit=True)
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
I would like to stick with model clean validation, rather than jumping into forms clean. If django admin is able to do it, then it is surely possible to implemented in custom modelforms.