Skip to content

Commit 78a2b9c

Browse files
committed
feat(filters): add filter by method, refactor ParameterDefinition to Parameter for consistency across filter definitions
1 parent 9d83ba6 commit 78a2b9c

3 files changed

Lines changed: 33 additions & 13 deletions

File tree

app/domains/company/filters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ class CompanyFilterSet(filters.FilterSet):
77
filters.CharFilter('name', view_name='search', default_lookup='icontains'),
88
filters.IntegerFilter('id', lookups=['in', 'gte', 'lte']),
99
filters.BooleanFilter('name', default_lookup='isnull', exclude=True),
10-
filters.ChoiceFilter('status', choices=CompanyStatus),
10+
filters.ChoiceFilter('status', choices=CompanyStatus, lookups=['exact', 'isnull'], method='by_status'),
1111
]
1212

1313
class Meta:
1414
model = Company
15+
16+
def by_status(self, queryset, value: CompanyStatus, parameter: filters.Parameter):
17+
return self.filter_by_parameter(queryset, value, parameter)

fastapi_ronin/filters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
FilterSet,
1010
FloatFilter,
1111
IntegerFilter,
12+
Parameter,
1213
UUIDFilter,
1314
)
1415

@@ -22,6 +23,7 @@
2223
'FilterSet',
2324
'FloatFilter',
2425
'IntegerFilter',
26+
'Parameter',
2527
'UUIDFilter',
2628
'ChoiceFilter',
2729
]

fastapi_ronin/filters/filters.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212

1313

1414
@dataclass
15-
class ParameterDefinition:
15+
class Parameter:
1616
param_name: str
1717
field_name: str
18+
view_name: str
1819
lookup: str
1920
negate: bool
2021
filter: 'Filter'
@@ -35,6 +36,7 @@ def __init__(
3536
default: Any = None,
3637
default_lookup: Optional[str] = None,
3738
description: Optional[str] = None,
39+
method: Optional[str] = None,
3840
_field_attrs: Optional[Dict[str, Any]] = None,
3941
_field_type: Optional[Type] = None,
4042
**kwargs,
@@ -49,6 +51,7 @@ def __init__(
4951
self.lookups = lookups or [self.default_lookup or 'exact']
5052
self.required = required
5153
self.exclude = exclude
54+
self.method = method
5255
self.field_attrs = {
5356
'description': description,
5457
'default': default or ... if (self.required and len(self.lookups) == 1) else None,
@@ -76,17 +79,18 @@ def _validate(self):
7679
if token not in self.lookups:
7780
raise ValueError(f"default_lookup '{self.default_lookup}' not in lookups {self.lookups}")
7881

79-
def get_param_definitions(self) -> List[ParameterDefinition]:
80-
defs: List[ParameterDefinition] = []
82+
def get_param_definitions(self) -> List[Parameter]:
83+
defs: List[Parameter] = []
8184
for lookup in self.lookups:
8285
display_token = f'not_{lookup}' if self.exclude else lookup
8386
annotation = LOOKUP_TYPES.get(lookup, self.field_type)
8487
param_name, negate_flag = self._resolve_param_name(lookup, display_token)
85-
param_def = ParameterDefinition(
88+
param_def = Parameter(
8689
param_name=param_name,
8790
negate=negate_flag,
8891
annotation=Optional[annotation], # type:ignore
8992
filter=self,
93+
view_name=self.view_name,
9094
field_attrs=self.field_attrs,
9195
field_name=self.field_name,
9296
lookup=lookup,
@@ -153,7 +157,7 @@ def __new__(mcs, name, bases, namespace, **kwargs):
153157
if not isinstance(filters, list):
154158
raise ValueError('fields must be a list of Filter objects')
155159

156-
param_map: Dict[str, ParameterDefinition] = {}
160+
param_map: Dict[str, Parameter] = {}
157161
for f in filters:
158162
if not isinstance(f, Filter):
159163
raise ValueError('All elements of fields must be Filter instances')
@@ -181,7 +185,7 @@ def _validate_model(self):
181185

182186
@classmethod
183187
def get_build(cls):
184-
param_map: Dict[str, ParameterDefinition] = getattr(cls, '_param_map', {})
188+
param_map: Dict[str, Parameter] = getattr(cls, '_param_map', {})
185189

186190
def build(**kwargs):
187191
data = {k: v for k, v in kwargs.items() if v is not None}
@@ -201,19 +205,30 @@ def build(**kwargs):
201205
return build
202206

203207
def filter_queryset(self, queryset: QuerySet) -> QuerySet:
204-
param_map: Dict[str, ParameterDefinition] = getattr(self, '_param_map', {})
208+
param_map: Dict[str, Parameter] = getattr(self, '_param_map', {})
205209

206210
for pname, raw_value in self.data.items():
207-
info: Optional[ParameterDefinition] = param_map.get(pname)
208-
if not info or raw_value is None:
211+
parameter: Optional[Parameter] = param_map.get(pname)
212+
if not parameter or raw_value is None:
209213
continue
210214

211-
processed = info.filter._process_value(raw_value, info.lookup)
215+
processed = parameter.filter._process_value(raw_value, parameter.lookup)
212216
if processed is None:
213217
continue
214218

215-
kwargs = LOOKUP_EXPRESSIONS[info.lookup](info.field_name, processed)
216-
queryset = queryset.exclude(**kwargs) if info.negate else queryset.filter(**kwargs)
219+
if parameter.filter.method:
220+
method = getattr(self, parameter.filter.method, None)
221+
if method and callable(method):
222+
result = method(queryset=queryset, value=processed, parameter=parameter)
223+
if isinstance(result, QuerySet):
224+
queryset = result
225+
else:
226+
queryset = self.filter_by_parameter(queryset, processed, parameter)
227+
return queryset
228+
229+
def filter_by_parameter(self, queryset: QuerySet, value: Any, parameter: Parameter) -> QuerySet:
230+
kwargs = LOOKUP_EXPRESSIONS[parameter.lookup](parameter.field_name, value)
231+
queryset = queryset.exclude(**kwargs) if parameter.negate else queryset.filter(**kwargs)
217232
return queryset
218233

219234

0 commit comments

Comments
 (0)