5

How to define OneToOne relationship to the same Model?

I have a model called Order which can be paired with another one Order. Now I'm trying to figure out how to handle models for this relationship.

My ideas:

class Order(models.Model):
    paired_order = models.OneToOneField(self)

OR:

class Pairing(models.Model):
    order1 = models.ForeignKey(Order, related_name='pairing')
    order2 = models.ForeignKey(Order, related_name='pairing')

What do you think? Which is more efficient?

I want to have simple calling of paired ones. So I would do something like:

order.paired_order 

OR:

order.pairing.paired

I want this relation symmetrical so for each pair of orders I call the same thing and get paired order.

Pairing model would be a good solution because I can add additional information to this relationship, but there is a problem that I would have to detect which order is it, so I couldn't call order.pairing.order1 because I don't know whether I'm not calling the same order.

EDIT:

>>> from _app import models
>>> order1 = models.Order(flight_number="xxx")
>>> order2 = models.Order(flight_number="yyy", paired_order=order1)
>>> order1.paired_order.flight_number

RETURNS None object has not ....

The problem is that when I set order1 is a paired order for order2, I want the same thing in opposite direction. So order1.paired_order = order2 do this as well order2.paired_order = order1.

Milano
  • 18,048
  • 37
  • 153
  • 353
  • What are you asking about here? How to define a relationship or which one is better? – Sayse Jul 08 '16 at 07:31
  • How to define because these doesn't work. I've added an edit at the bottom of the question. – Milano Jul 08 '16 at 07:33
  • 2
    Possible duplicate of [Django self-referential relationship?](http://stackoverflow.com/questions/4547101/django-self-referential-relationship) – Sayse Jul 08 '16 at 07:34
  • 2
    Possible duplicate of [Django-model: how to reference to self?](http://stackoverflow.com/q/4910905/1324033) – Sayse Jul 08 '16 at 07:34
  • 2
    Possible duplicate of [Django self-referential foreign key](http://stackoverflow.com/q/15285626/1324033) – Sayse Jul 08 '16 at 07:35
  • As I mentioned in comment below, the biggiest problem is with symmetry. If I have order1 and order2 I want to order1.paired to get order2 but the same with order2 - order2.paired (or with pairing model order1.pairing.paired) – Milano Jul 08 '16 at 07:36
  • 1
    Why would it work any different to any other one to one relationship? You should try using any of these duplicates – Sayse Jul 08 '16 at 07:37
  • I've already tried and it doesn't work as I expected. I want it to be symmetrical. I'm editing question and adding an example at the bottom. – Milano Jul 08 '16 at 07:46
  • You never save the orders in the example you've given, hence the relationship cannot be created – Sayse Jul 08 '16 at 07:52
  • It is being saved. Probably because I do it in shell_plus. This works: >>> order2.paired_order.flight_number returns 'xxx' – Milano Jul 08 '16 at 07:56

3 Answers3

3

Pairing model would be a good solution because I can add additional information to this relationship.

In that case, you could model that group of "orders" (you've called it Pairing) and add a shortcut to retrieve the paired order.

class OrderPair(models.Model):
    pass        
    # additional information goes here


class Order(models.Model):
    pair = models.ForeignKey(to="OrderPair", related_name="orders")
    # you'll have to add custom validation 
    # to make sure that only 2 orders can belong to the same "OrderPair"

    @property
    def paired_order(self):
         return self.pair.orders.exclude(id=self.id).first()

Once you've got this working, you might also want to cache the paired order to avoid too many queries. In that case, you don't want a related name so you can use + (the less explicit thing in Django ever).

class Order(models.Model):
    ...
    cached_paired_order = models.ForeignKey(to='self', related_name='+')

@property
def paired_order(self):
     if self.cached_paired_order:
          ...
     else:
          ...
François Constant
  • 5,531
  • 1
  • 33
  • 39
2

Having this problem myself, the term 'symmetrical' was key to finding the answer: https://code.djangoproject.com/ticket/7689

class Order(models.Model):
    paired_order = models.OneToOneField(self)

def save(self, *args, **kwargs):
    super(Order, self).save(*args, **kwargs)
    self.paired_order.paired_order = self
    super(Order, self.paired_order).save()
Renoc
  • 419
  • 4
  • 13
1

The ForeignKey accepts as an argument not just a class, but also a string name of the form ForeignKey('ModelNameInSameModelsPyFile') or ForeignKey('app_name.ModelName).

In your case, it could be like

class Order(models.Model):

     paired = models.ForeignKey('Order', null=True)

You can read more at https://docs.djangoproject.com/en/1.8/ref/models/fields/#foreignkey

zsepi
  • 1,572
  • 10
  • 19
  • 4
    Or just use `"self"`. – Nils Werner Jul 08 '16 at 07:34
  • Ok, so let's have order1 and order2. Now when I do order1.paired = order2, can I call order1 this way: order2.paired? Probably not and that's the problem. – Milano Jul 08 '16 at 07:34
  • You can have a `related_name` but I believe you cannot call it `paired` - you need another name. – François Constant Jul 08 '16 at 07:37
  • Depends. If pairing is a unique, i.e.: one order can only be paired to a single other order, you could use a `OneToOneField` (https://docs.djangoproject.com/en/1.8/ref/models/fields/#onetoonefield). and you need to set the `related_name` (but it cannot be `paired`, maybe `paired_to`). If it's not unique, then it can be used by `order2..filter(....)` – zsepi Jul 08 '16 at 07:39
  • It has to be unique. One order has one paired order and vice versa. – Milano Jul 08 '16 at 07:42