user3628119
user3628119

Reputation: 357

Django proxy model: parent class accessing sub-class methods

I am trying to wrap my head around how proxy model works. Supposed I have a base class called Animal, and I would like to implement two sub-classes: Dog and Cow. They have the same data requirements, so I don't really want to create two separate tables. So I am trying using proxy models:

class Animal(models.Model):
    name = models.CharField()
    animal_type = models.CharField(choices=(('Dog','Dog'),('Cow','Cow')))

    def get_sound(self):
        if animal_type == 'Dog':
            return self.dog.get_sound()    #doesn't work
        elif self.animal_type == 'Cow':
            return self.cow.get_sound()    #doesn't work

class Dog(Animal):
    class Meta:
        proxy=True

    def get_sound(self):
        return 'Woof'

class Cow(Animal):
    class Meta:
        proxy=True

    def get_sound(self):
        return 'Moo'

The problem I have is, how can I access the sub-class methods from the parent class? I have it as self.dog.get_sound(). This is possible in multi-table inheritance, but does not work in a proxy model.

>>obj = Animal.objects.create(name='Max', animal_type='Dog')
>>obj.get_sound()
'Woof'    <-- what I'd like it to return

Is proxy model the wrong way to do this? Would prefer to keep one table.

Upvotes: 1

Views: 1580

Answers (2)

swinters
swinters

Reputation: 153

Here you go:

class Animal(models.Model):
    class Type(models.TextChoices):
        Dog = ('dog', 'Dog')
        Cow = ('cow', 'Cow')

    name = models.CharField()
    animal_type = models.CharField(choices=Type.choices)

    def get_subclass_instance(self):
        subclasses = {
            self.Type.choices.Dog: Dog,
            self.Type.choices.Cow: Cow,
        }
        instance = subclasses[self.type].objects.get(id=self.id)
        return instance

    def get_sound(self):
        self.get_subclass_instance().get_sound()


class Dog(Animal):
    class Meta:
        proxy=True

    def get_sound(self):
        return 'Woof'

class Cow(Animal):
    class Meta:
        proxy=True

    def get_sound(self):
        return 'Moo'

Upvotes: 1

gmacro
gmacro

Reputation: 427

Yeah, that's not going to work. Behind the scenes Django creates a OneToOne foreign key for each inherited model, you're right about it, except in the case of proxy models (docs). Proxy models are not meant to be used this way, they work exactly like the original model, sometimes in a subset of the original table (like your example), extra methods, etc. If you use a proxy model you shouldn't need to use the original table, as you want.

I have a few suggestions for you:

class DogManager(Manager):

    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.filter(kind='Dog')  # Literal is not good

    def create(self, **kwargs):
        kwargs.update({'kind': 'Dog'})
        return super().create(**kwargs)


class CowManager(Manager):

    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.filter(kind='Cow')  # Literal is not good

    def create(self, **kwargs):
        kwargs.update({'kind': 'Cow'})
        return super().create(**kwargs)


class Animal(models.Model):
    name = models.CharField()
    # I've changed the name because I don't like variables with 'type' in the name
    kind = models.CharField(choices=(('Dog','Dog'),('Cow','Cow')))


class Dog(Animal):

    objects = DogManager()

    class Meta:
        proxy=True

    def get_sound(self):
        return 'Woof'


class Cow(Animal):

    objects = CowManager()

    class Meta:
        proxy=True

    def get_sound(self):
        return 'Moo'

And now you don't need to call Animal class, as you have a subset of your original data in those two proxy models proxy managers.

instance = Dog.objects.create(name='Max')
instance.get_sound()  # Woof

Dog.objects.all() # all animals with kind 'Dog'
Cow.objects.all() # all animals with kind 'Cow'

Upvotes: 1

Related Questions