Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Modelform (with excluded field)

I have a sample form:

class AdminDiscountForm(ModelForm):  
    class Meta:  
        model = Discount  
        exclude = ('company',)

the model it's pointing to is:

class Discount(models.Model):
    class Meta:
        verbose_name=_('Discount')
        verbose_name_plural=_('Discounts')
        unique_together = ('company','type')

    company = models.ForeignKey(Company)
    type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
    discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))

The form excludes the 'company' field because the user has already selected this using the UI.

i am planning on doing a:

company = blah
if form.is_valid():
    obj = form.save(commit=False)
    obj.company = company
    obj.save()

The problem is that the combination of 'company' and 'type' should be unique (hence the 'unique_together'). This is enforced in the database, so django doesn't care. I need to extend the clean() method of this form to check for uniqueness as such:

def clean(self):
    cleaned_data = self.cleaned_data
    # check for uniqueness of 'company' and 'type'

The problem here is that 'company' is not in there because it has been excluded. What is the best way to raise a form validation error in this case?

-- edit This is only for adding discount entries. There's no initial instance.

like image 511
Dim Avatar asked Apr 18 '11 16:04

Dim


2 Answers

Jammon's method is the one I use. To expand a bit (using your example):

models.py

class Discount(models.Model):
    class Meta:
        verbose_name=_('Discount')
        verbose_name_plural=_('Discounts')
        unique_together = ('company','type')

    company = models.ForeignKey(Company)
    type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
    discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))

forms.py

class AdminDiscountForm(ModelForm):  
    class Meta:  
        model = Discount  
        exclude = ('company',)

views.py

def add_discount(request, company_id=None):
    company = get_object_or_404(Company, company_id)

    discount=Discount(company=company)

    if request.method == 'post':
        form = AdminDiscountForm(request.POST, instance=discount)
        if form.is_valid():
            form.save()
            return HttpResponse('Success')
    else:
        form = AdminDiscountForm(instance=company)

    context = { 'company':company,
                'form':form,}

    return render_to_response('add-discount.html', context,
        context_instance=RequestContext(request))

This works by creating an instance of your discount model, then binding your form to this instance. This instance is not saved to your db but used to bind the form. This bound form has a value for company of the bound instance. It is then sent to your template for the user to fill out. When the user submits this form, and the form is validated, the model validation check will check for uniqueness of the unique together defined in Meta.

See Model Validation Docs and overriding clean for ModelForms

edit:

You can do a couple of things to catch non unique together entry attempts.

  1. Inside your form.is_valid() you can except an Integrity Error like this:

    if request.method == 'post':
        form = AdminDiscountForm(request.POST, instance=discount)
        if form.is_valid():
            try:
                form.save()
                return HttpResponse('Success')
            except IntegrityError:
                form._errors["company"] = "some message"
                form._errors["type"] = "some message"
        else:
            ...
    
  2. Use self.instance within the model form's clean method to check for uniqueness.

like image 151
dting Avatar answered Nov 15 '22 22:11

dting


You could try this:

discount = Discount(company = blah)
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
    discount = form.save()

And the docs say: By default the clean() method validates the uniqueness of fields that are marked as ... unique_together

like image 36
jammon Avatar answered Nov 15 '22 21:11

jammon