Django model field that's actually a reference to a field in a related model










2















Apologies in advance if this question is hard to follow – I'm not sure how to phrase it. Basically, I'm trying to create a sort of "pseudo-field" in a Django model that works exactly like any other Django field except that it's actually a reference to a field on a related model.



As an example, suppose I am running a hotel for dogs. In my hotel, I have rooms, and each room is assigned to a customer and contains a dog.



class Customer(Model):
name = models.CharField(max_length=256, null=False)

class Dog(Model):
name = models.CharField(max_length=256, null=False)
customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner

class Room(Model):
customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL)
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)


The corresponding tables in the database look something like this



CUSTOMER:

| ID | NAME |
___________________
| 01 | John Smith |
| 02 | Jane Doe |


DOG:

| ID | NAME | CUSTOMER_ID |
____________________________
| 01 | Rover | 01 |
| 02 | Fido | 01 |
| 03 | Spot | 02 |


ROOM:

| ID | DOG_ID | CUSTOMER_ID |
_____________________________
| 01 | 01 | 01 |
| 02 | 03 | 02 |


So, my boss notices that we're storing redundant data in the DB: the rooms table doesn't really need to have its own customer ID column: the customer is always the resident dog's owner, and each room contains only one dog, and each dog has one owner, so we can always get the customer assigned to the room by going to the dog table and looking up the owner. I've been asked to remove the customer ID column from the rooms table in a way that is "totally transparent" to the rest of the code base.



To start with, I can convert customer into a @property in the Room class, with a custom getter:



class Room(Model):
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

@property
def customer(self):
return self.dog.customer


So now everywhere in the code where we do something like c = room.customer, it continues to work. The problem is, the code base is full of Django field queries like room__customer, which all stop working when I make customer into a @property of Room. I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent".



I did some research and I've tried implementing a custom manager and annotating the query set for Rooms:



class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(customer=F('dog__customer'))


When I do that, I can use room__customer in queries, but not room__customer__name, I suppose because F() is returning the primary key value rather than a model instance (https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations).



So my question is, is there a way to make this work that I just don't know about? A way to make it so that Room acts like it has a direct foreign key relationship with Customer, without having customer_id stored in the rooms table?










share|improve this question






















  • Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

    – Neil
    Nov 12 '18 at 18:18






  • 3





    Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

    – Neil
    Nov 12 '18 at 18:20











  • I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

    – J. Bestoso
    Nov 12 '18 at 18:37















2















Apologies in advance if this question is hard to follow – I'm not sure how to phrase it. Basically, I'm trying to create a sort of "pseudo-field" in a Django model that works exactly like any other Django field except that it's actually a reference to a field on a related model.



As an example, suppose I am running a hotel for dogs. In my hotel, I have rooms, and each room is assigned to a customer and contains a dog.



class Customer(Model):
name = models.CharField(max_length=256, null=False)

class Dog(Model):
name = models.CharField(max_length=256, null=False)
customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner

class Room(Model):
customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL)
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)


The corresponding tables in the database look something like this



CUSTOMER:

| ID | NAME |
___________________
| 01 | John Smith |
| 02 | Jane Doe |


DOG:

| ID | NAME | CUSTOMER_ID |
____________________________
| 01 | Rover | 01 |
| 02 | Fido | 01 |
| 03 | Spot | 02 |


ROOM:

| ID | DOG_ID | CUSTOMER_ID |
_____________________________
| 01 | 01 | 01 |
| 02 | 03 | 02 |


So, my boss notices that we're storing redundant data in the DB: the rooms table doesn't really need to have its own customer ID column: the customer is always the resident dog's owner, and each room contains only one dog, and each dog has one owner, so we can always get the customer assigned to the room by going to the dog table and looking up the owner. I've been asked to remove the customer ID column from the rooms table in a way that is "totally transparent" to the rest of the code base.



To start with, I can convert customer into a @property in the Room class, with a custom getter:



