| name | turbodrf |
| description | TurboDRF - fast Django REST framework with automatic OpenAPI, serializers, views, routers, and caching |
| metadata | {"author":"mte90","version":"1.1.0","tags":["python","django","rest-api","openapi","serializers","caching"]} |
turbodrf
TurboDRF - Dead simple Django REST API generator with role-based permissions
Turn your Django models into fully-featured REST APIs with a mixin and a configuration method. Zero boilerplate.
Overview
TurboDRF is a Django REST Framework mixin-based library that automatically generates CRUD API endpoints for your models. Unlike traditional DRF setups requiring ViewSets and serializers, TurboDRF uses a simple mixin pattern where you declare your model inherits from TurboDRFMixin and define a turbodrf() configuration method.
Key Features:
- Automatic CRUD endpoints from model declaration
- Role-based access control (RBAC)
- Field-level permissions
- Built-in search, filtering, ordering, and pagination
- Nested field support for relationships
- Client-side field selection (
?fields=)
- Auto-generated API documentation (Swagger UI, ReDoc)
- Performance optimizations with compiled read path
- Security: sensitive fields deny-list, FK injection defense, startup safety gates
Installation
PyPI
pip install turbodrf
pip install turbodrf[fast]
GitHub
pip install git+https://github.com/AlexanderCollins/TurboDRF.git
Requirements
- Python >=3.10 (tested: 3.10, 3.11, 3.12, 3.13, 3.14)
- Django >=4.2 (tested: 4.2, 5.2, 6.0)
- Django REST Framework >=3.14.0
- drf-yasg >=1.21.0, django-filter >=23.0
- Optional extras:
turbodrf[fast] (msgspec/orjson ~7x faster JSON), turbodrf[allauth] (django-allauth >=0.57.0)
Verified against TurboDRF v0.5.1 (2026-07-12).
Quick Start
1. Add to INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_filters',
'turbodrf',
'myapp',
]
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.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
published_date = models.DateField()
searchable_fields = ['title', 'author']
@classmethod
def turbodrf(cls):
return {
'fields': ['title', 'author', 'price', 'published_date']
}
3. Add the router
from django.contrib import admin
from django.urls import path, include
from turbodrf.router import TurboDRFRouter
router = TurboDRFRouter()
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
4. Configure TurboDRF roles
TURBODRF_ROLES = {
'admin': [
'myapp.book.read',
'myapp.book.create',
'myapp.book.update',
'myapp.book.delete',
'myapp.book.price.read',
'myapp.book.price.write',
],
'editor': [
'myapp.book.read',
'myapp.book.update',
'myapp.book.price.read',
],
'viewer': [
'myapp.book.read',
]
}
5. Extend User Model with Roles
from django.apps import AppConfig
from django.contrib.auth import get_user_model
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
User = get_user_model()
def get_user_roles(self):
return [group.name for group in self.groups.all()]
if not hasattr(User, 'roles'):
User.add_to_class('roles', property(get_user_roles))
Done! You now have a full REST API at /api/ with:
GET /api/books/ # List all books
POST /api/books/ # Create a new book
GET /api/books/1/ # Get a specific book
PUT /api/books/1/ # Update a book
DELETE /api/books/1/ # Delete a book
Query parameters:
GET /api/books/?search=django # Search
GET /api/books/?author__name=Smith # Filter
GET /api/books/?ordering=-price # Order
GET /api/books/?page=2&page_size=10 # Paginate
GET /api/books/?fields=title,price # Client field selection
Model Configuration
Basic Configuration
@classmethod
def turbodrf(cls):
return {
'enabled': True,
'endpoint': 'books',
'fields': ['title', 'author'],
'public_access': False,
'lookup_field': 'pk',
'compiled': True,
}
Fields Specification
All database fields:
'fields': '__all__'
Specific fields (same for list and detail):
'fields': ['title', 'author', 'price']
Different fields for list vs detail:
'fields': {
'list': ['title', 'author', 'price'],
'detail': ['title', 'description', 'author', 'author__email', 'price']
}
Nested Fields
Access related model fields with __ notation:
'fields': [
'title',
'author__name',
'author__publisher__name',
'tags__name',
]
FK fields are flattened in responses (author__name becomes author_name). M2M fields are arrays of objects:
{
"title": "Django for APIs",
"author_name": "William Vincent",
"tags": [{"name": "Python"}, {"name": "Django"}]
}
Maximum nesting depth is 3 by default. Change with TURBODRF_MAX_NESTING_DEPTH in settings.
Property Fields
Model @property methods work in the compiled path:
class Book(models.Model, TurboDRFMixin):
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
@property
def display_title(self):
return self.title.upper()
@classmethod
def turbodrf(cls):
return {
'fields': ['title', 'price', 'display_title']
}
Properties that access related objects (e.g., self.author.name) won't work in the compiled path — use author__name in the field config instead.
List/Detail Field Separation
class Book(models.Model, TurboDRFMixin):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
@classmethod
def turbodrf(cls):
return {
'fields': {
'list': ['title', 'author', 'price'],
'detail': ['title', 'author', 'description', 'price']
}
}
Permissions
Permission Modes
TurboDRF supports three permission modes:
-
No permissions (development):
TURBODRF_DISABLE_PERMISSIONS = True
-
Django default permissions:
TURBODRF_USE_DEFAULT_PERMISSIONS = True
-
Role-based permissions (default):
TURBODRF_ROLES = {
'admin': [
'myapp.book.read',
'myapp.book.create',
'myapp.book.update',
'myapp.book.delete',
'myapp.book.price.read',
'myapp.book.price.write',
],
'editor': [
'myapp.book.read',
'myapp.book.update',
'myapp.book.price.read',
],
'viewer': [
'myapp.book.read',
]
}
Permission Format
- Model-level:
app_label.model_name.action (read, create, update, delete)
- Field-level:
app_label.model_name.field_name.read or .write
Field Permissions
- If ANY role defines an explicit field rule (e.g.,
price.read), that field requires explicit permission for ALL roles
- Fields without explicit rules fall back to model-level permission
- To restrict
price for viewers, add price.read to at least one role (like admin)
How It Works
TurboDRF reads user.roles — a property that returns a list of role names:
User.add_to_class('roles', property(lambda self: [g.name for g in self.groups.all()]))
class User(AbstractUser):
user_roles = models.JSONField(default=list)
@property
def roles(self):
return self.user_roles
Authenticated users with no roles get 403 on all endpoints.
Database-Backed Permissions
For runtime changes without redeployment:
TURBODRF_PERMISSION_MODE = 'database'
TURBODRF_PERMISSION_CACHE_TIMEOUT = 300
from turbodrf.models import TurboDRFRole, RolePermission, UserRole
role = TurboDRFRole.objects.create(name='editor')
RolePermission.objects.create(role=role, app_label='books', model_name='book', action='read')
UserRole.objects.create(user=user, role=role)
Nested Field Permissions
Permissions are checked at each level of a nested field path. For author__publisher__name:
- Can user read
author on Book?
- Can user read
publisher on Author?
- Can user read
name on Publisher?
If any level fails, the field is excluded.
Filter Permissions
Users can only filter on fields they have read permission for. Filters on hidden fields are silently ignored.
Custom Actions (v0.5.0)
TurboDRF supports custom actions attached to a model's config, inheriting tenant and predicate scoping:
from turbodrf.decorators import turbodrf_action
class Book(models.Model, TurboDRFMixin):
@classmethod
def turbodrf(cls):
return {
'actions': [
turbodrf_action(detail=True, methods=["post"], url_path="resend")(cls.resend)
],
}
def resend(self, request):
...
Read-Only and HTTP Method Control (v0.5.0)
read_only: True — restricts a model to GET endpoints only
http_methods: ['list', 'retrieve'] — whitelist specific HTTP methods
full_clean: True — runs model.full_clean() before save
Computed Fields on Both Read Paths (v0.5.0)
@property fields work on both the standard and compiled read paths via DictProxy. Models with unsupported fields (GenericFK) auto-fallback to the serializer path.
Strict Swagger Role Preview (v0.5.0)
The ?role= query parameter is honored in Swagger only for roles the caller actually holds. A user cannot preview another role's permissions.
Tenancy & Row-Level Access
Multi-Tenant SaaS
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',
'owner_field': 'owner',
'bypass_owner_roles': ['manager', 'admin'],
'fields': ['title', 'workspace', 'owner'],
}
```python
TURBODRF_TENANT_MODEL = 'accounts.Workspace'
TURBODRF_TENANT_USER_FIELD = 'workspace'
Request Flow
A request GET /api/projects/ from Alice (member at ABC workspace) goes through:
- Permission gate — Alice's role
member has app.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, owner but maybe not all configured fields. Hidden ones are removed from the response.
Quick Recipes
{'tenant_field': 'store', 'owner_field': 'customer', 'bypass_owner_roles': ['staff']}
{'owner_field': 'author', 'bypass_owner_roles': ['admin']}
{'tenancy': 'shared'}
{'visibility': [Tenant('workspace'), Members('participants')]}
{'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.
Full Predicate Vocabulary (v0.4.0+)
Predicate primitives in turbodrf.predicates:
Tenant(field='tenant') — mandatory tenant boundary, applied as AND outside the algebra (OR-composition can never escape it)
Owner(field='owner') — row belongs to request.user
Members(field='members') — row's M2M members contains user (read-only: raises NotImplementedError on writes)
Group(field='group') — row's group field matches user's group (read-only: raises NotImplementedError on writes)
Either(left, right) — logical OR of two predicates
Conditional(q_func, write_validator=...) — custom predicate; write_validator is mandatory (read-only if omitted: raises NotImplementedError on writes)
Custom(q_func, write_validator=...) — fully custom; write_validator required
Tenant() inside Either() is prohibited — the tenant boundary must not be OR-able away.
Security
Sensitive Fields
Fields like password, token, and secret_key are never exposed:
TURBODRF_SENSITIVE_FIELDS = [
'password', 'password_hash', 'secret_key', 'api_key',
'token', 'access_token', 'refresh_token', 'session_key',
]
Fail-Closed Design
If a permission check fails due to an error, access is denied. TurboDRF never grants access on exception.
Error Responses
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'turbodrf.exceptions.turbodrf_exception_handler',
}
{
"error": {
"status": 403,
"code": "permission_denied",
"message": "You do not have permission to perform this action."
}
}
Security Gates
TurboDRF runs 5 startup safety passes; each has a kill-switch setting:
- Tenancy declaration — every model declares
tenant_field/visibility/tenancy: 'shared' (kill-switch: TURBODRF_REQUIRE_TENANCY=False)
- Compiled-path FK safety — validates FK annotations can't bypass predicates (kill-switch:
TURBODRF_ALLOW_UNSAFE_COMPILED_FK)
- Compiled-path M2M safety — validates M2M target traversal (kill-switch:
TURBODRF_ALLOW_UNSAFE_COMPILED_M2M)
- Filter traversal safety — validates searchable fields can't leak via filter joins (kill-switch:
TURBODRF_ALLOW_UNSAFE_FILTER_TRAVERSAL)
- Custom predicate write-safety —
Custom/Conditional must carry explicit write_validator (kill-switch: TURBODRF_ALLOW_UNSAFE_CUSTOM_WRITE)
- Permission string typo check — catches typos in
TURBODRF_ROLES with difflib suggestions (kill-switch: TURBODRF_ALLOW_UNKNOWN_PERMISSIONS)
Additional protections:
Members, Group, and Conditional predicates raise NotImplementedError on writes (read-only enforcement)
TURBODRF_MAX_FILTER_VALUE_LENGTH (default 1000) caps filter value length
TURBODRF_LOG_UNRESTRICTED_CUSTOM (default True) logs when unrestricted custom predicates run
- FK injection defense: every FK in create/update bodies validated against the target's predicate stack; cross-tenant targets return 400 indistinguishable from nonexistent
These gates refuse to boot if unsafe configurations are detected, preventing cross-permission read leaks.
Documentation
Auto-generated Swagger UI and ReDoc:
- Swagger UI:
/api/swagger/
- ReDoc:
/api/redoc/
Disable in production:
TURBODRF_ENABLE_DOCS = False
Management Commands
python manage.py turbodrf_check
python manage.py turbodrf_benchmark
python manage.py turbodrf_explain
Settings Reference
Key settings:
| Setting | Description |
|---|
TURBODRF_ROLES | Role-based permissions dict |
TURBODRF_DISABLE_PERMISSIONS | Disable all permissions |
TURBODRF_USE_DEFAULT_PERMISSIONS | Use Django default permissions |
TURBODRF_PERMISSION_MODE | 'static' or 'database' |
TURBODRF_PERMISSION_CACHE_TIMEOUT | Permission cache TTL (seconds) |
TURBODRF_TENANT_MODEL | Default tenant model |
TURBODRF_TENANT_USER_FIELD | User's tenant field |
TURBODRF_SENSITIVE_FIELDS | Fields to hide from all users |
TURBODRF_ENABLE_DOCS | Enable Swagger/ReDoc |
TURBODRF_MAX_NESTING_DEPTH | Max nested field depth |
TURBODRF_USE_FILTERS | Enable Django filters |
TURBODRF_DEFAULT_PAGE_SIZE | Default pagination page size |
TURBODRF_REQUIRE_TENANCY | Force tenancy declaration on all models (default True) |
TURBODRF_ALLOW_UNSAFE_COMPILED_FK | Kill-switch for compiled FK safety gate |
TURBODRF_ALLOW_UNSAFE_COMPILED_M2M | Kill-switch for compiled M2M safety gate |
TURBODRF_ALLOW_UNSAFE_FILTER_TRAVERSAL | Kill-switch for filter traversal safety gate |
TURBODRF_ALLOW_UNSAFE_CUSTOM_WRITE | Kill-switch for custom predicate write-safety gate |
TURBODRF_ALLOW_UNKNOWN_PERMISSIONS | Kill-switch for permission string typo check |
TURBODRF_MAX_FILTER_VALUE_LENGTH | Max filter value length (default 1000) |
TURBODRF_LOG_UNRESTRICTED_CUSTOM | Log unrestricted custom predicates (default True) |
Examples
Complete CRUD API
models.py:
from django.db import models
from turbodrf.mixins import TurboDRFMixin
class Author(models.Model, TurboDRFMixin):
name = models.CharField(max_length=100)
email = models.EmailField()
@classmethod
def turbodrf(cls):
return {
'fields': ['name', 'email']
}
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)
@classmethod
def turbodrf(cls):
return {
'fields': {
'list': ['title', 'author__name'],
'detail': ['title', 'author__name', 'author__email', 'price']
}
}
settings.py:
INSTALLED_APPS = [
'rest_framework',
'turbodrf',
'myapp',
]
TURBODRF_ROLES = {
'admin': [
'myapp.book.read',
'myapp.book.create',
'myapp.book.update',
'myapp.book.delete',
'myapp.book.price.read',
'myapp.book.price.write',
],
'editor': [
'myapp.book.read',
'myapp.book.update',
'myapp.book.price.read',
],
'viewer': [
'myapp.book.read',
]
}
urls.py:
from django.contrib import admin
from django.urls import path, include
from turbodrf.router import TurboDRFRouter
router = TurboDRFRouter()
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
Best Practices
1. Use Meta Options
Define fields explicitly rather than __all__ for better control.
2. Validate Input
Use Django's form validation or custom validators:
Integrations
TurboDRF ships optional, experimental integrations (all settings-gated, marked experimental in docs):
- Sentry — security-event breadcrumbs
- Keycloak — role mapping (
STRICT_ROLES=True default)
- django-allauth — group→role mapping (
pip install turbodrf[allauth])
- drf-api-tracking — request logging
Fast JSON: pip install turbodrf[fast] adds msgspec (~7x faster serialization; orjson auto-detected if present).
AI Agent Guidance
The TurboDRF repository ships an AGENTS.md with canonical guidance for AI coding agents — the "what never to do" list (don't override get_queryset, don't use Model.objects.all() in custom actions, don't widen serializer fields, don't use tenancy: 'shared' as a test workaround) plus invalidate_user_permissions() cache API and testing patterns.
Docs status: The readthedocs and GitHub Pages sites are currently 404. The repo docs/ folder is the only current documentation source.
References
### 3. Use Permissions
Restrict access appropriately:
```python
TURBODRF_ROLES = {
'public': ['myapp.book.read'],
'staff': [
'myapp.book.read',
'myapp.book.create',
'myapp.book.update',
'myapp.book.delete',
],
}
4. Filter Usage
Users can only filter on fields they have read permission for.
5. Secure Sensitive Data
Always include sensitive fields in the deny-list:
TURBODRF_SENSITIVE_FIELDS = [
'password', 'token', 'api_key', 'secret_key',
]
Troubleshooting
Import Errors
If you get import errors, ensure TurboDRF is properly installed:
pip list | grep turbodrf
No API Endpoints
If no endpoints appear, check that:
- Your models inherit from
TurboDRFMixin
- Models have a
turbodrf() classmethod
- The model is not disabled (
'enabled': False)
Permission Denied
If you get 403 errors:
- Check your user's roles
- Verify role permissions in
TURBODRF_ROLES
- Ensure the User model has a
roles property
Compiled Path Safety Issues
If startup gate fires due to M2M/FK traversals:
- Drop the path from the parent's
turbodrf() fields list
- Set
'compiled': False on the parent model
- Strip the target's row-level rules if genuinely public
TURBODRF_ALLOW_UNSAFE_COMPILED_M2M = True (not recommended)
References