2

I know what ForeignKeys and OneToOneFields are, as well as ManyToManyField, how they work, and when to use them. However, I am working with a project, whose Many part of the relation cannot be modified. So, suppose I want to let a user have many phone numbers, I would normally do this:

# my_app/models.py
from django.db import models
class User(Model):
    ...

class PhoneNumber(models.Model):
    user = models.ForeignKey(User)

The problem I have is that my PhoneNumber model equivalent is from a third-party package, already populated with records, and not subclassed in my own app. That is

# third_party_django_package/models.py
from django.db import models
class PhoneNumber(models.Model):
    # This cannot change

# my_app/models.py
from django.db import models
from third_party_django_package.models import PhoneNumber
class User(Model):
    # These do not work -- a user can have more than one phone number
    phone_number = models.ForeignKey(PhoneNumber)
    phone_number = models.OneToOneField(PhoneNumber)

    # This is close, but I want a phone number to belong to only one User
    phone_numbers = models.ManyToManyField(PhoneNumber, related_name=...)

    def clean(self):
        # Validating the M2M relation costs extra queries, is slow, and 
        # is prone to race conditions

This is all pseudocode.

Without using yet another third-party package that accesses Django's internal members, which makes the project even less forwards-compatible, what options do I have left to achieve a proper OneToManyField with the correct schema-level constraints?

Community
  • 1
  • 1
Brian
  • 7,394
  • 3
  • 25
  • 46

1 Answers1

8

You could create another intermediate model, then make phone number OneToOneField to that model, then in that model you define User as ForeignKey.

class UserPhoneNumber(models.Model):
    phone_number = models.OneToOneField(PhoneNumber)
    user = models.ForeignKey(User)

It's a little cumbersome, but at least it achieves what you need.

Edit:

As @Daniel said, it's possible to do this using m2m relationship with through model, with unique_together on the fields:

class User(Model):
    phone_numbers = models.ManyToManyField(PhoneNumber, through=UserPhoneNumber)

class UserPhoneNumber(Model):
    phone_number = models.ForeignKey(PhoneNumber)
    user = models.ForeignKey(User)

    class Meta:
        unique_together = ('phone_number', 'user')

This will make your life easier if you want to look up on user's phone numbers by doing numbers = user.phone_numbers.all().

Shang Wang
  • 24,909
  • 20
  • 73
  • 94
  • I don't think you can do better than this, or an equivalent model inheritance setup. – Peter DeGlopper Dec 23 '15 at 22:34
  • Aha, this worked in my head! Agreed about everything you said, including the cumbersome part. – Brian Dec 23 '15 at 22:34
  • You might actually be able to define this as the through table of an M2M from User to PhoneNumber, in which case it becomes significantly less cumbersome. – Daniel Roseman Dec 23 '15 at 22:50
  • Doesn't the M2M implementation still allow for the possibility of a phone number belonging to more than one user, which the OP notes in pseudocode as undesirable? The `unique_together` constraint prevents the same phone number from being recorded multiple times, but doesn't prevent different users from sharing one. – Peter DeGlopper Dec 23 '15 at 23:16
  • Yeah, I asked the question after realising an M2M (with `through` or otherwise) is not possible. The original answer is still better than any other option in this/my scenario. – Brian Dec 25 '15 at 15:46