class Room(Model):
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

@property
def customer(self):
return self.dog.customer


So now everywhere in the code where we do something like c = room.customer, it continues to work. The problem is, the code base is full of Django field queries like room__customer, which all stop working when I make customer into a @property of Room. I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent".



I did some research and I've tried implementing a custom manager and annotating the query set for Rooms:



class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(customer=F('dog__customer'))


When I do that, I can use room__customer in queries, but not room__customer__name, I suppose because F() is returning the primary key value rather than a model instance (https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations).



So my question is, is there a way to make this work that I just don't know about? A way to make it so that Room acts like it has a direct foreign key relationship with Customer, without having customer_id stored in the rooms table?










share|improve this question






















  • Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

    – Neil
    Nov 12 '18 at 18:18






  • 3





    Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

    – Neil
    Nov 12 '18 at 18:20











  • I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

    – J. Bestoso
    Nov 12 '18 at 18:37













2












2








2








Apologies in advance if this question is hard to follow – I'm not sure how to phrase it. Basically, I'm trying to create a sort of "pseudo-field" in a Django model that works exactly like any other Django field except that it's actually a reference to a field on a related model.



As an example, suppose I am running a hotel for dogs. In my hotel, I have rooms, and each room is assigned to a customer and contains a dog.



class Customer(Model):
name = models.CharField(max_length=256, null=False)

class Dog(Model):
name = models.CharField(max_length=256, null=False)
customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner

class Room(Model):
customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL)
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)


The corresponding tables in the database look something like this



CUSTOMER:

| ID | NAME |
___________________
| 01 | John Smith |
| 02 | Jane Doe |


DOG:

| ID | NAME | CUSTOMER_ID |
____________________________
| 01 | Rover | 01 |
| 02 | Fido | 01 |
| 03 | Spot | 02 |


ROOM:

| ID | DOG_ID | CUSTOMER_ID |
_____________________________
| 01 | 01 | 01 |
| 02 | 03 | 02 |


So, my boss notices that we're storing redundant data in the DB: the rooms table doesn't really need to have its own customer ID column: the customer is always the resident dog's owner, and each room contains only one dog, and each dog has one owner, so we can always get the customer assigned to the room by going to the dog table and looking up the owner. I've been asked to remove the customer ID column from the rooms table in a way that is "totally transparent" to the rest of the code base.



To start with, I can convert customer into a @property in the Room class, with a custom getter:



class Room(Model):
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

@property
def customer(self):
return self.dog.customer


So now everywhere in the code where we do something like c = room.customer, it continues to work. The problem is, the code base is full of Django field queries like room__customer, which all stop working when I make customer into a @property of Room. I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent".



I did some research and I've tried implementing a custom manager and annotating the query set for Rooms:



class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(customer=F('dog__customer'))


When I do that, I can use room__customer in queries, but not room__customer__name, I suppose because F() is returning the primary key value rather than a model instance (https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations).



So my question is, is there a way to make this work that I just don't know about? A way to make it so that Room acts like it has a direct foreign key relationship with Customer, without having customer_id stored in the rooms table?










share|improve this question














Apologies in advance if this question is hard to follow – I'm not sure how to phrase it. Basically, I'm trying to create a sort of "pseudo-field" in a Django model that works exactly like any other Django field except that it's actually a reference to a field on a related model.



As an example, suppose I am running a hotel for dogs. In my hotel, I have rooms, and each room is assigned to a customer and contains a dog.



class Customer(Model):
name = models.CharField(max_length=256, null=False)

class Dog(Model):
name = models.CharField(max_length=256, null=False)
customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner

class Room(Model):
customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL)
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)


The corresponding tables in the database look something like this



CUSTOMER:

| ID | NAME |
___________________
| 01 | John Smith |
| 02 | Jane Doe |


DOG:

