Skip to main content
Version: next

Django REST Framework Driver

Targets Django REST Framework — the dominant REST API toolkit in the Python ecosystem — paired with django-filter for filtering and PageNumberPagination for pagination.

Configure

import { DriverEnum, provideNgQubee } from 'ng-qubee';

bootstrapApplication(AppComponent, {
providers: [provideNgQubee({ driver: DriverEnum.DRF })]
});

Wire format

ConcernOutput
Filters (single value)field=value
Filters (multi-value)field__in=v1,v2,v3
Operator filtersdjango-filter lookup suffix — see below
Orderingordering=-field1,field2 (- prefix = DESC)
Searchsearch=term (DRF SearchFilter)
Paginationpage=N&page_size=M

ordering and page_size are DRF-idiomatic names and are intentionally not configurable through QueryBuilderOptions (same precedent as PostgREST's order / offset).

Operator filters

FilterOperatorEnum translates to django-filter's double-underscore lookup convention:

import { FilterOperatorEnum } from 'ng-qubee';

qb.addFilterOperator('price', FilterOperatorEnum.GTE, 100); // price__gte=100
qb.addFilterOperator('name', FilterOperatorEnum.CONTAINS, 'foo'); // name__contains=foo
qb.addFilterOperator('name', FilterOperatorEnum.ILIKE, 'JOHN'); // name__icontains=JOHN
qb.addFilterOperator('name', FilterOperatorEnum.SW, 'Ja'); // name__startswith=Ja
qb.addFilterOperator('tag', FilterOperatorEnum.IN, 'a', 'b', 'c'); // tag__in=a,b,c
qb.addFilterOperator('price', FilterOperatorEnum.BTW, 10, 100); // price__range=10,100
qb.addFilterOperator('deletedAt', FilterOperatorEnum.NULL, true); // deletedAt__isnull=true
qb.addFilterOperator('deletedAt', FilterOperatorEnum.NULL, false); // deletedAt__isnull=false

Translation table

FilterOperatorEnumdjango-filter lookup
EQ(no suffix — django-filter's default exact)
GT / GTE / LT / LTE__gt / __gte / __lt / __lte
CONTAINS__contains
ILIKE__icontains (case-insensitive contains)
IN__in (comma-joined values)
SW__startswith
BTW__range (arity-checked, comma-joined [min, max])
NULL (true)__isnull=true
NULL (false)__isnull=false
NOTunsupported — no generic negation in django-filter; use __exclude server-side
FTS / PHFTS / PLFTS / WFTSunsupported — PostgREST-only

Value shape rules

Enforced at call time — both throw InvalidFilterOperatorValueError:

  • BTW requires exactly 2 values ([min, max]).
  • NULL requires exactly 1 boolean value (trueIS NULL, falseIS NOT NULL).

Other operators leave shape validation to the server.

Combining simple and operator filters

Both forms emit independent query segments, so they coexist freely:

qb.addFilter('status', 'active');
qb.addFilterOperator('status', FilterOperatorEnum.NULL, false);
// → status=active&status__isnull=false

Feature matrix

MethodSupported?Notes
addFilter / deleteFiltersSingle value → field=value; multi-value → field__in=v1,v2,v3
addFilterOperator / deleteOperatorFiltersSee translation table above
addSort / deleteSortsEmitted as ordering=-field1,field2
setSearch / deleteSearchDRF's SearchFilter (search=term)
setLimit / setPagepage_size mirrors limit
addFields / deleteFields / deleteFieldsByModelDRF has no first-class per-model fields (django-restql exists but is not core)
addIncludes / deleteIncludesDRF has no first-class relation includes
addEmbedded / deleteEmbeddedPostgREST-only — throws UnsupportedEmbeddedError
addSelect / deleteSelectDRF has no flat column-list select

Response shape

DRF's PageNumberPagination returns:

{
"count": 100,
"next": "http://api.example.com/items/?page=3&page_size=10",
"previous": "http://api.example.com/items/?page=1&page_size=10",
"results": [
{ "id": 1, "name": "Hello" }
]
}

The body emits no current_page field, so DrfResponseStrategy derives it by parsing the previous URL:

  • previous === null → current page is 1.
  • previous set but with no ?page=N → current page is 2 (DRF omits page=1 from URLs when the previous page is the first).
  • previous contains ?page=N → current page is N + 1.

perPage is parsed from ?page_size=N on the next URL first, falling back to previous. lastPage is then computed as Math.ceil(count / perPage), and from/to follow from (currentPage - 1) * perPage + 1 and min(currentPage * perPage, count).

When neither URL carries ?page_size (e.g. a single-page result that fits the server's default size), perPage, lastPage, from, and to are left undefined — the body alone is not enough to recover them.

Customising response paths

provideNgQubee({
driver: DriverEnum.DRF,
response: {
data: 'items', // override 'results'
total: 'totalCount', // override 'count'
nextPageUrl: 'nextLink',
prevPageUrl: 'prevLink'
}
});

Defaults are encoded in DrfResponseOptions. The currentPage / perPage / lastPage / from / to slots are ignored by the strategy (it derives them from URL parsing instead), so overriding them in IConfig.response has no effect.