On Fri, Jun 1, 2012 at 5:54 PM, Anssi Kääriäinen
<[email protected]> wrote:
> On Jun 1, 12:26 pm, Russell Keith-Magee <[email protected]>
> wrote:
>> 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.
>>
>> It might not happen in your case specifically, but I wouldn't think it
>> would be unusual to have a single view in which both "full" list and
>> the "filtered" list are retrieved. If you're working with the managers
>> directly, that's easy:
>>
>> selected_canddiates = Candidate.objects.all()
>> all_candidates = Candidate.objects_all.all()
>>
>> but if you want to go through the relation for objects related to a
>> specific race, it's not:
>>
>> selected_canddiates = race.candidates.all()
>> all_candidates = race.candidates.all() # this won't give the right
>> results...
>>
>> If I'm understanding your proposal correctly, I'd need to either tweak
>> the _state of race between the two calls to switch the context, or
>> write a router that was able to differentiate between the two uses
>> based on some other contextual cue (I'm not sure what could be
>> provided to give that differentiation, though).
>>
>> The context switching might be possible with some utility methods or a
>> Python context managers, but it still seems like the wrong way to
>> affect control -- modifying an object's state so that it's default
>> behaviour changes, rather than explicitly requesting the behaviour
>> your want.
>>
>> Here's a counter-proposal:
>>
>> mycandidate.races is a descriptor. That descriptor implements
>> __get__() to return a RelatedObjectsManager that does the appropriate
>> filtering. The related objects manager is a standard manager that
>> allows calls to all(), filter() etc. Why not also implement __call__,
>> and have __call__() return a new RelatedObjectsManager, bound to a
>> different manager (as named in the arguments to call)?
>>
>> So, using your example:
>>
>> * 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)
>>
>> With some slightly neater manager naming, it becomes even easier to
>> read. Consider the following example:
>>
>> class Person(…):
>> people = Manager()
>> men = MaleManager()
>> women = FemaleManager()
>>
>> class Event(…):
>> attendees = ManyToMany(Person, related_name='events')
>> objects = Manager()
>> confirmed = ConfirmedManager()
>>
>> then:
>>
>> all people: Person.people.all()
>> all women: Person.women.all()
>>
>> all events: Event.objects.all()
>> all confirmed events: Event.confirmed.all()
>>
>> all events being attended by the Person frank: frank.events.all()
>> all confirmed events being attended by the Person frank:
>> frank.events('confirmed').all()
>>
>> all people attending the brogrammer event: brogrammer.attendees.all()
>> all men attending the brogrammer event: brogrammer.attendees('men').all()
>>
>> For my money, that's an explicit API that makes sense when reading the
>> single query line, and doesn't require the interpolation of extra
>> state/context from a router or some other context manager. It's also
>> backwards compatible, because there won't be any existing code
>> invoking __call__() on a manager.
>>
>> Russ %-)
>
> +1.
>
> Just as a bike-shedding thought: Would it be possible to have
> frank.events.confirmed.all() as the syntax? I see this a tiny bit
> cleaner. On the other hand it isn't explicit you can only use
> the .confirmed only directly after the events, and there is the
> possibility of clashing with queryset methods. The clash issue can be
> solved by just using frank.events('clashing_name').all() instead of
> frank.events.clashing_name.all() in the rare cases this is needed.
True - but providing an API that avoids the clash in the first place
is preferable, IMHO.
That said, I'm not *strongly* opposed to .events.confirmed -- it just
tastes a bit funny. I'd rather not eat it, but if if everyone else
says I should…. :-)
> How would this work with prefetch_related? Maybe
> prefetch_related('events__confirmed')? IMHO this is an important use
> case, as there isn't currently any possibility to use different
> filtering, ordering etc than the one given by the default related
> manager.
That's a good point -- I hadn't thought of the consequences on
prefetch_related. I'm not wild about overloading the __ notation to
add the 'manager selector" interpretation. There are already
complications differentiating between aggregates, filter clauses,
related fields… I don't think we need to add another to this mix.
How about a kwarg to prefetch_related? For example:
person.prefetch_related('events', managers={'events': 'confirmed'})
or maybe just use kwargs directly:
person.prefetch_related(events='confirmed')
If you start getting into multiple joins, it's going to get more
complex; we might have to require a list of managers:
person.prefetch_related(events__things=['confirmed', 'objects'])
??
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.