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 FlexQuery sub-type from a function.

This classmethod can be used as decorator. As long as func is None, a functools.partial with the given keyword arguments is returned.

Parameters
  • func (function | None) – function taking a base QuerySet and returning a Q object

  • attrs – additional attributes to set on the newly created type

Returns InitializedFlexQueryType | functools.partial

Raises

TypeError – if func is no function

class django_flexquery.flexquery.Manager

Use this class’ from_queryset method if you want to derive a Manager from a QuerySet with FlexQuery members. If Django’s native Manager.from_queryset was 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 Manager from a QuerySet class via as_manager, preserving FlexQuery members.