Hi Sanjay,
You may want to try signals.
https://docs.djangoproject.com/en/2.0/topics/signals/


-----Original Message-----
From: django-users@googlegroups.com [mailto:django-users@googlegroups.com] On 
Behalf Of Sanjay Bhangar
Sent: Tuesday, July 31, 2018 1:09 PM
To: django-users@googlegroups.com
Subject: Validating count + property of a Many2Many with a through table

Hello!

This is more of a code organization question than a "something is not working" 
question - it might get slightly rambling, so if this kind of question is not 
your cup of tea, you have been warned :-)

So - I have a set of models like this (highly simplified):

class Item(models.Model):
  title = models.CharField(...)
  price = models.IntegerField(...)
  tags = models.ManyToManyField(Tag, through=ItemTag)

class Tag(models.Model):
  name = models.CharField(...)

class ItemTag(models.Model):
  item = models.ForeignKey(Item)
  tag = models.ForeignKey(Tag)
  is_primary = models.BooleanField()


So, I have an Item model that has some properties - it has a ManyToMany 
relationship with Tag, and one or more tags can be selected as "is_primary" 
tags for the model.

Now, to validate properties on the Item model, I call validation methods inside 
a "clean" method on the Item model, something like:

  def clean(self):
    if self.price > 10000:
      raise ValidationError('Price cannot be so high!")

This is simplified, but you get the idea - this works great, errors are 
propagated in the Admin, as well as raised when I import data via a CSV file 
for example. I know there's a few different places one can define validations - 
I personally like to have them in the model, but I would love to hear alternate 
view-points if this is not the best place to put them.

So, coming to the problem: there is now a requirement to validate that a model 
cannot have more than 3 primary tags associated with it - i.e.
cannot be associated with more than 3 tags with the through table specifying 
is_primary=True.

This, of course, cannot really go in the `clean` method of the Item model. The 
ItemTag relationships don't exist yet, and I don't have the data I need to be 
able to validate a maximum of 3 tags.

The problem is, I cannot seem to be able to do this kind of validation in the 
ItemTag model either, since it depends on knowing how many existing ItemTag 
relationships to Item there are - and before the entire transaction is 
committed to the database, a single ItemTag doesn't know whether the total 
exceeds the allowed 3.

The only place to put it that worked (and made sense) was in the Formset 
definition for the inline form in the admin.

So, we landed up over-riding the formset used for the ItemTag inline in the 
admin, adding our validations to the `clean` method - roughly
like:

class ItemImageInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        image_count = 0
        for form in self.forms:
            if form.cleaned_data.get('primary') == True:
                image_count += 1
        if image_count > 3:
            raise ValidationError('Cannot have more than 3 primary tags')


This works. However, it seems a bit strange to have validations split between a 
model method, and a subclass of a formset of an admin form.
It seems confusing in terms of maintainability - also, writing tests for the 
validations in the model seemed a lot more straightforward when validations 
were just in the model's `clean` method.

I can see why this is so. Am just wondering if I'm getting something wrong in 
terms of patterns here / if there is a way other folks solve this, to, ideally, 
be able to have all the validation that pertains to a particular model in one 
place, and be able to test all model validations in a consistent manner.

Thank you to whoever read this far :-)

-Sanjay

--
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/CAG3W7ZFZnemOSOwDRjDN--JLKdF94QEJNrbuTw2MCPoK1QxP7Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/8888981a20654ff4b41e32c600464ddd%40ISS1.ISS.LOCAL.
For more options, visit https://groups.google.com/d/optout.

Reply via email to