Skip to content

CRUD

Class attributes of the generic model controllers and their companions. Attributes left unset fall back to settings. See the CRUD guide.

ModelController (every model controller)

Base for controllers backed by a Django model.

Name Type Default Description
mode Literal['sync', 'async', 'auto'] 'auto' Which implementation of each operation to register: "auto" follows NINJA_DEVX["ASYNC_MODE"].
lookup_field str 'pk' Model field matched by the lookup path segment ("slug", "uuid").
lookup_param str 'pk' Name of the lookup path segment: "slug" exposes /{slug}. Declared paths keep using {pk}.
lookup_converter bool False Use Django path converters ({int:pk}): invalid ids get Django's 404 instead of a JSON 422, and static routes of routers mounted later are never shadowed.
owner_field str \| None None Path to the user ("author", "customer__user"): enforced with IsAuthenticated and IsOwner; a direct foreign key is also set on create.
scope_queryset_to_owner bool False Also restrict every query (including lists) to the current user's objects.
tenant_field str \| None None The field pointing at the tenant ("organization", "project__organization"): every query is filtered by the current tenant, which is also set on create.
tenant_resolver TenantResolver \| None None Returns the request's tenant (sync or async); defaults to NINJA_DEVX["TENANT_RESOLVER"]. Assign it with staticmethod(...).
tenant_context object \| None None A RequestContext[User, Tenant] key resolved from the container; its tenant is used. Defaults to NINJA_DEVX["TENANT_CONTEXT"].
object_permissions ObjectPermissions \| None None Per-object Django permissions (grants or django-guardian): object checks by HTTP method and, with filter_lists, querysets limited to viewable objects.
sparse_fields bool False Let clients pick response fields with ?fields=id,title (list and retrieve); the output schema needs the FieldVisibility mixin. Expandable relations add ?expand=.
fields_param str 'fields' Query parameter of sparse_fields.
expand_param str 'expand' Query parameter listing the Expandable relations to embed.
etag ETag \| None None Conditional requests: ETag and 304 on reads, If-Match and 412 on writes.
parent Parent \| None None Nest under a parent from the URL, e.g. Parent(Author, field="author").
service_class type[object] \| None None The ModelService subclass handling writes, resolved from the container when there is one. Defaults to a ModelService over a ModelRepository of the model.
validate_model bool True Run Model.full_clean() before saving (for the default service); errors are 422.
refresh_after_write bool True Re-fetch through scoped_queryset() after writes so responses see its joins.
optimize_queries bool \| Literal['only'] True Join/prefetch what the output schema renders; "only" also restricts columns.
related Sequence[str] () Explicit lookups the N+1 planner always loads (("author", "comments__user")), for resolvers or properties it cannot analyse. Also set with @requires_related.
expand_rules Mapping[str, ExpandRule] MappingProxyType({}) How an expanded to-many relation is loaded ({"comments": ExpandRule(limit=5)}); keys must be Expandable fields of the output schema.
openapi_examples bool False Fill an OpenAPI example into the input and output schemas from ninja_devx.testing.sample (see ninja_devx.serialization.examples.with_examples).

List options (ListMixin, ReadOnlyModelController, CRUDController)

Filtering, search, ordering, pagination and selectors for list endpoints.

Name Type Default Description
filter_schema type[FilterSchema] \| None None An explicit Ninja FilterSchema for GET / (instead of generated filters).
search_fields Sequence[str] () Fields searched with icontains by the search_param query parameter.
search_backend SearchBackend[Model] \| None None Replace the default icontains search (for example PostgresSearch()).
search_param str 'search' Name of the search query parameter.
filter_fields FilterFields MappingProxyType({}) Generated typed filters: {"status": ("exact",), "created": ("gte", "lte")}.
filterset_class type[FilterSetLike] \| None None A django-filter FilterSet whose filters become query parameters of GET / (pip install ninja-devx[filters]); it runs with the request.
ordering_fields Sequence[str] () Fields the client may order by (?ordering=-created), validated as an enum.
ordering_param str 'ordering' Name of the ordering query parameter.
default_ordering Sequence[str] () Ordering when the client sends none; must be among ordering_fields.
pagination_class type[PaginationBase] \| None None A Ninja pagination class (or CursorPagination); defaults to NINJA_DEVX["PAGINATION_CLASS"].
pagination_options Mapping[str, object] MappingProxyType({}) Keyword arguments for the pagination class ({"page_size": 50}).
selector_class type[Selector[Never, Model]] \| None None A selector (resolved from the container when there is one) replacing get_queryset for lists. Querysets it returns are still ordered, paginated and optimized.

ExpandRule

How ?expand= loads a to-many relation: filtered, ordered and capped per parent.

Name Type Default Description
filter Q \| None None Restricts the related rows, applied before order_by and limit.
order_by tuple[str, ...] () Ordering applied before limit; defaults to the related model's Meta.ordering.
limit int \| None None Rows kept per parent object.

