Dead simple Django REST API generator with role-based permissions
Project description
TurboDRF
Dead simple Django REST Framework API generator with role-based permissions.
Turn your Django models into fully-featured REST APIs with a mixin and a method. Zero boilerplate.
Walkthrough
A 16-minute walkthrough covering setup, query parameters, writes, role-based access control, predicates, and the security model:
If your renderer doesn't show the player, use the direct link.
Install
pip install turbodrf
# Optional: faster JSON rendering (7x faster than stdlib)
pip install turbodrf[fast]
Quick Start
1. Add to settings:
INSTALLED_APPS = [
'rest_framework',
'turbodrf',
]
2. Add the mixin to your model:
from django.db import models
from turbodrf.mixins import TurboDRFMixin
class Book(models.Model, TurboDRFMixin):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
searchable_fields = ['title']
@classmethod
def turbodrf(cls):
return {
'fields': {
'list': ['title', 'author__name', 'price'],
'detail': ['title', 'author__name', 'author__email', 'price']
}
}
3. Add the router:
from turbodrf.router import TurboDRFRouter
router = TurboDRFRouter()
urlpatterns = [
path('api/', include(router.urls)),
]
Done. You now have a full REST API with search, filtering, pagination, and field selection:
GET /api/books/ # List
GET /api/books/1/ # Detail
POST /api/books/ # Create
PUT /api/books/1/ # Update
DELETE /api/books/1/ # Delete
GET /api/books/?search=django # Search
GET /api/books/?price__lt=20 # Filter
GET /api/books/?fields=title,price # Select fields
Documentation
- Configuration -- all
turbodrf()options, field selection, nested fields - Permissions -- role-based, field-level, and Django default permissions
- Tenancy & row-level access -- predicates, multi-tenancy, FK injection defense
- RLS (Postgres) -- optional defense-in-depth at the database layer
- Performance -- compiled read path, fast JSON rendering, benchmarking
- Filtering & Search -- filtering, search, ordering, OR queries
- Integrations -- allauth, Keycloak, drf-api-tracking (all experimental)
- Security -- sensitive fields, secure defaults, error responses
- Management Commands -- turbodrf_check, turbodrf_benchmark, turbodrf_explain
- Settings Reference -- every TURBODRF_* setting in one place
Permissions and access control
TurboDRF answers four standard authorization questions, in three layers that all apply to every request (AND'd together):
| Question | Layer | Mechanism |
|---|---|---|
| Can this user reach this endpoint? | RBAC (Role-Based Access Control) | Roles in TURBODRF_ROLES map to permissions. permissions.py checks <app>.<model>.<action> for the request method. |
| Which rows can this user see? | Row-level access | Predicates declared in turbodrf() config. Mandatory tenant boundary + discretionary within-tenant predicates (Owner, Members, Either, Custom). |
| Which fields can this user read or write? | Field-level permissions | Per-field rules <app>.<model>.<field>.read / .write in TURBODRF_ROLES. Hidden fields are stripped from responses, search, ordering, filters, and OPTIONS metadata. |
| Are FK targets the user provides actually theirs? | Write validation | On every create/update, FKs in the request body are validated against the related model's predicate stack. Cross-tenant or invisible targets return 400. |
How it actually works (concrete walk-through)
A multi-tenant SaaS has two workspaces (ABC and XYZ) and three roles: member, manager, admin. A Project model is configured:
class Project(models.Model, TurboDRFMixin):
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
@classmethod
def turbodrf(cls):
return {
'tenant_field': 'workspace', # mandatory wall
'owner_field': 'owner', # within-tenant rule
'bypass_owner_roles': ['manager', 'admin'], # roles ignore owner check
'fields': ['title', 'workspace', 'owner'],
}
Plus two project-wide settings:
TURBODRF_TENANT_MODEL = 'accounts.Workspace'
TURBODRF_TENANT_USER_FIELD = 'workspace' # request.user.workspace → tenant
Now a request GET /api/projects/ from Alice (member at ABC) goes through:
- Permission gate — Alice's role
memberhasapp.project.read. Pass. - Tenant filter (mandatory, applied first, never bypassable):
WHERE project.workspace_id = <Alice's workspace>
- Owner filter (Alice has no bypass role, so this layer applies):
AND project.owner_id = <Alice's user id>
- Field stripping — Alice's role has read on
title,workspace,ownerbut maybe not all configured fields. Hidden ones are removed from the response.
If Alice tries cross-tenant tricks:
GET /api/projects/<XYZ_project_id>/→ 404 (not 403, no existence leak)PATCH /api/projects/<her_project_id>/ {"workspace": <XYZ>}→ 400 (tenant reassignment rejected)POST /api/comments/ {"document": <XYZ_bank_id>}→ 400 (FK injection rejected)
If a manager (with bypass) at ABC asks for /api/projects/:
WHERE project.workspace_id = <ABC's workspace id>
-- no owner filter (manager bypassed it)
Manager sees all ABC projects, but still can't see XYZ — the tenant boundary is mandatory and applied separately from the predicate algebra (it's a setting, not a predicate). This rules out an entire class of compositional bugs where bypass roles could OR-compose their way past the tenant wall.
Optional: Postgres Row Level Security (defense in depth)
For Postgres deployments, TurboDRF can additionally generate RLS policies that enforce the same rules at the database layer — every connection is filtered by Postgres itself, so even raw SQL or admin scripts are blocked. App-layer is the source of truth; RLS is a backup. See docs/rls.md. RLS is off by default (three manual steps to enable: install middleware, run turbodrf_emit_rls, apply the SQL).
Performance
Tenant + owner predicates add ~0 measurable latency vs. the unscoped baseline (predicates compile to a single Q AND'd onto the queryset; the WHERE clause runs at the DB layer with index hits). FK injection check on writes adds ~one .exists() query per FK in the request body. Both are negligible for typical workloads. See docs/performance.md for benchmarking the compiled vs DRF read paths.
Quick recipes
# Multi-tenant SaaS — most common case
{'tenant_field': 'store', 'owner_field': 'customer', 'bypass_owner_roles': ['staff']}
# Personal data app (no tenant)
{'owner_field': 'author', 'bypass_owner_roles': ['admin']}
# Reference data (currencies, country codes — not tenant-scoped)
{'tenancy': 'shared'}
# M2M membership (Slack channels, Linear projects)
{'visibility': [Tenant('workspace'), Members('participants')]}
# Power-form composition (when sugar doesn't fit)
{'visibility': [Tenant('workspace'), Either(Owner('owner'), Members('shared_with'))]}
See docs/tenancy.md for the full predicate vocabulary, hard-fail-at-startup behavior, and 404-vs-403 semantics.
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file turbodrf-0.4.0.tar.gz.
File metadata
- Download URL: turbodrf-0.4.0.tar.gz
- Upload date:
- Size: 323.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
309f01c6695d7f2932682ab359520f2cce444ad9e364fa285fa5722b0efba645
|
|
| MD5 |
b2e8c4a3f6e9b2175cd41acab0441dab
|
|
| BLAKE2b-256 |
c4a4d00f98b2862371c7a7647fc8cb27c1bb5a4ddf045b095682cbf0056c143b
|
Provenance
The following attestation bundles were made for turbodrf-0.4.0.tar.gz:
Publisher:
publish.yml on AlexanderCollins/TurboDRF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
turbodrf-0.4.0.tar.gz -
Subject digest:
309f01c6695d7f2932682ab359520f2cce444ad9e364fa285fa5722b0efba645 - Sigstore transparency entry: 1440210415
- Sigstore integration time:
-
Permalink:
AlexanderCollins/TurboDRF@81bc93dfe4feb2d175a44cd20ef0f75b55d7c4d3 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/AlexanderCollins
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@81bc93dfe4feb2d175a44cd20ef0f75b55d7c4d3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file turbodrf-0.4.0-py3-none-any.whl.
File metadata
- Download URL: turbodrf-0.4.0-py3-none-any.whl
- Upload date:
- Size: 99.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50fb140efc9700f58bab4afec50ce3748d2c4c40d245d772393af74db6a824ca
|
|
| MD5 |
ae6c4307e685dce9bdda5f447c5a9033
|
|
| BLAKE2b-256 |
2d64e4df890f0d175eca7e4402fe61f18e8610e9f7a77c76e24433031c2dca63
|
Provenance
The following attestation bundles were made for turbodrf-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on AlexanderCollins/TurboDRF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
turbodrf-0.4.0-py3-none-any.whl -
Subject digest:
50fb140efc9700f58bab4afec50ce3748d2c4c40d245d772393af74db6a824ca - Sigstore transparency entry: 1440210431
- Sigstore integration time:
-
Permalink:
AlexanderCollins/TurboDRF@81bc93dfe4feb2d175a44cd20ef0f75b55d7c4d3 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/AlexanderCollins
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@81bc93dfe4feb2d175a44cd20ef0f75b55d7c4d3 -
Trigger Event:
release
-
Statement type: