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

  1. Ensure filterset_class is set on the view before any filter request (same fix as filtering_not_enabled).
  2. If you subclass ComplexFilterBackend, do not bypass _validate_fields.
  3. 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

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.