Reputation: 357
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
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
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