Search code examples
djangoinheritancedjango-modelssubclassmultiple-choice

Django: Access certain attribute of multiple subclasses with inheritance from one superclass


My aim is to access an attribute of a subclass without knowing beforehand which of the two subclasses was choosen (multiple choice classes)

Ideally there is an attribute in the SuperClass that changes depending upon which SubClass was choosen.

The reason is that I have created Forms directly from the SubClasses and use the SuperClass as the entry point for accessing values.

I am aware I can use true or false with hasattr(horse), but ideally I am asking if there is a bit neater solution, such as the SubClass can signal to SuperClass which SubClass was used.

e.g. for product 8 on my list

subclass = getattr(Product(8), 'subclass', 0)
print(subclass)
>> Horse

or

place = Product.location
Print(place)
>> Stable

The whole "problem" stem from the fact that I create Products via SubClass Forms, meanwhile much of the later logic goes top-down, starting with Product

class Product(models.Model):
  product_name = models.Charfield(max_length=20)

class Car(Product):
  engine = models.Charfield(max_length=20)
  location = models.Charfield(default="Garage", max_length=20, editable=False)
  product = models.OneToOneField(Product, parent_link=True, on_delete=models.CASCADE)

class Horse(Product):
  saddle_model = models.Charfield(max_length=20)
  location = models.Charfield(default="Stable", max_length=20, editable=False)
  product = models.OneToOneField(Product, parent_link=True, on_delete=models.CASCADE)

Solution

  • If you want to access the other models properties from the Product model you could implement a property method on Product that inspects the reverse relation between it and its related models and then returns the appropriate location (https://docs.djangoproject.com/en/2.1/topics/db/examples/one_to_one/).

    class Product(models.Model):
        product_name = models.CharField(max_length=20)
    
        @property
        def location(self):
            """Return the location of the related subclass"""
            if self.car:
                return self.car.location
            elif self.horse:
                return self.horse.location
            else:
                return None
    
        @property
        def product_subclass(self):
            """Return the location of the related subclass"""
            if self.car:
                return self.car
            elif self.horse:
                return self.horse
            else:
                return None
    

    This should allow you to use it like so:

    car_product = Product.objects.create(product_name="Car Product")
    car = Car.objects.create(engine="engine", location="123 Fake Street", product=car_product)
    print(car_product.location)  # Prints "123 Fake Street"
    
    horse_product = Product.objects.create(product_name="Horse Product")
    horse = Horse.objects.create(saddle_model="Buckingham", location="1982 Old Street", product=horse_product)
    print(horse_product.location)  # Prints "1982 Old Street"
    

    If you'd like to do something similar to return the subclass:

    print(car_product.product_subclass)  # Prints <Car object>
    print(horse_product.product_subclass)  # Prints <Horse object>
    

    These property methods require a database query to check with the Car and Horse table's, since the relation is stored on those tables as the product_id column. So to figure out if product.car is valid, the ORM does a query similar to Car.objects.get(product_id=product.pk)