Hello there,
Here is a basic structure:
class Parent(models.Model):
name = models.CharField(...)
default_child = models.ForeignKey(
to="Child",
null=True,
on_delete=models.SET_NULL,
limit_choices_to=models.Q(parent_id=models.F("id")),
related_name="+",
)
class Child(models.Model):
name = models.CharField(...)
parent = models.ForeignKey(
to="Parent",
related_name="children",
)
Doing this I was hoping to make it possible to select a default child for parents through their admin panel’s change page. Though it doesn’t give me any error at any point, the “default_child” select field remains empty. Is there any other way to do that?
For my purpose, which was to only allow one default child in for each parent, I found that workaround:
class Parent(models.Model):
name = models.CharField(...)
class Child(models.Model):
name = models.CharField(...)
parent = models.ForeignKey(
to="Parent",
related_name="children",
)
is_default = models.BooleanField(
default=False,
)
class Meta:
constraints = [
models.UniqueConstraint(
name="one_default_child_per_parent",
fields=["is_default", "parent"],
condition=Q(is_default=True),
violation_error_message="Each parent can only contain one default child",
),
]
By moving the information on Child, I can make sure through the constraint that only one Child can be default for a Parent at any time. limit_choices_to is NOT a database constraint but a tool for forms like the admin panel to populate the field and validation, but it does not guarantee integrity at the database level.
If anybody has the answer for the initial question, I’m still interested in knowing the reason why it didn’t work.
As far as I have been able to determine, limit_choices_to
becomes a query filter in the referenced model, e.g. Child
. The object containing the limit_choices_to does not pass the variable through the F
function, so what happens is that the query as you have written it is checking to see if the parent_id
field is the same as the id
field of Child
.
Even replacing the Q
expression with a function returning the dict, I don’t believe there’s a way to access the current instance of the model when the function is being evaluated.
For the admin, I think you would need to use the formfield_for_foreignkey function in the admin class definition for this.
Hi Ken, thanks for that insight.
Now I look at it and I understand what’s playing: if I wanted to limit choices to children where a condition is met comparing two of their fields for example, I’d use F to reference a Child field, not a Parent field.
1 Like