When saving Django models that have cyclical foreign keys, how to save all at once?

Question:

I currently have the following models. Both Chat and Message have references to each other.

class Chat(models.Model):
    id   = models.BigAutoField(primary_key=True)

    subject      = models.CharField(max_length=200)

    last_message = models.OneToOneField(to='Message',
                                        on_delete=models.PROTECT,
                                        related_name='last_chat')

class Message(models.Model):
    id   = models.BigAutoField(primary_key=True)

    chat = models.ForeignKey(to='Chat',
                             on_delete=models.CASCADE)

    user = models.ForeignKey(to='accounts.User',
                             on_delete=models.PROTECT)

    text = models.CharField(max_length=200)

    date = models.DateTimeField()

The problem is, in order to create a new chat room, I will have to insert both a new Chat object and at least one new Message object.

But since they both reference each other, inserting either first without the other will cause a ForeignKey constraint error and crash.

How do I solve this? Should I delay the enforcement of foreign key constraints until after I have saved both? Any suggestions are welcome.

Asked By: AlanSTACK

||

Answers:

The problem as you say is that you have a circular relationship. But I don’t think you need it; the last_message field is unnecessary, since you can always query the last message by doing chat.message_set.order_by('date').last().

Answered By: Daniel Roseman

I would suggest using post_save signal for setting last message. First we need to make last_message nullable.

class Chat(models.Model):
    id   = models.BigAutoField(primary_key=True)

    subject      = models.CharField(max_length=200)

    last_message = models.OneToOneField(to='Message', null=True,
                                        on_delete=models.PROTECT,
                                        related_name='last_chat')
class Message(models.Model):
    ...

# example of signal
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Message)
def set_last_message(sender, instance, created, **kwargs):
    if created:
        Chat.objects.filter(id=instance.chat_id).update(last_message=instance)

# in your controller code
chat = Chat.objects.create(...)
Message.objects.create(chat=chat, ...) # so your last_message will be set on this line

NOTE(31.01.20):

This could be achieved in newer django version without denormalization via Window function https://docs.djangoproject.com/en/3.0/ref/models/expressions/#window-functions

Answered By: Sardorbek Imomaliev
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.