v0.6.1 Django REST Framework, generated

Your Django models,
served as a secure API.

One mixin and one method on a model give you the full REST API. Every request is held to the caller's tenant, their own rows, and the fields their role may read.

Quickstart
A real model, and what it returned to five callers. Pick one. 1Search2Sensitive field3Fields4Tenancy5Owner rule
projects/models.py
1from turbodrf.mixins import TurboDRFMixin
2
3class Project(models.Model, TurboDRFMixin):
4 title = models.CharField(max_length=200)
5 status = models.CharField(max_length=20, default="active")
6 workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE)
7 client = models.ForeignKey(Client, on_delete=models.CASCADE)
8 owner = models.ForeignKey(Member, null=True, on_delete=models.SET_NULL)
9 budget = models.DecimalField(max_digits=12, decimal_places=2)
10
1 searchable_fields = ["title"]
2 sensitive_fields = ["budget"]
13
14 @classmethod
15 def turbodrf(cls):
16 return {
3 "fields": [
18 "id",
19 "title",
20 "status",
21 "client",
22 "client__name",
23 "owner__username",
24 "budget",
25 ],
4 "tenant_field": "workspace",
5 "owner_field": "owner",
28 "bypass_owner_roles": ["admin", "manager"],
29 }
TURBODRF_ROLES in settings.py
adminreadcreateupdatedeletebudget.readbudget.write
managerreadupdatebudget.read
viewerread
Who's asking?
GET/api/projects/2001 row
Acme workspace
  • VWebsite relaunchbudget2
  • MMobile appnot returned5
  • AData warehousenot returned5
  • AOld prototypenot returned5
Globex workspace4
  • BPayments APInot returned
  • 4Acme's projects only.
  • 5viewer keeps the owner rule: just the project he owns.
  • 2budget left out: viewer has no budget.read.
Python 3.10 to 3.14 Django 4.2, 5.2 and 6.0 Django REST Framework 3.14+ MIT licensed

Permissions and tenancy

What each caller can do.

Seven requests from five callers: 35 real responses from the same model. Pick any cell to see the exchange and the rule behind it.

allowed allowed, a field held back refused

Swipe the table to see every caller.

RequestAaliceadmin · AcmeMmayamanager · AcmeVvictorviewer · AcmeBbobadmin · Globex?anonymousnot signed in
List projects
GET /api/projects/
Read “Website relaunch”
GET /api/projects/1/ · owner victor
Read “Mobile app”
GET /api/projects/2/ · owner maya
Change its status
PATCH /api/projects/1/ {status}
Change its budget
PATCH /api/projects/1/ {budget}
Use another workspace's client
POST /api/projects/ {client}
Delete “Old prototype”
DELETE /api/projects/5/
victor · viewer in Acme
GET/api/projects/2/

5“Mobile app” belongs to maya, and viewer keeps the owner rule.

404 Not Found
{
"detail": "No Project matches the given query."
}

On every request

Which rows, then which fields.

  1. Authenticated

    Anonymous callers get nothing unless the model is public_access.

  2. Role allows it

    demo.project.read, from TURBODRF_ROLES or the database.

  3. Rows in scope

    The caller's tenant, then owner and predicate rules.

  4. Fields they can read

    Sensitive fields need an explicit .read grant.

  5. Compiled query

    .values() with joins: no model instances to build.

  6. JSON

    Rendered by msgspec or orjson when installed.

Fails closed

Mistakes stop the app starting.

The router checks every model when it starts and refuses to serve an API with any of these:

  • A model with no tenancy decision
  • A permission string with a typo in the app, model or field
  • A Tenant() inside Either(...)
  • A searchable field that reaches across a scoped relation
  • A nested field deeper than TURBODRF_MAX_NESTING_DEPTH
python manage.py runserver
django.core.exceptions.ImproperlyConfigured: Invoice.turbodrf() declares no tenancy and no tenant FK could be auto-detected to demo.Workspace. Add one of: 'tenant_field': '<path>', 'visibility': [...], or 'tenancy': 'shared' (for reference data). An 'owner_field' alone scopes rows within a tenant, not across tenants. Set TURBODRF_REQUIRE_TENANCY=False to disable this check.

Also generated

Everything else a REST API needs.

The whole REST surface

List, detail, create, update and delete routes, plus OpenAPI, from the model. No view or URL files.

Search, filter, order

Lookups, ordering and ?fields= on every field the caller can read, nothing else.

Predicates when you need them

Owner, Members, Group, Either and Conditional for row rules beyond owner and tenant.

Lean list reads

Lists read values straight from the database, with joins for nested fields, in a fixed number of queries.

OpenAPI per role

Swagger and ReDoc show each caller only the fields their role can read and write.

Roles from your identity provider

Map django-allauth groups or Keycloak / OIDC claims to roles. Sentry hooks included.

python manage.py turbodrf_check
demo.Client [compiled] OK
  Fields: 1 DB, 0 FK, 0 M2M, 0 property
  Public access: False
  Tenant model: demo.Workspace
  Tenant field: workspace

demo.Project [compiled] OK
  Fields: 5 DB, 2 FK, 0 M2M, 0 property
  Public access: False
  Tenant model: demo.Workspace
  Tenant field: workspace
  Within-tenant predicates: Owner('owner', bypass=['admin', 'manager'])

For you and your coding agent

Ships with an AGENTS.md.

Instructions written for AI coding agents: how to add a model, which tenancy key to pick, and what never to do. turbodrf_check reports each model's tenancy so you can see what it'll enforce.

Read AGENTS.md

Watch it run

Fifteen seconds each.

A model becomes an API
A model becomes an API
Same request, four callers
Same request, four callers
Six refused requests
Six refused requests

Start with one model.

Install it, add the mixin and the router, give your roles their permissions.