makeplane/plane · error · DRFValidationError
filterset_missing
filterset_missing
Error message
Filtering requires a filterset_class to be defined on the view
What it means
Raised by _build_leaf_q when the view has no filterset_class at the moment a leaf condition is turned into a Q object. Conceptually a sibling of filtering_not_enabled (122) but emitted later in the pipeline, after structural validation has already passed — meaning the view allowed field names through _validate_fields but lost the filterset by the time it built the query.
Source
Thrown at apps/api/plane/utils/filters/filter_backend.py:250
"""
return leaf_conditions
def _build_leaf_q(self, leaf_conditions, view, queryset):
"""Build a Q object from leaf filter conditions using the view's FilterSet.
We serialize the leaf dict into a QueryDict and let the view's
filterset_class perform validation and build a combined Q object
from all the field filters.
Returns a Q object representing all the field conditions in the leaf.
"""
if not leaf_conditions:
return Q()
# Get the filterset class from the view
filterset_class = getattr(view, "filterset_class", None)
if not filterset_class:
raise DRFValidationError(
{
"message": ("Filtering requires a filterset_class to be defined on the view"),
"code": "filterset_missing",
}
)
# Apply preprocessing hook
processed_conditions = self._preprocess_leaf_conditions(leaf_conditions, view, queryset)
# Build a QueryDict from the leaf conditions
qd = QueryDict(mutable=True)
for key, value in processed_conditions.items():
# Default serialization to string; QueryDict expects strings
if isinstance(value, list):
# Repeat key for list values (e.g., __in)
qd.setlist(key, [str(v) for v in value])
else:
qd[key] = "" if value is None else str(value)View on GitHub (pinned to 1c8a60f858)
Solutions
- Ensure filterset_class is set on the view before any filter request (same fix as filtering_not_enabled).
- If you subclass ComplexFilterBackend, do not bypass _validate_fields.
- In tests, set view.filterset_class explicitly rather than mocking the view wholesale.
Example fix
# before
class MyBackend(ComplexFilterBackend):
def _validate_fields(self, data, view):
pass # skip allowlist
# after
class MyBackend(ComplexFilterBackend):
# keep default _validate_fields; just declare filterset_class on the view
pass Defensive patterns
Strategy: validation
Validate before calling
if not getattr(view, 'filterset_class', None):
raise RuntimeError('View %r lacks filterset_class; ComplexFilterBackend cannot build a Q.' % type(view).__name__) Prevention
- Do not override _validate_fields to skip the allowlist check.
- Keep filterset_class as a class-level attribute so getattr cannot return None mid-request.
When it happens
Trigger: A subclass overrides _validate_fields to skip the allowlist check, or mutates view.filterset_class to None mid-request; in normal flow this is unreachable because _validate_fields already raised filtering_not_enabled. Most likely a custom subclass that bypasses the standard validation path.
Common situations: Custom ComplexFilterBackend subclass that overrides _validate_fields or _evaluate_node without preserving the filterset_class guard; concurrent mutation of view attributes during a test; mock frameworks that strip attributes off the view.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/57122fedcf8816ad.
Report an issue: GitHub.