Search code examples
djangodjango-managers

Django: How to cascade custom managers defined in different abstract classes?


I want to be able to make complex queries combining different custom manager functions from different Abstract classes.

My models are like these:

class GenderManager(models.Manager):
    def male(self):
        return self.filter(gender="M")

    def female(self):
        return self.filter(gender="F")


 class SpeciesManager(models.Manager):
    def lion(self):
        return self.filter(species="L")

    def tiger(self):
        return self.filter(species="T")


class GenderModel(models.Model):
    gender = models.CharField(max_length=1)
    objects = GenderManager()

    class Meta:
        abstract = True


class SpeciesModel(models.Model):
    species = models.CharField(max_length=1)
    objects = SpeciesManager()

    class Meta:
        abstract = True


class Animal(GenderModel,SpeciesModel):
    name = models.CharField(max_length=30)
    age = models.DecimalField(max_digits=4, decimal_places=2)

The reason I want to split Gender and Species is that in my models sometimes I will need to inherit only from GenderModel and sometimes only from SpeciesModel.

In the cases where I want to inherit form both (like in Animal class), I would like to be able to make queries like this:

Animal.objects.male().tiger().filter(age__gte = 10.00)

But it doesn't work.

However, if I don't use custom manager functions it works:

Animal.objects.filter(gender="M").filter(species="T").filter(age__gte = 10.00)

How can I make it work with custom manager functions do make it DRY?

Thank you!


Solution

  • This can't be done due to how Managers actually work but there can be different ways to refactor your model and the logic, depending on the complexity and how independent the models actually need to be.

    Since your manager is actually needed for the concrete class and not the abstract class, concanate both managers into a single manager, afterall you are filtering on the concrete class and not the Abstract.

    class GenderModel(models.Model):
        gender = models.CharField(max_length=1)
    
        class Meta:
            abstract = True
    
    
    class SpeciesModel(models.Model):
        species = models.CharField(max_length=1)
    
        class Meta:
            abstract = True
    
    
    class AnimalManager(models.Manager):
        def gender(self):
            return self.filter(gender="M")
    
        def female(self):
            return self.filter(gender="F")
    
        def lion(self):
            return self.filter(species="L")
    
        def tiger(self):
            return self.filter(species="T")
    
        def get_male_tigers(self):
            return self.filter(species="T").filter(gender="M").all()
    
    class Animal(GenderModel,SpeciesModel):
        name = models.CharField(max_length=30)
        age = models.DecimalField(max_digits=4, decimal_places=2)
        objects = AnimalManager()
    

    Then:

     animals = Animal.objects.get_male_tigers()
    

    Of course you can further refactor to your needs