It should be relatively simple to allow QuerySets to know which manager they came from, and then override __getattr__ to use the declared @querymethod from that manager (instead of the other way around). This avoids the unpickleable QuerySet subclass problem.
On Jan 10, 8:54 am, Anssi Kääriäinen <[email protected]> wrote: > On Jan 9, 10:51 pm, Anssi Kääriäinen <[email protected]> wrote: > > > > > > > > > > > I have a very quickly crafted draft of the main idea > > athttps://github.com/akaariai/django/compare/dynamic_man > > > As you can see, the implementation is very simple. It is missing > > manager autodiscovery, and the @querymethod decorator. > > > I have tested it with: > > > class PublishedManager(Manager): > > def published(self): > > return > > self.get_query_set().filter(published_date__lte=datetime.now()) > > > class SomeModel(models.Model): > > published_date = models.DateTimeField() > > objects = PublishedManager() > > > print SomeModel.objects.published().query > > print > > SomeModel.objects.order_by('id').add_managers([SomeModel.objects]).publishe > > d().query > > > These simple test-cases seem to work. Note that @querymethod is not > > implemented. Also, add_managers is not part of the proposed API. The > > set of managers to look for chainable methods should somehow be > > autodiscovered, but I have no good ideas about how to do that. > > I did some more testing, and I think this will work. Now, I also have > a suggestion about the manager autodiscovery API: > - All managers defined for the model are added into the > queryset.managers list. That is, the method resolution order is the > same order in which the managers are defined. Current model's managers > first, then parent model managers. > - The managers list should be _meta option. Currently this is not > available in the above order, but it should be possible to > have ._meta.ordered_managers attribute. This could then be used in > __getattr__. Instead of doing for manager in self.managers do: for > manager in self.model._meta.ordered_managers. > - You can also access managers directly, if you had: > > class M1(Manager): > some_constant = 'bar' > def get_foo_objs(self): > return self.get_query_set().filter(foo=self.some_constant) > > class M2(Manager): > def get_foo_objs(self): > return self.get_query_set().filter(foo='baz') > > class SomeModel(Model): > ... > objects = M1() > other_objects = M2() > > and you do SomeModel.objects.order_by('id').get_foo_objs(), you will > get M1 get_foo_objs(). However, you can also do > SomeModel.objects.order_by('id').other_objects.get_foo_objs(), that > is, you can get the dynamically created manager by using other_objects > on the queryset. The only nasty thing about this is that now _all_ > methods of other_objects manager are available to you, not only those > which are actually chainable. > > I think the base idea (dynamic manager) has some merit which can not > be easily achieved by adding the chainable methods to QuerySet > (dynamic) subclasses: > - You can easily upgrade your current managers. Verify that your > manager method is chainable (it is based on .get_query_set() > and .get_query_set() is not overridden). If so, add @chainable and > things should just work. > - You can call other methods of the manager, or access class > variables. This is not achievable in the QuerySet subclassing idea, > unless you transfer all the methods, and all the variables to the > QuerySet subclass. And in this case, @chainable decorator is > pointless. For example M1.get_foo_objs() would not work, because it > accesses self.some_constant, and it would be hard to make that > available in the queryset subclass. > - You can't access duplicate manager methods. In the above example > M2.get_foo_objs() would not be available through the queryset. > > It could be possible to make the "dynamic manager" idea work even > without creating dynamic manager subclasses. Just define > Manager.get_query_set() as: > if self._chain_to_qs is not None: > return self._chain_to_qs > else: > # return new queryset as normally. > > And the only thing you need to do in __getattr__ is instantiating the > manager, set its _chain_to_qs to self. And return the asked method by > getattr. > > This would make even overridden get_query_set() based managers work, > as long as your overriding implementation calls > super().get_query_set(). > > I have updated the draft patch according to the above idea of > modifying manager.get_query_set(). The patch is even more simple than > before. The manager resolution order is not implemented (I use > chain(_meta.abstract_managers, _meta.concrete_managers). @chainable > is still not implemented. The patch is available > fromhttps://github.com/akaariai/django/compare/dynamic_man > > It would be pretty easy to return the "dynamic managers" from > __getattr__, but I am not sure that is a good idea, so I have left > that out. > > - Anssi -- 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.
