Search code examples
pythondjangoforeign-keys

Trouble Creating QuerySet of Django model objects with self-referential ForeignKey


Simply put I have two models

A dialogue model:

class Dialogue(models.Model):
  content = models.TextField()
  created_at = models.DateTimeField(auto_now_add = True)
  updated_at = models.DateTimeField(auto_now = True)

And a choice model:

class Choice(models.Model):
  option = models.CharField(max_length = 255)
  content = models.TextField()
  dialogue = models.ForeignKey(
    Dialogue, 
    related_name = "choices", 
    blank = True, 
    null = True, 
    on_delete = models.CASCADE
  )
  subChoices = models.ForeignKey(
    "self",
    related_name = "parent",
    blank = True,
    null = True,
    on_delete = models.CASCADE
  )
  created_at = models.DateTimeField(auto_now_add = True)
  updated_at = models.DateTimeField(auto_now = True)

You may have noticed the recursive ForeignKey "Choice.subChoices". This is where my issue lies.

If I attempt to use the add() method via the instance of this model that I want to be added to a Choice's list of further choices, I get a "'Choice' object has no attribute 'add'". If I attempt to the the reverse and add an instance of the Choice model to a Choice's parents attribute it overwrites instead of creating a query set.

Examples of both below:

choice1 = Choice.objects.get(id = 1)
choice2 = Choice.objects.get(id = 2)
choice3 = Choice.objects.get(id = 3)
choice1.subChoices.add(choice2)
>>> AttributeError: 'Choice' object has no attribute 'add'
choice2.parent.add(choice1)
choice3.parent.add(choice2)
print(choice1.subChoices)
>>> Choice object(3)

A print statement of choice1.subChoices.all() returns a similar attribute error.

My goal here is to have Choice objects optionally have a list of Choice objects, should the outcome of a choice result in a further selection.


Solution

  • You defined this in the wrong way. The ForeignKey acts as a many-to-one relation, not a one-to-many relation. The ForeignKey thus always points to a parent, not to its children.

    This ForeignKey should thus be named parent and the related_name should be named subChoices. The modeling thus should look like:

    class Choice(models.Model):
        option = models.CharField(max_length = 255)
        content = models.TextField()
        dialogue = models.ForeignKey(
            Dialogue,
            related_name='choices',
            blank=True, 
            null=True, 
            on_delete=models.CASCADE
        )
        parent = models.ForeignKey(
            'self',
            related_name='subChoices',
            blank=True,
            null=True,
            on_delete = models.CASCADE
      )
      created_at = models.DateTimeField(auto_now_add=True)
      updated_at = models.DateTimeField(auto_now=True)