Opened 10 years ago
Last modified 10 months ago
#23533 assigned New feature
Hook for default QuerySet filtering defined on the QuerySet itself.
Description ¶
Django 1.7 brought managers automatically created from QuerySet
which replaces defining a custom manager for the purpose of defining reusable methods. Refs #20625.
One use-case remains inelegant: using a custom QuerySet
with default QuerySet
customization/filtering:
BaseCustomManager = Manager.from_queryset(CustomQueryset) class CustomManager (BaseCustomManager ): def get_queryset(self): queryset = super(Manager, self).get_queryset() return queryset.filter(...)
This ticket proposes adding a hook on QuerySet
to enable this without requiring a custom Manager
.
According to the ticket's flags, the next step(s) to move this issue forward are:
- To provide a patch by sending a pull request. Claim the ticket when you start working so that someone else doesn't duplicate effort. Before sending a pull request, review your work against the patch review checklist. Check the "Has patch" flag on the ticket after sending a pull request and include a link to the pull request in the ticket comment when making that update. The usual format is:
[https://github.com/django/django/pull/#### PR]
.
Change History (4)
comment:1 by , 10 years ago
comment:2 by , 10 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:3 by , 10 months ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:4 by , 10 months ago
It'd be good to explore other implementations than that get_initial_queryset
hook as that bi-directionnaly couples Querysets with managers (even more than the existing as_manager
method) and prevents reuse of the same queryset class for different managers of the same model.
Approaches such as CustomQueryset.as_manager(filter=Q(is_active=True))
and CustomQueryset.filter(is_active=True).as_manager()
(this would require marking some methods class_or_instance_method
at __init_subclass__
time to capture the calls and apply them at Manager.contribute_to_class
/ app readyness time) seem more valuable as they don't require overriding any methods.
In other words, the get_initial_queryset
hook saves you from defining a manager but you still have to define a method. It also ties your queryset class to a single manager usage with seems wrong? What if you want to use your custom queryset class with two different filters sets?
Some code example might help understanding better here.
Today the problem is
class FooQueryset(models.QuerySet): def is_bar(self): return self.filter(bar=True) class FooBazManager(FooQueryset.as_manager()): def get_queryset(self): return super().get_queryset().filter(baz=True) class FooBatQueryset(FooQueryset.as_manager()): ddef get_queryset(self): return super().get_queryset().filter(baz=True) class Foo(models.Model): bar = models.BooleanField() baz = models.BooleanField() bat = models.BooleanField() objects = FooQueryset.as_manager() baz_objects = FooBazQueryset.as_manager() bat_objects = FooBatQueryset.as_manager()
What get_initial_queryset
would allow
class FooQueryset(models.QuerySet): def is_bar(self): return self.filter(bar=True) class FooBazQueryset(FooQueryset): def get_initial_queryset(self): return self.filter(baz=True) class FooBatQueryset(FooQueryset): def get_initial_queryset(self): return self.filter(bat=True) class Foo(models.Model): bar = models.BooleanField() baz = models.BooleanField() bat = models.BooleanField() objects = FooQueryset.as_manager() baz_objects = FooBazQueryset.as_manager() bat_objects = FooBatQueryset.as_manager()
And what a more thorough solution would provide
class FooQueryset(models.QuerySet): def is_bar(self): return self.filter(bar=True) class Foo(models.Model): bar = models.BooleanField() baz = models.BooleanField() bat = models.BooleanField() objects = FooQueryset.as_manager() baz_objects = FooQueryset.filter(baz=True).as_manager() bat_objects = FooQueryset.filter(bat=True).as_manager()
I don't believe that get_initial_queryset
brings much to the table TBH.
It's worth noting that
QuerySet.__init__()
can't be used for providing such initialization/customization:__init__
can't return a different instance.So far the best option I can think of is a hook called externally by the manager.
POC with a
QuerySet.get_initial_queryset()
method https://github.com/loic/django/compare/ticket23533