Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django model relationship to multiple models

Suppose there is an abstract model CarOwner: whereas a Person or a Business can be a CarOwner. In addition a Car with a certain VIN can belong (relate) to either a Person or a Business, but not both (mutually exclusive case). At the very end of the following code I presented two possibilities (see comments in the code "# 1. SHOULD I HAVE THIS???" and "# 2. ...OR SHOULD I HAVE THIS???"). In the first possibility a Many-to-One relationship is established to an abstract model and I am not sure if this is the right way. In the second case two relationships are established and I am not sure if that is correct either, especially it is not clear how to make them mutually exclusive. So which one is right and if neither, please, provide the right answer if you could. Thanks.

class CarOwner(models.Model):
    location = models.CharField(max_length=50, blank=True)

    class Meta:
        abstract = True

class Person(CarOwner):
    name = models.CharField(max_length=50, blank=True)

class Business(CarOwner):
    business_id = models.CharField(max_length=50, blank=True)

class Car(models.Model):
    vin = models.CharField(max_length=50, blank=True)

    # 1. SHOULD I HAVE THIS??? (CarOwner is abstract)
    carowner = models.ForeignKey(CarOwner, blank=True, null=True)

    # 2. ...OR SHOULD I HAVE THIS???
    person = models.ForeignKey(Person, blank=True, null=True)
    business = models.ForeignKey(Business, blank=True, null=True)
like image 348
jazzblue Avatar asked Jan 04 '14 20:01

jazzblue


2 Answers

Like jproffitt mentioned, generic relations might be a good solution for you. Alternatively you can use #2 and make it a bit more convenient by creating property and adding some simple logic to it:

class Car(models.Model):
    vin = models.CharField(max_length=50, blank=True)
    person = models.ForeignKey(Person, blank=True, null=True)
    business = models.ForeignKey(Business, blank=True, null=True)

    @property
    def carowner(self):
        return self.person or self.business

    @carowner.setter
    def carowner(self, obj):
        if type(obj) == Person:
            self.person = obj
            self.business = None
        elif type(obj) == Business:
            self.business = obj
            self.person = None
        else:
            raise ValueError("obj parameter must be an object of Business or Person class")

However for queries you'll have to use person or business.

like image 192
zaan Avatar answered Nov 15 '22 10:11

zaan


Since CarOwner is abstract, you cannot do #1. You could make CarOwner concrete (db table inheritance), and then that would work, but table inheritance brings its own set of complications. You could either do #2 or use a generic foreign key:

carowner_content_type = models.ForeignKey(ContentType)
carowner_object_id = models.PositiveIntegerField()
carowner = generic.GenericForeignKey('carowner_content_type', 'carowner_object_id')
like image 33
jproffitt Avatar answered Nov 15 '22 10:11

jproffitt