Generated schemas (AutoCRUDController, AutoReadOnlyController)

Which model fields the generated schemas contain.

Name Type Default Description
schema_fields Sequence[str] \| Literal['__all__'] '__all__' Model fields in the schemas (field names; "__all__": every concrete and m2m field).
schema_exclude Sequence[str] () Fields left out of both schemas.
read_only_fields Sequence[str] () Fields in responses only. The primary key and non-editable fields always are.
write_only_fields Sequence[str] () Fields in requests only (password).

model_schemas()

def model_schemas(model: type[Model], *, fields: Sequence[str] | Literal['__all__'] = '__all__', exclude: Sequence[str] = (), read_only: Sequence[str] = (), write_only: Sequence[str] = ()) -> tuple[type[Schema], type[Schema]]: ...

(<Model>Out, <Model>In) generated with Ninja's create_schema (cached).

Parameter Type Default Description
model type[Model] — The Django model.
fields Sequence[str] \| Literal['__all__'] '__all__' Field names to include, or "__all__".
exclude Sequence[str] () Field names left out of both schemas.
read_only Sequence[str] () Field names only in the output schema.
write_only Sequence[str] () Field names only in the input schema.

Bulk options (Bulk*Mixin)

Name Type Default Description
bulk_limit int 100 Maximum number of objects per bulk request (NINJA_DEVX["BULK_LIMIT"]).

BulkErrorDetail

One item of a bulk 207 entry's errors, in Ninja's validation error shape.

