Opened 10 years ago
Last modified 7 months ago
#23533 assigned New feature
Hook for default QuerySet filtering defined on the QuerySet itself.
Reported by: | Loic Bistuer | Owned by: | Mariusz Felisiak |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
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
.
Change History (4)
comment:1 by , 10 years ago
comment:2 by , 10 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:3 by , 7 months ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:4 by , 7 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