Overview
TurboDRF turns Django models into REST APIs. Add a mixin, describe the model in a turbodrf() method and register the router. You get list, detail, create, update and delete endpoints, with search, filtering, ordering, pagination and ?fields= selection.
What each role may do lives in one setting, TURBODRF_ROLES. Which rows a user can see lives on the model. Both are checked on every request.
Why I built it
Every Django project I started needed the same ViewSets, the same permission classes and the same tenant checks. I'd write them again, get one slightly wrong, and find out later. TurboDRF is that code written once, with the safe choice as the default. It's still small enough to read end to end.
Three layers on every request
- Roles.
TURBODRF_ROLESmaps each role to what it can do on each model, down to single fields. - Rows. A tenant boundary comes first. Predicates (
Owner,Members,Group,Either,Conditional,Custom) then narrow what each user sees. - Fields. Read and write rules per field. Writes are checked for foreign keys and many-to-many links that point into another tenant.
It won't start with a bad config
Most permission bugs are configuration bugs, so TurboDRF checks at startup. The app refuses to boot when:
- a model doesn't declare its tenancy,
- a permission string has a typo (it suggests the one you meant),
- a
Custompredicate has nowrite_validator, - a search field or the compiled read path would need an unsafe join.
Rows outside your access return a 404 instead of a 403, so the API never confirms they exist. When in doubt, it fails closed.
Fast reads, and tools to check them
List views skip DRF serializers by default and build .values() queries instead. turbodrf_explain prints the query plan each role gets, with the SQL if you want it. turbodrf_check validates the whole config and turbodrf_benchmark times your endpoints. JSON rendering through msgspec or orjson is an optional extra.
Endpoints of your own go through @turbodrf_action and inherit the same tenant and row scoping. Swagger and ReDoc come built in, and each caller's schema only lists what their role can reach.
What it looks like
from django.db import models
from turbodrf.mixins import TurboDRFMixin
class Project(models.Model, TurboDRFMixin):
title = models.CharField(max_length=200)
workspace = models.ForeignKey("accounts.Workspace", on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
searchable_fields = ["title"]
@classmethod
def turbodrf(cls):
return {
"fields": ["title", "workspace", "owner"],
"tenant_field": "workspace", # the tenant wall
"owner_field": "owner", # members see only their own rows
"bypass_owner_roles": ["manager"], # managers see the whole workspace
} # settings.py
TURBODRF_TENANT_MODEL = "accounts.Workspace"
TURBODRF_TENANT_USER_FIELD = "workspace"
TURBODRF_ROLES = {
"member": ["projects.project.read", "projects.project.create"],
"manager": ["projects.project.read", "projects.project.update"],
}
# urls.py
from turbodrf.router import TurboDRFRouter
router = TurboDRFRouter()
urlpatterns = [path("api/", include(router.urls))] Install
pip install turbodrf
pip install "turbodrf[fast]" # msgspec or orjson for JSON rendering The full guide, from the first model to tenancy and the management commands, is at turbodrf.com/docs.
Where it runs
The APIs behind Wealth MS and the bynoon CRM are both built on TurboDRF. Building Wealth MS turned up bugs in the compiled read path. They were fixed in 0.5.2.
Recent releases
Models with many-to-many fields use the compiled read path again on Django 6.0. Out-of-range IDs on Django 4.2 return 404 or 400 instead of a 500. CI now tests the Django version each job says it does.
A review of the whole framework. Field rules now cover anonymous callers through a guest role. Nested values from rows you can't see render as null. List and detail output match exactly, and the OpenAPI schema is built for each caller.
Fixes to the compiled read path, including a ?fields= request that could skip field-level rules.
A high-severity fix. On custom user models whose primary key isn't called id, cached permission snapshots could be shared between users.
Security hardening, custom endpoints, read-only models, http_methods allow-lists, full_clean on writes and a conformance suite. Postgres RLS support was removed.
Numbers
Reporting a security issue
Use GitHub's private vulnerability reporting on the repo. Fixes go into the 0.6.x line. The threat model and the known limits are written up in docs/security.md.