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.
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()
.
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
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.
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()
.
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