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 |