8

When creating two instances of a model and connecting them using a OneToOneField, the connection gets created and saved automatically at object creation:

from django.db import models

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    next = models.OneToOneField('self', on_delete=models.SET_NULL, related_name='prev', null=True, blank=True)

>>> m2 = MyModel.objects.create(name="2")
>>> m1 = MyModel.objects.create(name="1", next=m2)
>>> m2.prev
<MyModel: 1>
>>> m2.refresh_from_db()
>>> m2.prev
<MyModel: 2>

However, when creating the same connection but using the reverse field, the creation is also done automatically but not the save.

>>> m1 = MyModel.objects.create(name="1")
>>> m2 = MyModel.objects.create(name="2", prev=m1)
>>> m1.next
<MyModel: 2>
>>> m1.refresh_from_db()
>>> m1.next

Note that the last statement doesn't print anything since it returns None


How can I have it always save the relation when created using the reverse field without having to manually use .save() each time?

Oskar Persson
  • 6,605
  • 15
  • 63
  • 124
  • Why does it change the `prev` value when you call refresh_from_db? Is that the expected result? – Mojimi Mar 30 '17 at 18:07
  • `prev` isn't in the database when `refresh_from_db` is called and is therefore replaced with what is in the database, i.e. `None` – Oskar Persson Mar 30 '17 at 18:09

2 Answers2

7

Probably simple way to achieve this could be using pre_save/post_save signal which you see as a viable solution. But not sure how viable this answer would be, try to make some mods and see if this works!

from django.db.models.signals import post_save
from django.dispatch import receiver

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    next = models.OneToOneField('self', on_delete=models.SET_NULL, related_name='prev', null=True, blank=True)

@receiver(post_save, sender=MyModel)
def mymodel_post_save(sender, instance, **kwargs):
     if hasattr(instance, 'prev'): # if prev exists
          # now check if prev is added next
          if not instance.prev.next: # if next is not present 
                 instance.prev.next = instance
                 MyModel.objects.filter(
                     pk=instance.prev.pk
                 ).update(next=instance)
Oskar Persson
  • 6,605
  • 15
  • 63
  • 124
rrmerugu
  • 1,826
  • 2
  • 20
  • 26
  • `if not instance.prev.next` will always evaluate to false since it doesn't check what is actually in the database, only in memory. Removing the check would mean that the update will always be called. Would need something that checks what has only been changed in memory. – Oskar Persson Apr 09 '17 at 11:50
3

This is a terrible idea.

You will confuse everyone looking at your code.

I'm even surprised that MyModel.objects.create works instead of throwing an invalid keyword argument. I'll probably open a ticked for this.

When you call:

m2 = MyModel.objects.create(name="2", prev=m1)

in order to work as you expect, the .create method needs to call save on the m1 instance, because m1 is the instance that holds the relation (via next)

Who would expect that? You are hiding functionality. Some quotes from PEP 20

Explicit is better than implicit.

There should be one-- and preferably only one --obvious way to do it.

My advice is to change the relation to:

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    prev = models.OneToOneField('self',
                                null=True,
                                blank=True,
                                related_name='next',
                                on_delete=models.SET_NULL)

Because, its much more likely to know what prev is at a creation time.

Todor
  • 15,307
  • 5
  • 55
  • 62