10

I'm working with an Account model and I want to inner join it with a Settings model without having to create an additional settings_id column in my Account model, because the PK on the Account table matches the PK on the settings table exactly. I've been trying to set up a OneToOne relationship between these tables & columns, but can't figure out how to do that without having to create a new column or something. I'm trying to work with the existing schema as much as possible and would prefer to not create unnecessary columns.

Class Account(models.Model):
    login_id = models.AutoField(primary_key=True)
    settings = models.OneToOneField('Settings', to_field='login_id', null=True,
        related_name='settings', db_column='login_id') # Doesn't work because login_id already exists....


Class Settings(model.Model):
    login_id = models.OneToOneField('Account', to_field='login_id', related_name='account')

Basically, the query I'm trying to replicate is:

SELECT * FROM account
INNER JOIN settings
ON account.login_id=settings.login_id
WHERE account.login_id=1

Based on the errors, Django's ORM seems really persistent on creating a new column in the Account table, but I don't see the point of adding a new column when the relationship between the 2 tables is so simple: account.login_id = settings.login_id

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
  • Why do you have a one-to-one relationship on both models pointing to each other? You dont need to specify on both sides. Remove it on either one of them. Forward, and reverse relationships reachable through the ORM – karthikr Oct 04 '14 at 02:25
  • Since they're related, it seemed logical. I don't think that's causing the errors that I'm creating, but I'll try and remove the relationship from the Settings model and see if that helps. – Cory Danielson Oct 04 '14 at 02:26
  • That would be a part of the issue. The other thing is - what exactly are you trying to achieve ? It looks like you would not have to run a query like this in a typical case. – karthikr Oct 04 '14 at 02:30
  • I have an Account table for each user and a Settings table for each user. The PK's on these tables are identical. I'm working on the settings portion of the application and we're going to backfill the settings for each user after the feature is complete. Because of that, the Settings don't exist for every users yet. I'd like to set up the models so that the identical PK's are enforced (which i think the OneToOne on both sides would do) – Cory Danielson Oct 04 '14 at 02:35
  • Unless it is an absolute requirement, i would just leave it the way the ORM would handle this situation. The reason being, one and only one setting object could exist per user, and create one if not already present. (I dont know the biz requirement, but this would mean one less thing to manage. ) – karthikr Oct 04 '14 at 02:38
  • I don't understand why this isn't part of Django's models. It seems perfectly normal to have a PK on a table that's also used as an FK to another table? It seems trivial – Cory Danielson Oct 04 '14 at 02:43
  • 1
    Agreed. Here is one way you can achieve this (atleast get you started): http://stackoverflow.com/questions/2846029/django-set-foreign-key-using-integer - on save check if object exists, if not, create and assign. – karthikr Oct 04 '14 at 02:46

2 Answers2

18

Although I've never tried it myself, declaring the OneToOneField as the primary key should work.

class Account(models.Model):
    login_id = models.AutoField(primary_key=True)

class Settings(model.Model):
    account = models.OneToOneField('Account', to_field='login_id', 
        primary_key=True, related_name='settings')

Declaring the primary_key will keep Django from trying to create a primary key column for you, and with this setup you can access account.settings and settings.account or settings.account_id.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
  • Thanks, this was the answer. I didn't have a full understanding of how the `related_name` worked. With the schema I'm working with, I didn't need to use the `primary_key=True` suggestion that you made. – Cory Danielson Oct 06 '14 at 16:41
  • It's odd to me that you can define a property on one model basically from a parameter within the property of another model. It seems like it would make a really big codebase with a lot of model relationships a bit confusing to follow. – Cory Danielson Oct 06 '14 at 16:46
  • 1
    @CoryDanielson: There's an inherent tension here between *being explicit* and *not repeating yourself*. Django consistently prefers DRY, whereas, say, SQLAlchemy has you define the field on both ends of the relationship (I think). Django's approach feels right to me; on the other hand, in the case of a `ManyToManyField`, where an entire new table is created behind the scenes, I almost always prefer to set that table up explicitly. – Kevin Christopher Henry Oct 06 '14 at 17:35
  • Yeah, I suppose the naming conventions should be strong enough that you shouldn't have to think too hard about from what model/table a property is derived. I think I was just coming into Django with more explicit expectations that it actually requires. – Cory Danielson Oct 06 '14 at 19:55
5

Here's what I ended up doing:

class Account(models.Model):
    login_id = models.AutoField(primary_key=True)

    def get_settings_dict(self):
        try:
            return self.settings.to_dict()
        except Settings.DoesNotExist:
            return Settings.get_defaults_dict()

class Settings(model.Model):
    account = models.OneToOneField('Account', to_field='login_id', related_name='settings')
    # other settings properties
    # other settings properties

    def to_dict(self):
        # return current values as dict

    @staticmethod
    def get_defaults_dict():
        # return default values dict

This allowed me to access a user's settings via account.settings without having to create an additional settings_id column on the Account model. The OneToOne relationship wasn't needed on both models.

Also, the get_settings_dict method returns the default settings when the settings do not exist yet for a user. This is a nice convenience that prevents me from having to do a null check every time I access the user's settings via account.settings.

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51