It feels to me that each place that ._default_manager is mentioned
here is a misfeature:
https://github.com/django/django/blob/2cd516002d43cdc09741618f0a0db047ee6d78fd/django/db/models/fields/related.py

As an example, we (Votizen) currently have some default managers which
show subsets of all objects (published, for example).

class Candidate(...):
   races = ManyToMany(Race, related_name='candidates')

   objects = CandidateWithPublishedRacesManager()
   objects_all = Manager()

class Race(...):
   objects = PublishedRaceManager()
   objects_all = Manager()

The intention here is that we show the blessed subset by default
(mostly for the site), show more for admin, shell, scripts, etc.

The trouble is this asymmetry:

candidate_with_unpublished_races = Candidate.objects_all.all()[0]

candidate_with_unpublished_races.races.all() !=
Race.objects.filter(candidates=candidate_with_unpublished_races)

This affects admin (because while we can control
CandidateAdmin.queryset, we can't control Candidate.races).  But it
also comes as a surprise fairly often in general.  Note that no error
is raised, you just process a subset of the data you meant to,
generally.

(We have considered instead adding manager methods for the site:
Candidate.objects.for_site()..., but you see this has the same problem
- assuming the default manager doesn't call that chained query
method.)


OK, for a solution, here's an API I think would remove the asymmetry:


Candidate.context('site') would return a manager depending on the
requested context.
(or perhaps Candidate.objects.using(context='site')... or
Candidate.objects.context('site'))

# get an instance while assigning ._state.context == 'site', similar
to ._state.db
candidate_with_unpublished_races = Candidate.context('site')

# the router grows a method to help decide what to do on a per-model basis.
class DatabaseRouter(object):
   ...# as now except, for example:
   def manager_for_context(self, context, model_class):
         # returns a manager
         # e.g.
         return getattr(model_class, "objects_%s" % context)
         # could also by dynamically constructed.

(It seems to me the router is the natural place to have
project-specific customization of what happens with managers, though
perhaps some community norms would be needed

And then perhaps models can have defaults, as they do DBs:

class FooModel(Model):
  ...
  class Meta:
      context = "site"

class FooAdmin(Admin):
  model = FooModel

  class Meta:
     context = "admin"

For backwards-compatibility, any model without a context specified in
meta gets "default", and the default database manager always resolves
to model_class._default_manager.

For app composition, there is still value in the concept of a default
manager, though I'm not entirely clear how this fits together.

Perhaps Frob.objects would resolve to some manager based on the default context,
unless .objects had been explicitly assigned to be a vanilla manager
(much as a queryset resolves the db via the router unless .using(db=)
has been used).

Note, I'm not entirely happy with this, but it gives something to start from.

Thoughts?

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to