0

I'm trying to validate a model with a ManyToManyField pointed at a parent model to be unique within a grandparent model. It's fine if different grandparents have grandchildren of the same name, but a grandparent may not have two grandchildren of the same name.

This question Discusses a solution if the relationships are ForignKeys, but that doesn't work here. The GrandChild has to be saved before I can reference the parents to test to see if it's unique within a GrandParent.

Since the Parents can't be attached until the GrandChild exists, how can the uniqueness of a GrandChild be validated against the GrandParent?

class GrandParent(models.Model):
    name = models.CharField(max_length=255)

class Parent(models.Model):
    name = models.CharField(max_length=255)
    grandparent = models.ForeignKey(GrandParent)

    class Meta:
        unique_together('name', 'GrandParent')    

class GrandChild(models.Model):
    name = models.CharField(max_length=255)
    parents = models.ManyToManyField(Parent)

    def validate_unique(self, exclude=None):
        # validate that GrandChild is unique within a given GrandParent

I tried adding the save() method to the grandchild method

def save(self, *args, **kwargs):
    grand_parent = self.parents.all()[0].grandparent
    grand_children = [child for child in parent.grandchild_set.all() for parent in grandparent.parent_set.all()]

    for grand_child in grand_children:
        if grand_child.name == self.name and grand_child != self:
            raise IntegrityError("must be unique")

But this gave the IntegrityError

ValueError: "<GrandChild: name>" needs to have a value for field "grandchild" before this many-to-many relationship can be used.
Community
  • 1
  • 1
Cohan
  • 4,384
  • 2
  • 22
  • 40
  • why can't you validate uniqueness when calling from the save method? You can say, if a grandparent contains a parent that contains a grandchild with x, y, z attributes, it won't be unique given the data passed to this grandchild currently being saved, so reject – Ryan Castner Apr 19 '17 at 16:35
  • I still run into the manytomany issue where the instance doesn't exist, but I need the instance to test for uniqueness. – Cohan Apr 19 '17 at 17:19

1 Answers1

0
def save(self, *args, **kwargs):
    grand_parent_id = kwargs.pop('grand_parent_id')
    if grand_parent_id is None:
        raise SomeException(...)
    grand_parent = GrandParent.objects.get(pk=grand_parent_id)
    for parent in grand_parent.parent_set.all():
        for grand_child in parent.grandchild_set.all():
           if grand_child.name == self.name:
               raise IntegrityError("Name must be unique")

# views.py
grand_parent = get_object_or_404(GrandParent, pk=kwargs.get('grand_parent_pk'))
if form.is_valid():
    form.save(grand_parent_id=grand_parent.pk)
Ryan Castner
  • 954
  • 2
  • 10
  • 21
  • I see how that could work, but at this point, I am trying to also do unit tests on the model (and haven't started implementing views). I'd also like to have it be valid for interfaces other than just the views. – Cohan Apr 19 '17 at 17:36
  • I don't think you understand how save() works, it is always called on model creation. Try to just do `save(self, *args, **kwargs): raise ValidationError('Nope.')` and do `GrandChild.objects.create(name='bob')` and you will get the `ValidationError`, views are just one of the most common interfaces for object creation so I was showing you how that worked. You can do `GrandChild.objects.create(name='bob', grand_parent_id=GrandParent.objects.filter(name='foo')[0].pk)` and it will work that way too – Ryan Castner Apr 19 '17 at 18:39