| ID | NAME | CUSTOMER_ID |
____________________________
| 01 | Rover | 01 |
| 02 | Fido | 01 |
| 03 | Spot | 02 |


ROOM:

| ID | DOG_ID | CUSTOMER_ID |
_____________________________
| 01 | 01 | 01 |
| 02 | 03 | 02 |


So, my boss notices that we're storing redundant data in the DB: the rooms table doesn't really need to have its own customer ID column: the customer is always the resident dog's owner, and each room contains only one dog, and each dog has one owner, so we can always get the customer assigned to the room by going to the dog table and looking up the owner. I've been asked to remove the customer ID column from the rooms table in a way that is "totally transparent" to the rest of the code base.



To start with, I can convert customer into a @property in the Room class, with a custom getter:



class Room(Model):
dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL)

@property
def customer(self):
return self.dog.customer


So now everywhere in the code where we do something like c = room.customer, it continues to work. The problem is, the code base is full of Django field queries like room__customer, which all stop working when I make customer into a @property of Room. I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent".



I did some research and I've tried implementing a custom manager and annotating the query set for Rooms:



class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(customer=F('dog__customer'))


When I do that, I can use room__customer in queries, but not room__customer__name, I suppose because F() is returning the primary key value rather than a model instance (https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations).



So my question is, is there a way to make this work that I just don't know about? A way to make it so that Room acts like it has a direct foreign key relationship with Customer, without having customer_id stored in the rooms table?







python django django-models






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 12 '18 at 17:55









J. BestosoJ. Bestoso

111




111












  • Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

    – Neil
    Nov 12 '18 at 18:18






  • 3





    Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

    – Neil
    Nov 12 '18 at 18:20











  • I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

    – J. Bestoso
    Nov 12 '18 at 18:37

















  • Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

    – Neil
    Nov 12 '18 at 18:18






  • 3





    Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

    – Neil
    Nov 12 '18 at 18:20











  • I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

    – J. Bestoso
    Nov 12 '18 at 18:37
















Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

– Neil
Nov 12 '18 at 18:18





Apologies I know this isn't an answer to the question. But I want to comment on how 'transparent' should be interpreted. when coders use shorthand like room__customer, they expect certain behavior. If you're overriding framework methods in a way that isn't predictable, well that's risky. It would be very difficult to debug any issues down the line if it's unclear how framework methods are behaving. It's also weird that storage efficiency is what's being prioritised here. They queries are going to be slower without a FK reference. And the business logic is not obvious at all after the change.

– Neil
Nov 12 '18 at 18:18




3




3





Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

– Neil
Nov 12 '18 at 18:20





Basically this: "I could change them all to room__dog__customer, which works fine, but then the change wouldn't be "totally transparent"." Is the best solve. This way the code reflects what's actually happening in the DB.

– Neil
Nov 12 '18 at 18:20













I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

– J. Bestoso
Nov 12 '18 at 18:37





I believe that "totally transparent" means "make a change in one place and have all of the rest of the code keep functioning without needing to change 400 different source files." This is my first real task at a job I started last week, so I'm a bit hesitant to go back to my boss and say that this is a bad idea and that we shouldn't be doing it.

– J. Bestoso
Nov 12 '18 at 18:37












1 Answer
1






active

oldest

votes


















1














I'm guessing your manager's line about "totally transparent" is simply a way to say "whatever you change shouldn't screw up other business logic." In other words, the application should continue to work as it does today. I doubt very much that he cares how many files you edit (though, he could be a micro-manager, in which case ... I'm sorry).



That said, I think your solution of changing room__customer to room__dog__customer is the best course of action. The change makes it obvious to other Django developers what's going on (you're walking the relationship), and it's a fairly straightforward change. Yes, you may end up having to touch a number of files, but such is life when you muck around with the backend schema.



One thing you need to consider with this change, however, is potential performance implications. You may need to introduce select_related calls to your queries, to ensure that tables are joined properly. Otherwise, doing the room -> dog -> customer lookups may become expensive (with an extra database round trip per lookup; this really adds up in a loop).



