FlexQuery¶
The FlexQuery class provides a decorator for functions declared on a custom
Manager or QuerySet:
from django_flexquery import FlexQuery, Manager, Q, QuerySet
# It's crucial to inherit from the QuerySet class of django_flexquery, because
# the FlexQuery's wouldn't make it over to a Manager derived using as_manager()
# with the stock Django implementation. That's the only difference however.
class FruitQuerySet(QuerySet):
# Declare a function that generates a Q object.
# base is a copy of the base QuerySet instance. It's not needed most of
# the time unless you want to embed a sub-query based on the current QuerySet
# into the Q object.
@FlexQuery.from_func
def large(base):
return Q(size__gte=10)
FruitQuerySet.large now is a sub-type of FlexQuery encapsulating the custom
function.
You can then derive a Manager from FruitQuerySet in two ways, using the
known Django API:
# Either use from_queryset() of the Manager class provided with this module.
class FruitManager(Manager.from_queryset(FruitQuerySet)):
...
# Or, if you don't want to add additional manager-only methods, create a Manager
# instance inside your model definition straight away.
class Fruit(Model):
objects = FruitQuerySet.as_manager()
...
When we assume such a Manager being the default manager of a Fruit model
with a size field, we can now perform the following queries:
Fruit.objects.large()
Fruit.objects.filter(Fruit.objects.large.as_q())
Internally, this is made possible by some metaclass and descriptor magic instantiating
the FlexQuery type whenever it is accessed as class attribute of a Manager
or QuerySet object. The resulting FlexQuery instance will be tied to its
owner and use that for all its filtering.
A FlexQuery instance is directly callable (Fruit.objects.large()), which just
applies the filters returned by our custom Q function to the base QuerySet. This
is a well-known usage pattern you might have come across often when writing custom
Django model managers or querysets.
However, FlexQuery also comes with an as_q() method, which lets you access the
Q object directly (Fruit.objects.filter(Fruit.objects.large.as_q())). The
FlexQuery can mediate between these two and deliver what you need in your
particular situation.
Conversion Costs¶
Providing a standalone QuerySet filtered by the Q from a supplied Q
function is a cheap operation. The Q object generated by your custom function is
simply applied to the base using QuerySet.filter(), resulting in a new QuerySet
you may either evaluate straight away or use to create a sub-query.
Why do I Need This?¶
This approach enables you to declare logic for filtering once with the Manager
or QuerySet of the model it belongs to. When combined with the prefix() method
of the extended Q object implementation, related models can then simply fetch the
generated Q object and prefix it with the related field’s name in order to reuse it
in their own filtering code, without needing sub-queries. Think of something like:
class TreeQuerySet(QuerySet):
@FlexQuery.from_func
def having_ripe_apples(base):
return Q(kind="apple") & Fruid.objects.large.as_q().prefix("fruits")
API¶
This module provides a convenient way to declare custom filtering logic with Django’s model managers in a reusable fashion using Q objects.
-
class
django_flexquery.flexquery.FlexQuery(base)¶ Flexibly provides model-specific query constraints as an attribute of Manager or QuerySet objects.
When a sub-type of FlexQuery is accessed as class attribute of a Manager or QuerySet object, its metaclass, which is implemented as a descriptor, will automatically initialize and return an instance of the FlexQuery type bound to the holding Manager or QuerySet.
-
__call__(*args, **kwargs)¶ Filters the base queryset using the provided function, relaying arguments.
- Returns QuerySet
-
as_q(*args, **kwargs)¶ Returns the result of the configured function, relaying arguments.
- Returns Q
-
call_bound(*args, **kwargs)¶ Calls the provided function with
self.base.all()as first argument.This may be overwritten if arguments need to be preprocessed in some way before being passed to the custom function.
- Returns Q
-
classmethod
from_func(func=None, **attrs)¶ Creates a
FlexQuerysub-type from a function.This classmethod can be used as decorator. As long as
funcisNone, afunctools.partialwith the given keyword arguments is returned.- Parameters
func (function | None) – function taking a base
QuerySetand returning aQobjectattrs – additional attributes to set on the newly created type
- Returns InitializedFlexQueryType | functools.partial
- Raises
TypeError – if
funcis no function
-
-
class
django_flexquery.flexquery.Manager¶ Use this class’
from_querysetmethod if you want to derive aManagerfrom aQuerySetwithFlexQuerymembers. If Django’s nativeManager.from_querysetwas used instead, those members would be lost.
-
class
django_flexquery.flexquery.QuerySet(model=None, query=None, using=None, hints=None)¶ Adds support for deriving a
Managerfrom aQuerySetclass viaas_manager, preservingFlexQuerymembers.