On Sat, Jun 2, 2012 at 1:54 AM, Jeremy Dunck <[email protected]> wrote:
> On Fri, Jun 1, 2012 at 2:26 AM, Russell Keith-Magee
> <[email protected]> wrote:
>> On Fri, Jun 1, 2012 at 8:53 AM, Jeremy Dunck <[email protected]> wrote:
> ...
>>> 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')
> ...
>> Is this really something that's going to be able to be managed at the
>> routing level? It seems to me that the decision about exactly which
>> set of objects you want returned needs much finer control that a
>> router method will allow.
>>
> ...
>> Here's a counter-proposal:
>>
> ...
>>  * race.candidates.all() would use _default_manager
>>
>>  * race.candidates('objects_all').all() would use the objects_all manager
>>
>>  * race.candidates('objects').all() would also use _default_manager
>> (but explicitly)
>
>
> OK, the problem I'm trying to address is "stickiness" of purpose.
> Unless we have well-known names, the admin app couldn't know to use
> Main.related_name(context_name).all on its inline lookups.

> The thought was that
>
> class EventAdmin(Admin):
>   def queryset(self):
>      return self.model.objects('admin')
>
> would result in an event instance whose person_set descriptor would
> continue returning objects as useful for admin, without needing to
> know to call event.person_set('admin') and so on.  I think well-known
> names would lead to composability problems between apps.
>
> I felt that putting it in the router nicely paralleled db_for_read in
> the sense that the instance.save method writes to the ._state.db from
> which the instance was received, or asks db_for_write if no such DB is
> known.  This continues the idea of db aliases, where the alias name is
> abstract and the behavior of that name is centralized into the router.
>
> In your objection, yes, I'm saying that a race gotten through
> Race.objects.context('admin') (or Race.object('admin') if you prefer
> to spell it that way) would have a .candidates which returns for the
> 'admin' context, whatever that means, unless told otherwise.  The
> implicit contract right now is that it returns 'default'.    If you
> wanted candidates outside of the 'admin' subset, you probably didn't
> want to get a race through that context.
>
> In any case, I understand your concern that the router approach might
> not be flexible enough for general purposes, but I have a specific
> purpose of needing "sticky" behavior in related lookups in a similar
> manner to ._state.db does currently.

True. I suppose the question here is whether the use case you're
describing -- "In site context A return subset X" -- is a general
usage pattern with a general implementation pattern, or something that
only exists in your specific use case; and if it's a general use
pattern, is it something that can/should be handled with a
routing-style behaviour, or by a slightly more abstract 'explicit'
handling.

However, here's my problem. Lets take database selection as an analogy:

The DB router doesn't *require* any manual selection of databases. The
model class and instance provides all the details required for most
routing decisions. You can manually force database selection (with
using()), and database selection will cascade down joins. However,
manual database selection isn't done by alias -- you don't say "select
the 'write' database", you specifically name the database that is used
for writes -- ok, you use an alias, but the alias is an alias for the
specific database connection, not for the broader behaviour that the
connection is performing.

In the manager selection case, the model class and instance doesn't
provide enough details for the routing decision -- the fact that
you're "in the admin" is a broader concept. So you need provide the
context in code. However, this means that one of three approaches is
needed:

Firstly, you write methods that are specifically "admin" methods,
rather than general utilities. This seems to defeat the purpose of
abstracting the manager selection process -- you might as well be
explicit about manager selection in this case.

Secondly, you write methods that take a "context" as an argument. This
might work fine under some circumstances, but for this sort of thing
to work in the admin, we'd need to ensure that every admin extension
point takes a new 'context' argument -- which seems extremely invasive
to me.

Thirdly, you write methods that take a "pre-contextualized" object as
an argument. This fits slightly better with the admin case (we can
make the origin query set set a context, and that then cascades down).

The third approach is the most appetising from an architectural point
of view, but -- and here's my problem -- I'm not convinced it's
something where there is a 'generic' behavior that warrants API-level
support.

The most common cases I've seen for custom managers are to explicitly
remove certain objects from the "default" list -- i.e., to hide
'unpublished' or 'deleted' objects that need to exist in the database,
but shouldn't be visible by default. The pain then comes because you
want the admin to show all objects, not just the default 'visible'
objects.

But then, the admin only ever really shows zeroth- and first-level
relationships -- show me all the Books, and show me all the books
related to this author. When we're only dealing with zeroth and first
level relationships, there's no need for fancy cascading behaviour,
because the effort you need to go to to select a 'context' is
essentially identical to the effort needed to specifically name a
manager.

Cascading only yields a benefit when you're traversing multiple joins.
You need to be able to say that when I've selected a list of books
related to an author, the fact that I'm in the "admin" context means
that any join I do on that book should also be restricted to the
'admin' context, and any join I do on the objects returned by that
join should be in the 'admin' context, and so on.

My concern is that my experience with custom managers -- and it's
essentially the reason that we're in this problem in the first place
-- is that while it's very easy to say "the default behaviour should
always be X", there's always a case where you don't want the default
behaviour. More importantly, this is a place where being explicit is a
virtue, because any default behaviour will inevitably result in the
"wrong" decision being made sometimes. The current default manager
selection behaviour is clearly "wrong" in a lot of cases, but it's
also very predictable how it will be wrong. My fear is that if we
introduce routing behaviour, this predictability will be lost.

Apologies for the long and rambling answer; I hope it's shed some
light on the source of my concern. To be clear -- I'm not completely
opposed to your idea of 'stickiness', and I can see how it might be
useful -- I've just got concerns about the predictability of the
resulting mechanism, and I'm not completely convinced that DB-routing
is the best analogy for comparable behavior.

Russ %-)

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

Reply via email to