After much experimentation and skimming the source (v3.2), I believe I have figured out the important parts.
-
If no manager explicitly created, a generic (
models.Manager) manager will be autocreated and attached with attribute nameobjects. -
There exists two
Metaclass options namedbase_manager_nameanddefault_manager_namethat are not set by default. If set, they will be accessible as_metaattributes. -
There exists three
_metaattributes that aggregate managers:
a._meta.managers- all managers (list)
b._meta.managers_map- all managers indexed by manager.name (dict)
c._meta.local_managers- guessing this is managers in current class (not base classes)
NOTE: I have no idea how these get populated - something to do with a mechanism calledcontribute_to_class, but I stopped following that trail. Seems to include all explicitly created managers plus autocreated default managers. (Not autocreated base managers.) -
There exists a
_metaattribute namedbase_managerwhich is a@cached_property:
a. If_meta.base_manager_nameset, will attempt to return_meta.managers_map[base_manager_name]
b. else, creates and returns generic manager with name set to_base_manager -
There exists a
_metaattribute nameddefault_managerwhich is a@cached_property:
a. If_meta.default_manager_nameset, will attempt to return_meta.managers_map[default_manager_name]
b. else, returns first manager (_meta.managers[0]) -
There exists two Model class attributes named
_base_managerand_default_managerthat are properties (@property) that simply return_meta.base_managerand_meta.default_manager, respectively
Which leaves the question - what are the default and base managers used for? Obviously, when explicitly referencing them (Foo.objects…), you control which one you’re using. So, the question is, which one does Django use - specifically when dealing with relations.
To test that, I created the following example:
class InvestorManager1(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(deleted=True)
class InvestorManager2(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
class Investor(models.Model):
advisors = models.ManyToManyField( "Advisor", related_name="investors", through="Connection" )
name = models.CharField(max_length=30)
deleted = models.BooleanField(default=False)
m1 = InvestorManager1()
m2 = InvestorManager2()
class Meta:
base_manager_name = 'm1'
default_manager_name = 'm2'
class Advisor(models.Model):
name = models.CharField(max_length=30)
deleted = models.BooleanField(default=False)
class Connection(models.Model):
investor = models.ForeignKey( Investor, on_delete=models.CASCADE, related_name="connections" )
advisor = models.ForeignKey( Advisor, on_delete=models.CASCADE, related_name="connections" )
And, after temporarily commenting out the manager lines, created records:
a = Investor.objects.create(name="Jane Not-Deleted")
b = Investor.objects.create(name="Joe Deleted", deleted=True)
x = Advisor.objects.create(name="Frank", deleted=True)
c1 = Connection.objects.create(investor=a, advisor=x)
c2 = Connection.objects.create(investor=b, advisor=x)
Then ran the tests:
x.investors.all() - Yielded Jane only, which means Django used the default manager (m2) to resolve a relational queryset.
c1.investor - Raised Investor.DoesNotExist error, which means Django used the base manager (m1) to resolve a relational object.
c2.investor - Yielded Joe, confirming the same conclusion.