1010# XOR is available only in Django 4.1+: https://docs.djangoproject.com/en/4.1/ref/models/querysets/#xor
1111ExpressionConnector = Literal ["AND" , "OR" , "XOR" ]
1212
13- DEFAULT_IGNORE_NONE = True
13+ DEFAULT_IGNORE_NONE : bool = True
1414DEFAULT_CLASS_LEVEL_EXPRESSION_CONNECTOR : ExpressionConnector = "AND"
1515DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR : ExpressionConnector = "OR"
1616
@@ -29,8 +29,8 @@ def __init__(
2929 self ,
3030 q : str | List [str ],
3131 * ,
32- expression_connector : str = DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR ,
33- ignore_none : Optional [ bool ] = DEFAULT_IGNORE_NONE ,
32+ expression_connector : ExpressionConnector = DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR ,
33+ ignore_none : bool = DEFAULT_IGNORE_NONE ,
3434 ):
3535 """
3636 Args:
@@ -42,19 +42,14 @@ def __init__(
4242 ignore_none: Whether to ignore None values for this field specifically. Default is True.
4343 """
4444 self .q = q
45- self .expression_connector = cast ( ExpressionConnector , expression_connector )
45+ self .expression_connector = expression_connector
4646 self .ignore_none = ignore_none
4747
4848
4949T = TypeVar ("T" , bound = QuerySet )
5050
5151
5252class FilterSchema (Schema ):
53- # if TYPE_CHECKING:
54- # __config__: ClassVar[Type[FilterConfig]] = FilterConfig # pragma: no cover
55-
56- # Config = FilterConfig
57-
5853 class Config (Schema .Config ):
5954 ignore_none : bool = DEFAULT_IGNORE_NONE
6055 expression_connector : ExpressionConnector = (
@@ -97,9 +92,9 @@ def _get_filter_lookup(
9792 return filter_lookups [0 ]
9893 else :
9994 raise ImproperlyConfigured (
100- f"Multiple FilterLookup instances found in metadata of { self .__class__ .__name__ } .{ field_name } . "
101- f"Use at most one FilterLookup instance per field. "
102- f"If you need multiple lookups, specify them as a list in a single FilterLookup: "
95+ f"Multiple FilterLookup instances found in metadata of { self .__class__ .__name__ } .{ field_name } .\n "
96+ f"Use at most one FilterLookup instance per field.\n "
97+ f"If you need multiple lookups, specify them as a list in a single FilterLookup:\n "
10398 f"{ field_name } : Annotated[{ field_info .annotation } , FilterLookup(['lookup1', 'lookup2', ...])]"
10499 )
105100
@@ -138,11 +133,7 @@ def _get_field_ignore_none(
138133 ) -> bool | None :
139134 filter_lookup = self ._get_filter_lookup (field_name , field_info )
140135 if filter_lookup :
141- return (
142- filter_lookup .ignore_none
143- if filter_lookup .ignore_none is not None
144- else default
145- )
136+ return filter_lookup .ignore_none
146137
147138 # Legacy approach, consider removing in future versions
148139 field_extra = cast (dict , field_info .json_schema_extra ) or {}
@@ -187,15 +178,18 @@ def _resolve_field_expression(
187178
188179 def _connect_fields (self ) -> Q :
189180 q = Q ()
181+ class_ignore_none = self .model_config .get ("ignore_none" , DEFAULT_IGNORE_NONE )
190182 for field_name , field_info in self .__class__ .model_fields .items ():
191183 filter_value = getattr (self , field_name )
192- ignore_none = self ._get_field_ignore_none (
193- field_name ,
194- field_info ,
195- cast (
196- bool | None ,
197- self .model_config .get ("ignore_none" , DEFAULT_IGNORE_NONE ),
198- ),
184+ # class-level ignore_none set to False (non-default) takes precedence over field-level ignore_none
185+ ignore_none = (
186+ False
187+ if class_ignore_none is False
188+ else self ._get_field_ignore_none (
189+ field_name ,
190+ field_info ,
191+ DEFAULT_IGNORE_NONE ,
192+ )
199193 )
200194
201195 # Resolve Q expression for a field even if we skip it due to None value
0 commit comments