6

Please see the code below. Basically, when the user creates an object of this class, they need to specify the value_type. If value_type==2 (percentage), then percentage_calculated_on (which is a CheckboxSelectMultiple on the form/template side needs to have one or more items checked. The model validation isn't allowing me to validate like I'm trying to -- it basically throws an exception that tells me that the instance needs to have a primary key value before a many-to-many relationship can be used. But I need to first validate the object before saving it. I have tried this validation on the form (modelform) side (using the form's clean method), but the same thing happens there too.

How do I go about achieving this validation?

INHERENT_TYPE_CHOICES = ((1, 'Payable'), (2, 'Deductible'))
VALUE_TYPE_CHOICES = ((1, 'Amount'), (2, 'Percentage'))

class Payable(models.Model):
    name = models.CharField()
    short_name = models.CharField()
    inherent_type = models.PositiveSmallIntegerField(choices=INHERENT_TYPE_CHOICES)
    value = models.DecimalField(max_digits=12,decimal_places=2)
    value_type = models.PositiveSmallIntegerField(choices=VALUE_TYPE_CHOICES)
    percentage_calculated_on = models.ManyToManyField('self', symmetrical=False)

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.value_type == 2 and not self.percentage_calculated_on:
            raise ValidationError("If this is a percentage, please specify on what payables/deductibles this percentage should be calculated on.")
chefsmart
  • 6,873
  • 9
  • 42
  • 47
  • I have marked Manoj Govindan's answer below as "accepted" as it solves the problem. However, I would still like to have it validated using Django's model validation. So if anyone has any ideas, do make the effort to post it here. Thanks. – chefsmart Oct 04 '10 at 12:53
  • same question: http://stackoverflow.com/questions/7986510/django-manytomany-model-validation – user920391 Feb 08 '13 at 16:02

1 Answers1

2

I tested out your code in one of my projects' admin app. I was able to perform the validation you required by using a custom ModelForm. See below.

# forms.py
class MyPayableForm(forms.ModelForm):
    class Meta:
        model = Payable

    def clean(self):
        super(MyPayableForm, self).clean() # Thanks, @chefsmart
        value_type = self.cleaned_data.get('value_type', None)
        percentage_calculated_on = self.cleaned_data.get(
             'percentage_calculated_on', None)
        if value_type == 2 and not percentage_calculated_on:
            message = "Please specify on what payables/deductibles ..."
            raise forms.ValidationError(message)
        return self.cleaned_data

# admin.py
class PayableAdmin(admin.ModelAdmin):
    form = MyPayableForm

admin.site.register(Payable, PayableAdmin)

The Admin app uses the SelectMultiple widget (rather than CheckboxSelectMultiple as you do) to represent many to many relationships. I believe this shouldn't matter though.

Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
  • Errr... is `model` really an attribute of `admin.ModelAdmin`? – Dominic Rodger Oct 04 '10 at 09:31
  • @Dominic: It most certainly isn't :P Thanks for pointing it out. I've fixed it. – Manoj Govindan Oct 04 '10 at 09:34
  • I am doing something similar within my ModelForm, except I call super(MyPayableForm, self).clean() first and that I use self.instance.value_type and self.instance.percentage_calculated_on instead. – chefsmart Oct 04 '10 at 09:42
  • Also instead of doing raise forms.ValidationError(message), I "attach" the message to the field using self._errors["percentage_calculated_on"] = ErrorList([message]) and del it from the cleaned_data. Other things shouldn't matter, so I am incline do think I might be erring on that use of self.instance? – chefsmart Oct 04 '10 at 09:52
  • @chefsmart: I don't know how to do it inside a model. M2M attributes won't work unless the instance is saved, thereby making it impossible. Perhaps there is a way. I am unaware of it. – Manoj Govindan Oct 04 '10 at 10:15