From e22399d1bf31bcd908a0bbb07dc1384c22a172c1 Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Thu, 27 Nov 2025 10:59:39 +0100
Subject: [PATCH] Fixed #23533 -- Allow defining initial filters on QuerySet
classes.
Thanks Simon Charette for the implementation idea.
---
django/db/models/manager.py | 6 ++++--
django/db/models/query.py | 24 ++++++++++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
index 467e79f9b9..b8648d76cd 100644
|
a
|
b
|
|
| 1 | 1 | import copy |
| 2 | 2 | import inspect |
| 3 | | from functools import wraps |
| | 3 | import types |
| | 4 | from functools import partial, wraps |
| 4 | 5 | from importlib import import_module |
| 5 | 6 | |
| 6 | 7 | from django.db import router |
| … |
… |
class BaseManager:
|
| 90 | 91 | |
| 91 | 92 | new_methods = {} |
| 92 | 93 | for name, method in inspect.getmembers( |
| 93 | | queryset_class, predicate=inspect.isfunction |
| | 94 | queryset_class, |
| | 95 | predicate=lambda member: isinstance(member, (types.FunctionType, partial)), |
| 94 | 96 | ): |
| 95 | 97 | # Only copy missing methods. |
| 96 | 98 | if hasattr(cls, name): |
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 73d6717bce..089aa5d914 100644
|
a
|
b
|
from django.db.models.deletion import Collector
|
| 29 | 29 | from django.db.models.expressions import Case, DatabaseDefault, F, Value, When |
| 30 | 30 | from django.db.models.fetch_modes import FETCH_ONE |
| 31 | 31 | from django.db.models.functions import Cast, Trunc |
| 32 | | from django.db.models.query_utils import FilteredRelation, Q |
| | 32 | from django.db.models.query_utils import FilteredRelation, Q, class_or_instance_method |
| 33 | 33 | from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, ROW_COUNT |
| 34 | 34 | from django.db.models.utils import ( |
| 35 | 35 | AltersData, |
| … |
… |
class FlatValuesListIterable(BaseIterable):
|
| 300 | 300 | class QuerySet(AltersData): |
| 301 | 301 | """Represent a lazy database lookup for a set of objects.""" |
| 302 | 302 | |
| | 303 | _initial_filter = None |
| | 304 | |
| 303 | 305 | def __init__(self, model=None, query=None, using=None, hints=None): |
| 304 | 306 | self.model = model |
| 305 | 307 | self._db = using |
| … |
… |
class QuerySet(AltersData):
|
| 323 | 325 | negate, args, kwargs = self._deferred_filter |
| 324 | 326 | self._filter_or_exclude_inplace(negate, args, kwargs) |
| 325 | 327 | self._deferred_filter = None |
| | 328 | if self._initial_filter is not None: |
| | 329 | self._query.add_q(self._initial_filter) |
| 326 | 330 | return self._query |
| 327 | 331 | |
| 328 | 332 | @query.setter |
| … |
… |
class QuerySet(AltersData):
|
| 1618 | 1622 | """ |
| 1619 | 1623 | return self._chain() |
| 1620 | 1624 | |
| 1621 | | def filter(self, *args, **kwargs): |
| | 1625 | def _class_filter(cls, *args, **kwargs): |
| | 1626 | if invalid_kwargs := PROHIBITED_FILTER_KWARGS.intersection(kwargs): |
| | 1627 | invalid_kwargs_str = ", ".join(f"'{k}'" for k in sorted(invalid_kwargs)) |
| | 1628 | raise TypeError(f"The following kwargs are invalid: {invalid_kwargs_str}") |
| | 1629 | initial_filter = Q(*args, **kwargs) |
| | 1630 | initial_filter_id = id(initial_filter) |
| | 1631 | class_name = f"{cls.__name__}WithFilter{initial_filter_id}" |
| | 1632 | return type( |
| | 1633 | class_name, |
| | 1634 | (cls,), |
| | 1635 | {"_initial_filter": initial_filter}, |
| | 1636 | ) |
| | 1637 | |
| | 1638 | def _instance_filter(self, *args, **kwargs): |
| 1622 | 1639 | """ |
| 1623 | 1640 | Return a new QuerySet instance with the args ANDed to the existing |
| 1624 | 1641 | set. |
| … |
… |
class QuerySet(AltersData):
|
| 1626 | 1643 | self._not_support_combined_queries("filter") |
| 1627 | 1644 | return self._filter_or_exclude(False, args, kwargs) |
| 1628 | 1645 | |
| | 1646 | filter = class_or_instance_method(_class_filter, _instance_filter) |
| | 1647 | _class_filter = classmethod(_class_filter) |
| | 1648 | |
| 1629 | 1649 | def exclude(self, *args, **kwargs): |
| 1630 | 1650 | """ |
| 1631 | 1651 | Return a new QuerySet instance with NOT (args) ANDed to the existing |