Name Type Default Description
type str — Ninja's validation error type ("missing", "value_error", ...).
loc builtins.list[int \| str] — Field path within the item, as Ninja reports it (without the item's own index).
msg str — Human-readable message.

BulkResultOut: {"results": [{"index", "status", "data"} | {"index", "status", "errors"}]}, the body of a partial-success (207) bulk response from bulk_partial=True; errors uses BulkErrorDetail, and the response carries an X-Bulk-Failed count header.

NestedWritesMixin

Adds child collections to create and update, declared with nested.

Name Type Default Description
nested Mapping[str, Nested] MappingProxyType({}) Child collections keyed by their field on the input schema.

Nested

One child collection written alongside its parent.

Name Type Default Description
model type[Model] — The child model.
field str — The child's foreign key to the parent.
schema type[BaseModel] — Input schema validating each item.
key str 'id' Field matching a payload item against an existing child on update.
remove_missing bool True On update, delete existing children whose key is absent from the payload.

TransitionsMixin

Adds POST /{pk}/<name> for each declared transition and GET /{pk}/transitions.

Name Type Default Description
state_field str 'status' The CharField holding the object's state.
transitions Mapping[str, Transition] MappingProxyType({}) Transition name to Transition.

Transition

One named move from a set of source states to a target state.

Name Type Default Description
source Sequence[str] — States the object must be in for this transition to apply.
target str — State written when the transition succeeds.
permissions Sequence[AnyPermission] () Added to the controller's permissions for this transition's route only.
guard Callable[[HttpRequest, Model], bool] \| None None Extra precondition beyond the source state; False also answers 409.
on_transition Callable[[HttpRequest, Model], None] \| None None Called after the new state is saved, inside the write transaction.

MetaMixin (GET /meta)

Describes the input and output schemas for a form or admin UI: field types, required/read-only, max length and enum choices, plus filter_fields, ordering_fields and search_fields. No configuration.

MetaMixin schemas

Class Arguments Description
ControllerMeta input: dict[str, FieldMeta], output: dict[str, FieldMeta], filter_fields: list[str], ordering_fields: list[str], search_fields: list[str] The body of GET /meta.
FieldMeta type: str, required: bool, read_only: bool, max_length: int \| None = None, choices: list[ChoiceOut] \| None = None What a form or admin UI needs to know about one schema field.
ChoiceOut value: str \| int \| bool, label: str One value/label choice.

AggregateMixin

GET /stats: grouped counts and sums over the controller's scoped queryset.

Name Type Default Description
aggregate_fields Sequence[str] () Allow-list of model fields group_by may name.
aggregate_metrics Mapping[str, Aggregate] MappingProxyType({'count': Count('pk')}) Allow-list of aggregate expressions metrics may name, by the name clients use.

ExportMixin

GET /export: the filtered list as CSV or JSON Lines, streamed.

Name Type Default Description
export_formats Sequence[ExportFormat] ('csv', 'jsonl') Formats clients may ask for; the first is the default.
export_filename str \| None None Download name without extension (default: the model's plural name).
csv_escape_formulas bool True Prefix text cells starting with = + - @ with ' so spreadsheets don't run them.
export_sensitive bool False Include Sensitive output fields unmasked (default: exported as "***").

ImportMixin

POST /import: create objects from a CSV or JSON Lines file, all or nothing.

Name Type Default Description
max_import_bytes int 10000000 Reject files larger than this byte limit before parsing or starting writes.
max_import_rows int 10000 Larger files are rejected with 413.
max_import_errors int 50 Stop validating after this many errors.

ObjectSharingMixin

Endpoints managing object permissions (grants backend or django-guardian).

Name Type Default Description
shareable_permissions tuple[str, ...] ('view', 'change', 'delete') Permission actions clients may grant (view means <app>.view_<model>).
sharing_permission str 'change' Action the caller needs on the object to see or change its sharing.

SoftDeleteMixin

Marks objects deleted instead of deleting them and adds POST /{pk}/restore.

Name Type Default Description
soft_delete SoftDelete \| str SoftDelete() A SoftDelete or just the field name.
soft_delete_cascade Sequence[str] () Related accessor names marked deleted/restored with this object (("comments", "attachments")). Only the configured marker field is cascaded.

SoftDelete

How a model marks deleted rows.

Name Type Default Description
field str 'deleted_at' The field marking deleted rows.
deleted object INFER Value written on delete (or a zero-argument callable); inferred for boolean and nullable datetime fields.
active object INFER Value of rows that are not deleted, written on restore; inferred like deleted.
deleted_at str \| None None Also set this DateTimeField to now on delete (and clear it on restore).
deleted_by str \| None None Also set this foreign key to the current user on delete (and clear it on restore). Defaults to "deleted_by" for models built on SoftDeletable.

soft_delete_unique()

def soft_delete_unique(model: type[Model], *fields: str, config: SoftDelete | None = None, name: str | None = None) -> UniqueConstraint: ...

A partial UniqueConstraint that only applies to active (not deleted) rows.

Parameter Type Default Description
model type[Model] — The model (used to resolve the marker field and its active value).
*fields str — The constrained fields.
config SoftDelete \| None None The SoftDelete configuration (default: deleted_at).
name str \| None None Constraint name (default derived from the table and fields).

Model bases (ninja_devx.models)

Abstract models for bookkeeping columns; controllers fill the user columns.

Base Columns Notes
TimeStamped created_at, updated_at auto_now_add/auto_now; created_at is indexed
UserStamped created_by, updated_by nullable AUTH_USER_MODEL keys (SET_NULL), set from the request user on create and update
Stamped all of the above
SoftDeletable deleted_at, deleted_by the defaults SoftDeleteMixin uses without a soft_delete configuration

Every column is editable=False and is left out of generated input schemas.

Parent

Scope a model controller to a parent object taken from the URL.

Name Type Default Description
model type[Model] — The parent model.
field str — The child's foreign key to the parent.
lookup_field str 'pk' Parent field matched by the URL segment.
param str \| None None Name of the URL segment (default <field>_pk).
tenant_field str \| None None Only find parents of the current tenant ("organization"): other tenants' parents 404.

ETag

Name Type Default Description
field str \| None None A field that changes on every write (updated_at, version). None hashes the output schema's representation instead, which costs one extra serialization per check.
weak bool False Weak tags (W/"...") survive compression and JSON formatting differences.
require_if_match bool False Writes without If-Match fail with 428 Precondition Required.
lists bool True Also tag list responses (from their rendered body).

Search backends

Class Arguments Description
IContainsSearch — Case-insensitive substring match across the fields, as a backend.
PostgresSearch *, config: str = 'english' PostgreSQL full-text search across the fields with a shared language config.

LimitOffsetPagination

pagination_class = LimitOffsetPagination with pagination_options:

Option Type Default Description
limit int 100 page size when the client sends no ?limit=
max_limit int 1000 upper bound for ?limit=
count bool \| Literal["estimate"] \| int True True: exact COUNT(*). False: no count query, null, one extra row fetched instead. "estimate": pg_class.reltuples on PostgreSQL for an unfiltered queryset, exact otherwise. N: exact up to N rows, then N with X-Total-Count: N+
max_offset int \| None None reject deeper ?offset= with 422

Query parameters: limit, offset. The response is {"items", "count", "next", "previous"} with absolute links that keep the other query parameters.

CursorPagination

pagination_class = CursorPagination with pagination_options:

Option Type Default Description
page_size int NINJA_PAGINATION_PER_PAGE items per page
max_page_size int NINJA_PAGINATION_MAX_PER_PAGE_SIZE upper bound for ?page_size=

Query parameters: cursor, page_size. Ordering comes from the request (ordering_param), default_ordering, Meta.ordering, then -pk.

Parameter annotations

Annotation Resolves to Exposed in OpenAPI
Lookup the lookup value, typed like lookup_field path {lookup_param}
Instance[Model] the object (404, object permissions) path {lookup_param}
Locked[Model] the object with select_for_update() path {lookup_param}
Filters the filter schema (generated or filter_schema) query parameters
Ordering the ordering schema query ordering_param
Patch[In] PatchData of the fields sent body
Annotated[int \| Out, Expandable()] (output field) the key, or Out with ?expand= query expand_param
BulkPatch[In] / BulkDelete {"pks": [...], "data": {...}} / {"pks": [...]} body