Generate the Django REST Framework boilerplate you'd otherwise type by hand —
admin.py, serializers.py, views.py and urls.py — from the models you
already wrote.
It reads your models one of two ways:
- Standalone, parsing
models.pywith Python'sastmodule. No Django install, no settings, no project needed — point it at any file. - As a management command, introspecting your live app registry. More accurate, because Django itself resolves inheritance and relations for you.
Both paths feed the same templates, so you get the same output either way.
pip install django-drf-file-generatorStandalone, against a file:
drf_gen -m myapp/models.py --all --out myapp/Or from inside a Django project, against an installed app:
python manage.py drfgen myappThen wire the generated router into your project's URLs:
urlpatterns = [path("api/", include("myapp.urls"))]Given this models.py:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=255)
email = models.EmailField(blank=True)
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField("Tag", blank=True)
metadata = models.JSONField(default=dict)
created = models.DateTimeField(auto_now_add=True)you get serializers.py:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ["id", "title", "author", "tags", "metadata", "created"]
read_only_fields = ["id", "created"]views.py:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
filterset_fields = ["title", "author", "tags", "created"]
search_fields = ["title"]
ordering_fields = ["id", "title", "created"]
ordering = ["id"]admin.py:
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ["id", "title", "author", "metadata", "created"]
list_filter = ["author", "created"]
search_fields = ["title"]
ordering = ["id"]and urls.py:
router = routers.DefaultRouter()
router.register(r"authors", AuthorViewSet, basename="authors")
router.register(r"books", BookViewSet, basename="books")
urlpatterns = [path("", include(router.urls))]A few things the generator gets right that are easy to get wrong by hand:
tagsis missing fromlist_display. AManyToManyFieldthere raisesadmin.E109at system-check time, which stops the whole site from booting.metadatais missing fromfilterset_fields. django-filter has no filter forJSONFieldand blows up on the first request to that endpoint. Same forBinaryField,FileFieldandImageField.createdandidare read-only. Primary keys,auto_now/auto_now_addfields, and anything witheditable=Falseshould never be client-writable.- Fields inherited from abstract base classes are included, and abstract models themselves are never emitted.
- An explicit
primary_key=Truefield is respected — no phantomid. - A model called
Tagsroutes at/tags/, not/tagss/.
The generated files pass ruff format --check unchanged, so regenerating won't
churn your diff.
| Flag | What it does |
|---|---|
-m, --model PATH |
the models.py to read (standalone only) |
-o, --out DIR |
where to write (default: drf_gen_build; the app's own directory for the management command) |
-a, --admin |
generate admin.py |
-s, --serializers |
generate serializers.py |
-v, --views |
generate views.py (--views in the management command) |
-u, --urls |
generate urls.py |
-A, --all |
generate all four |
-f, --force |
overwrite existing files |
--dry-run |
report what would be written |
--stdout |
print instead of writing |
--template-dir DIR |
use your own templates |
Re-running is safe and takes one step. Files still carrying the generated-by
header are refreshed automatically; anything else is left alone and reported.
Start editing a generated file (or just delete the header) and it becomes yours
— the generator won't touch it again without --force.
The one time you need --force up front is on a freshly created app, because
manage.py startapp leaves its own stub admin.py and views.py behind:
python manage.py drfgen myapp --all --forceThe four built-in templates are Jinja2 files. Copy the one you want to change
and point --template-dir at your copy — the rest still come from the package:
python -c "import drf_gen, pathlib; print(pathlib.Path(drf_gen.__file__).parent / 'templates')"
cp .../templates/serializers.py.jinja mytemplates/
drf_gen -m myapp/models.py --all --template-dir mytemplates/ --out myapp/Each template receives models (a list of ModelSpec), plus model_names,
serializer_names and viewset_names. A ModelSpec exposes name, route,
pk_name, fields, and the ready-made selections used above:
field_names, read_only_field_names, list_display, list_filter,
filterset_fields, search_fields, ordering_fields. See
drf_gen/ir.py.
- Python 3.9+
- Jinja2 (installed automatically)
- For the management command: Django 4.2+
- For the generated code: Django 4.2+, Django REST Framework 3.14+, and django-filter if any model has filterable fields
1.0 is a rewrite. The old version only ran on Python 2 and generated code for Django 1.8. What changed:
- Python 3.9+ only; the generated code targets Django 4.2+ / DRF 3.14+.
- Field types are found by parsing, not by matching the first letter of the type
name — so
JSONField,ManyToManyField,SlugFieldand others no longer disappear from the output. - The generated
UserView,PasswordResetandObtainAuthTokenare gone. They depended onrest_authand on amodels.Perfilfrom one specific private project. Bring your own auth. - The tool no longer edits your
models.py. 0.x appended ahack_models()monkeypatch tocore/models.pyon every run. If you have one, delete it. config.iniis gone; use--out.-D/--Delete, whichrmtree'd the output directory, is gone. Existing files are now protected by default instead.- Viewsets are named
<Model>ViewSet, not<Model>View.
See CONTRIBUTING.md. Bug reports and pull requests welcome.
MIT — see LICENSE.rst.
Written by Alexandre Proença.