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.

Answer

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)