Best of luck!






share|improve this answer
























    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53267603%2fdjango-model-field-thats-actually-a-reference-to-a-field-in-a-related-model%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1














    I'm guessing your manager's line about "totally transparent" is simply a way to say "whatever you change shouldn't screw up other business logic." In other words, the application should continue to work as it does today. I doubt very much that he cares how many files you edit (though, he could be a micro-manager, in which case ... I'm sorry).



    That said, I think your solution of changing room__customer to room__dog__customer is the best course of action. The change makes it obvious to other Django developers what's going on (you're walking the relationship), and it's a fairly straightforward change. Yes, you may end up having to touch a number of files, but such is life when you muck around with the backend schema.



    One thing you need to consider with this change, however, is potential performance implications. You may need to introduce select_related calls to your queries, to ensure that tables are joined properly. Otherwise, doing the room -> dog -> customer lookups may become expensive (with an extra database round trip per lookup; this really adds up in a loop).



    Best of luck!






    share|improve this answer





























      1














      I'm guessing your manager's line about "totally transparent" is simply a way to say "whatever you change shouldn't screw up other business logic." In other words, the application should continue to work as it does today. I doubt very much that he cares how many files you edit (though, he could be a micro-manager, in which case ... I'm sorry).



      That said, I think your solution of changing room__customer to room__dog__customer is the best course of action. The change makes it obvious to other Django developers what's going on (you're walking the relationship), and it's a fairly straightforward change. Yes, you may end up having to touch a number of files, but such is life when you muck around with the backend schema.



      One thing you need to consider with this change, however, is potential performance implications. You may need to introduce select_related calls to your queries, to ensure that tables are joined properly. Otherwise, doing the room -> dog -> customer lookups may become expensive (with an extra database round trip per lookup; this really adds up in a loop).



      Best of luck!






      share|improve this answer



























        1












        1








        1







        I'm guessing your manager's line about "totally transparent" is simply a way to say "whatever you change shouldn't screw up other business logic." In other words, the application should continue to work as it does today. I doubt very much that he cares how many files you edit (though, he could be a micro-manager, in which case ... I'm sorry).



        That said, I think your solution of changing room__customer to room__dog__customer is the best course of action. The change makes it obvious to other Django developers what's going on (you're walking the relationship), and it's a fairly straightforward change. Yes, you may end up having to touch a number of files, but such is life when you muck around with the backend schema.



        One thing you need to consider with this change, however, is potential performance implications. You may need to introduce select_related calls to your queries, to ensure that tables are joined properly. Otherwise, doing the room -> dog -> customer lookups may become expensive (with an extra database round trip per lookup; this really adds up in a loop).



        Best of luck!






        share|improve this answer















        I'm guessing your manager's line about "totally transparent" is simply a way to say "whatever you change shouldn't screw up other business logic." In other words, the application should continue to work as it does today. I doubt very much that he cares how many files you edit (though, he could be a micro-manager, in which case ... I'm sorry).



        That said, I think your solution of changing room__customer to room__dog__customer is the best course of action. The change makes it obvious to other Django developers what's going on (you're walking the relationship), and it's a fairly straightforward change. Yes, you may end up having to touch a number of files, but such is life when you muck around with the backend schema.



        One thing you need to consider with this change, however, is potential performance implications. You may need to introduce select_related calls to your queries, to ensure that tables are joined properly. Otherwise, doing the room -> dog -> customer lookups may become expensive (with an extra database round trip per lookup; this really adds up in a loop).



        Best of luck!







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 12 '18 at 19:19

























        answered Nov 12 '18 at 19:06









        Jonah BishopJonah Bishop

        8,80733057




        8,80733057



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53267603%2fdjango-model-field-thats-actually-a-reference-to-a-field-in-a-related-model%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Use pre created SQLite database for Android project in kotlin

            Darth Vader #20

            Ondo