Reputation: 23321
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.
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?
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
)
)
class DiscountForm(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'description',
'is_activa',
)
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,
})
I was trying also different approach with separate form used for POST, but it crashes with same error:
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
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.
Upvotes: 3
Views: 6511
Reputation: 1081
When assigning exhibitor using self.exhibitor = exhibitor
, this sets a property
on your form which has nothing to do with the content for the form.
To actually set the exhibitor, use the following code instead:
import copy
.
.
.
if request.method == 'POST':
# request.POST is an immutable QueryDict so it needs to be copied
form_data = copy.copy(request.POST)
form_data['exhibitor'] = exhibitor.id
form = DiscountFormAdd(data=form_data)
and entirely drop setting the exhibitor
inside your form's __init__
This way, your form's data (which is used to create your Discount
) object will be correct and can be cleaned
[Edit] How to create a hidden input field in a ModelForm:
class DiscountFormAdd(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DiscountFormAdd, self).__init__(*args, **kwargs)
self.fields['exhibitor'].widget = forms.HiddenInput()
...
or you can also use the widgets
attribute of the Meta class
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
widgets = {'exhibitor': forms.HiddenInput()}
...
Upvotes: 2