Filtering, search and ordering¶
Generated list routes support query parameters, and optional filter backends add coercion, search and ordering. These control query shape; they do not replace authentication, object/tenant policy or PostgreSQL RLS.
Configure the Ticket list¶
Use the Ticket model from the first-project tutorial.
This ViewSet replaces that tutorial's ViewSet rather than registering a second
route at the same prefix. Keep the application's authentication adapter and
register this class with include_viewset as shown in the
ViewSet guide.
from aksara import ModelViewSet
from aksara.api import AksaraFilterBackend, SearchFilter, OrderingFilter
from aksara.permissions import IsAuthenticated
from .models import Ticket
class TicketViewSet(ModelViewSet):
model = Ticket
prefix = "/api/tickets"
permission_classes = [IsAuthenticated]
ai_exposed = False
stream_enabled = False
filter_backends = [AksaraFilterBackend, SearchFilter, OrderingFilter]
filterable_fields = ["resolved"]
search_fields = ["subject", "description"]
ordering_fields = ["subject", "created_at", "id"]
ordering = ["subject", "id"]
def get_filter_fields(self):
return list(self.filterable_fields)
After authentication, clients can request:
GET /api/tickets/?resolved=false
GET /api/tickets/?search=login
GET /api/tickets/?resolved=false&search=login&ordering=-subject
GET /api/tickets/?ordering=-created_at,id&limit=20&offset=0
There are two filter entry points to configure:
- The generated router calls
get_filter_fields(). Its default returns all model field names; override it to restrict router-provided filters. AksaraFilterBackendusesfilterable_fields. That attribute does not by itself change the router's default allowlist.
The example shares one allowlist between the two. An unrelated query parameter is ignored rather than rejected. An accepted field prefix may still carry an unsupported lookup suffix; this backend is not a comprehensive query-language validator. Validate client-facing query contracts explicitly if your application requires a uniform 400/422 response for invalid filter syntax.
The backend runs synchronously to build a QuerySet. Database execution happens
later. DjangoFilterBackend is a compatibility alias for AksaraFilterBackend;
use the latter name in new applications.
Lookups and coercion¶
Common ORM lookup forms include exact equality, __gt, __gte, __lt, __lte,
__in, __isnull, __contains and __icontains. The field type, ORM compiler
and PostgreSQL still determine which combinations are valid. The list above is
not an exhaustive validation allowlist implemented by the backend.
The backend's coercion is heuristic, not derived from model-field definitions:
| Input form | Value supplied by the backend |
|---|---|
Exact true, yes, 1 (case-insensitive words) |
True |
Exact false, no, 0 |
False |
Exact null, none, or empty string |
None |
Other exact values such as 30 or 19.99 |
String, before subsequent ORM conversion |
__in=a,b,c |
List of nonempty trimmed strings |
__gt, __gte, __lt, __lte |
Tries float when the value contains a dot, otherwise integer; retains a string if parsing fails |
__isnull |
Uses the general coercion above; arbitrary strings are not strictly rejected here |
These rules can surprise applications with string identifiers such as "1",
"true" or "none". Do not promise that every field receives the same typed
value a request-body serializer would produce. Use an application-specific
backend or explicit parameter validation when the heuristic is unsuitable.
Search¶
SearchFilter uses the entire search value as a substring and combines the
configured fields with OR. Use direct text field names. Relation paths such as
author__name are not supported by this search implementation and fail when the
query is compiled. This is SQL ILIKE search, not tokenization, full-text search,
ranking, or semantic/vector search. % and _ can act as pattern wildcards.
Choose searchable fields deliberately; being able to search a field can reveal information even when it is omitted from a response. Keep tenant and object policy independent of client search terms.
Ordering¶
OrderingFilter reads comma-separated terms from ordering; - requests
descending order. Terms must be in ordering_fields and, when a principal is
resolved, pass the backend's field-visibility and tenant-field restrictions.
Prefer an explicit allowlist over "__all__".
ordering on the class supplies the default when there is no requested ordering
(or no ordering allowlist). A string is one term; use a list/tuple for several
default terms. If a client supplies only invalid terms, the backend returns the
query unchanged rather than applying the configured default. Do not assume such
a response has deterministic ordering. For normal paginated requests, include a
unique tie-breaker such as id.
Pagination and authorization¶
The generated default list envelope contains count, results, limit and
offset. count describes the filtered result set. Default limit is 20, maximum
100; the generated route rejects limits below 1 or above the configured maximum,
and negative offsets, with HTTP 422. Each built-in pagination class exposes its
own route parameters and response metadata. Application-defined paginators can
declare response schema fields, while undeclared custom metadata is preserved in
an unstructured response mapping. See
custom pagination classes.
Permissions are checked before the list query. PolicyEngine's required query filters are reapplied after filter backends so client parameters cannot replace them. This does not turn custom backends into trusted authorization code or prove that every application object policy is a SQL filter. Test cross-tenant and unauthorized requests with your real identity adapter and restricted role.