makeplane/plane · error · DRFValidationError
filtering_not_enabled
filtering_not_enabled
Error message
Filtering is not enabled for this endpoint (missing filterset_class)
What it means
Raised by _validate_fields when the view exposes ComplexFilterBackend in filter_backends but does not declare a filterset_class attribute (or it has no base_filters). This is a fail-closed guard: the backend refuses to apply any user-supplied filter when no allowlist exists, preventing unintended data exposure.
Source
Thrown at apps/api/plane/utils/filters/filter_backend.py:106
# Validate against the view's FilterSet (only declared filters are allowed)
self._validate_fields(filter_data, view)
# Build combined Q object from the filter tree
combined_q = self._evaluate_node(filter_data, view, queryset)
if combined_q is None:
return queryset
# Apply the combined Q object to the queryset once
return queryset.filter(combined_q)
def _validate_fields(self, filter_data, view):
"""Validate that filtered fields are defined in the view's FilterSet."""
filterset_class = getattr(view, "filterset_class", None)
allowed_fields = set(filterset_class.base_filters.keys()) if filterset_class else None
if not allowed_fields:
# If no FilterSet is configured, reject filtering to avoid unintended exposure # noqa: E501
raise DRFValidationError(
{
"message": ("Filtering is not enabled for this endpoint (missing filterset_class)"),
"code": "filtering_not_enabled",
}
)
# Extract field names from the filter data
fields = self._extract_field_names(filter_data)
# Check if all fields are allowed
for field in fields:
# Field keys must match FilterSet filter names (including any lookups)
# Example: 'sequence_id__gte' should be declared in base_filters
# Special-case __range: require the '<base>__range' filter itself
if field not in allowed_fields:
raise DRFValidationError(
{
"message": f"Filtering on field '{field}' is not allowed",View on GitHub (pinned to 1c8a60f858)
Solutions
- Add a filterset_class to the view: `class MyView(...): filterset_class = MyFilterSet; filter_backends = (ComplexFilterBackend,)`.
- If the endpoint genuinely should not support filtering, remove ComplexFilterBackend from filter_backends so the param is ignored entirely.
- Verify the import path of the filterset is correct and the attribute is not shadowed by None.
Example fix
# before
class CycleIssueView(BaseViewSet):
filter_backends = (ComplexFilterBackend,)
# after
class CycleIssueView(BaseViewSet):
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet Defensive patterns
Strategy: validation
Validate before calling
def assert_view_filterable(view):
fs = getattr(view, 'filterset_class', None)
assert fs is not None and hasattr(fs, 'base_filters'), (
f'{type(view).__name__} uses ComplexFilterBackend but has no filterset_class'
) Prevention
- Treat filter_backends and filterset_class as a pair: adding one requires the other.
- Add a test that iterates views with ComplexFilterBackend in filter_backends and asserts filterset_class is set.
When it happens
Trigger: A developer adds `filter_backends = (ComplexFilterBackend,)` to a view (e.g. a new CycleIssueView or ModuleIssueView subclass) but forgets to add `filterset_class = SomeFilterSet`. Any GET to that endpoint with a `?filters=...` param returns 400 with filtering_not_enabled.
Common situations: New view scaffolding; removing a filterset during a refactor; subclassing an existing view and overriding filter_backends without redeclaring filterset_class; mis-importing the filterset so the attribute is None.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/b8a3e5dda25d027a.
Report an issue: GitHub.