Issue
Let's say I have the following Django models, which represents a sorted relationship between a Parent and Child:
class Parent(models.Model):
name = models.CharField(max_length=50)
children = models.ManyToManyField("Child", through="ParentChild")
class Child(models.Model):
name = models.CharField(max_length=50)
class ParentChild(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(fields=["parent", "child"], name="uc_parent_child"),
models.UniqueConstraint(fields=["parent", "sort_number"], name="uc_parent_child"),
]
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
child = models.ForeignKey(Child, on_delete=models.CASCADE)
sort_number = models.IntegerField()
def save(self, *args, **kwargs):
exising_sort_numbers = self.parent.parentchild_set.values_list(
"sort_number", flat=True
)
if self.sort_number in exising_sort_numbers:
raise Exception(f"Duplicate sort number: {self.sort_number}")
super().save(*args, **kwargs)
Now if I create the relationships using the through model, I get the exception for a duplicate sort_number
:
ParentChild.objects.create(parent=parent, child=child1, sort_number=0)
ParentChild.objects.create(parent=parent, child=child2, sort_number=0) # raises Exception
However, if I create the relationships using the .add
method, I don't get the exception:
parent.children.add(child1, through_defaults={"sort_number": 0})
parent.children.add(child2, through_defaults={"sort_number": 0}) # does NOT raise Exception
I know using the .add method doesn't call the .save method on the through model so I need to use the m2m_change
signal to run this logic. But I'm not sure how to get the sort_number
within this signal. Here's the code I have for the signal so far:
@receiver(m2m_changed, sender=Parent.children.through)
def validate_something(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == "pre_add":
for pk in pk_set:
child = model.objects.get(pk=pk)
exising_sort_numbers = instance.parentchild_set.values_list(
"sort_number", flat=True
)
# where's sort_number specified in through_defaults ???
Any idea how I can get this value and perform the "pre_add"
validation or is this not possible?
Solution
You have this constraint - models.UniqueConstraint(fields=["parent", "sort_number"], name="uc_parent_child")
, which means that you can't have more than one relation with the same parent
and sort_number
. There's even an extra check in ParentChild
's save
method to further enforce this. Makes sense to have an exception thrown when you try to create such a relation.
Also, the constraint name needs to be unique. I tried the code and couldn't make migrations as is.
If you do what you are trying to, you'll get that exception again when saving. Instead of trying to hack around the constraint, you should either change/remove it or adapt your code to work with it - don't try to create instance which will violate it.
As to your specific question,the instance you get in validate_something
is Parent
and there's no direct access to the intermediary instance or it's defaults. You also can't query the intermediary instance, because it does not exist yet.
Answered By - 4140tm
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.