Self-hosted service
wemake-services/wemake-django-template avatar
wemake-services/wemake-django-template

wemake-django-template: a cookiecutter opinion factory for Django services

Bleeding edge django template focused on code quality and security.

2,275 stars223 forksPythonMIT

At a glance

What is it?
The template that treats your next Django service as a lint, typing and CI problem before it is a models and views problem.
Who is it for?
wemake-django-template is best understood as a set of defaults rather than a framework. It hands you a typed, linted, containerised Django service with a Caddy front and a GitLab pipeline already written, which removes an entire category of early project decisions.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 8, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A project generator wearing a template's name

There is a specific kind of confusion worth naming early. The repository description says it is a django template, and the README opens by calling itself a bleeding edge `django6.1` template. It is neither, in the way that word is normally used. It is a cookiecutter template: a directory of Jinja-templated files plus a `cookiecutter.json` manifest that expands into a fresh project on your machine.

The difference matters because nothing is installed and nothing is imported. There is no package to add to `INSTALLED_APPS`, no Django app, and no runtime dependency on the template itself. You run a command, answer a few prompts, and the generator writes a directory that is yours to edit from the first commit. The generated code does not depend on wemake-services in any way at runtime, which is the single strongest argument for the approach.

The README states the purpose plainly: this project is used to scaffold a `django` project structure, just like `django-admin.py startproject` but better. That comparison is fair about the shape of the output. The difference is everything `startproject` omits. Where Django gives you a settings module and a manage.py, this gives you a configured service with typing, tests, containers, a reverse proxy, documentation and a CI pipeline. The generated layout lives under a directory literally named `{{cookiecutter.project_name}}`, which is the cookiecutter convention for marking where substitution happens.

The repository tree makes the generator nature obvious. Alongside the templated project directory you will find `cookiecutter.json`, a `hooks/` directory for cookiecutter lifecycle scripts, and a `tests/` directory. The tests here test the generator, not the Django project it produces. That is a signal many projects skip entirely, and it is a reasonable one to look for when you are deciding whether a template will be maintained as software rather than left to rot.

The toolchain, and why each piece is there

The feature list reads like a shopping list, but the grouping tells you what the authors consider non-negotiable. Static analysis appears twice: `mypy` with `django-stubs` for types, and `ruff` with `wemake-python-styleguide` for linting. Tests appear as `pytest` and `hypothesis`, which is a notable pairing since Hypothesis generates property-based cases rather than hand-written examples. Packaging and containers follow, with `poetry@2` for dependencies and `docker` covering development, testing and production in one description.

The last two entries are where this template separates itself from most Django boilerplate. `sphinx` is included for documentation, and `Caddy` is turned on with automatic `https` and `http/2` by default. Shipping a reverse proxy in the scaffold means TLS termination is a solved problem on day one rather than an infrastructure ticket later. GitLab CI comes with `build`, `test` and `deploy` stages already configured.

The pyproject.toml reveals the maintainers dogfood their own tools, and not only on the generated project. The template's own dependency set includes `wemake-python-styleguide`, `ruff`, `mypy`, `pytest`, `pytest-randomly`, `pytest-cookies`, `binaryornot` and `docker-image-size-limit`. The `pytest-cookies` entry is the one to note: it is a pytest plugin for testing cookiecutter templates, which means the generated output is exercised during CI rather than merely inspected.

The ruff configuration is unusually opinionated and that is the point. Line length is set to 80, quotes are forced to single style, and the rule selection spans flake8-builtins, flake8-bugbear, mccabe complexity limits, pydocstyle, eradicate for commented-out code, flake8-logging-format, pep8-naming, refurb and perflint. Both `target-version` and `requires-python` are pinned to the 3.13 line:

toml
requires-python = ">=3.13,<3.14"
toml
target-version = "py313"
line-length = 80

A narrower target range than 3.13 alone is worth pausing on. An upper bound means the generated project declares that it works on 3.13 and not 3.14, so the first thing to check when 3.14 lands is whether that ceiling has moved. The same narrowness shows up in the pinned dev dependency majors: ruff at `^0.16` and pytest at `^9.1` are recent enough that you should expect the dependabot badges at the top of the README to be doing real work.

Generating a project takes four commands

The installation path is short enough to quote in full. Cookiecutter needs a Jinja extension from git, which is why the recommended install injects `jinja2-git` rather than the released package:

bash
pipx install cookiecutter
pipx inject cookiecutter jinja2-git

A global pip install is offered as the alternative, and then one command creates the project:

bash
cookiecutter gh:wemake-services/wemake-django-template

The `gh:` prefix tells cookiecutter to resolve the template from GitHub, so the generator pulls the default branch at run time. That has a practical consequence worth stating plainly: you are not installing a pinned version of this template. The two commands above and the project you get are two different points in time, and the second one decides what lands in your repository. If version control provenance matters to your team, the sensible move is to generate, then immediately record which commit of the template produced the output, whether in a comment or a generation log.

Because the default branch is `master` and the repository recorded a push on 2026-09-28, generation is effectively a rolling target. That is a reasonable model for a starter whose whole purpose is being current on dependencies, and a poor fit if your organisation needs a scaffold it can reproduce byte for byte a year from now. The template offers no version flag and no tags to pin in the documented flow, which pushes the reproducibility problem onto you rather than solving it.

Cookiecutter will also prompt for the project name and whatever else `cookiecutter.json` declares. Those answers substitute into the `{{cookiecutter.project_name}}` directory name, into module paths, and into test configuration, which is why the generator's own tests exist.

Why the releases page will mislead you

Here is the sharpest gap between what the documentation claims and what the repository's own history records. The README says bleeding edge `django6.1` and supports latest `python3.13`. The newest published release is the tag `goodbye-3.9`, dated 2023-09-05, and its body reads in full: since this release we will use Python 3.11.

Both statements are true and they describe different things. Three years separate that release from the README's stated target. Read together, the picture is a project that moves continuously on its default branch and only occasionally marks a moment worth naming, and every one of those names is a farewell. The release history is a list of what the template stopped supporting: goodbye-pipenv in 2018, with a body explaining the switch to `poetry` and pointing at issue 549; django-one in 2019, marking the last Django 1.11 release before moving to the 2.2 LTS with a plain statement that two versions cannot be maintained; goodbye-3.9 in 2023, moving to Python 3.11.

So the releases are milestone tags about transitions, not shipped packages. That reading fits the version fields in pyproject.toml, which stay at a placeholder:

toml
version = "0.1.0"

and the project explicitly disables packaging:

toml
[tool.poetry]
package-mode = false

The same file carries a `Private :: Do Not Upload` classifier, which is Poetry's way of refusing to publish. Nothing about this repository is intended to reach an index. What gets versioned is your generated project, under your own policy.

Which fact should guide you depends on the question. If you want to know which Python and Django versions a generated project will use, the README and pyproject.toml answer it, and the releases page is noise. If you want to know when the template last changed its supported Python, the releases page is the only source, because the dependabot churn on the default branch leaves no other trace. Treat the two as complements rather than as a conflict to resolve.

What the template deliberately does not lint

The ruff configuration carries an exclusion that deserves more attention than it usually gets:

toml
extend-exclude = [
  '[{][{]cookiecutter.project_name[}][}]',
]

Every rule selected in that file applies to the generator, not to the Django project it produces. That is a coherent design decision, and there is a good reason for it: the generated directory is full of Jinja-templated source containing `{{ cookiecutter.x }}` placeholders that are not valid Python until substitution. Running pydocstyle or the mccabe complexity check across those files would produce noise on every single run.

It does mean the enforcement story has a seam. The style rules advertised in the feature list govern the tooling repository, while the code you actually ship inherits those rules only through the config files the generator copies into your project. Whether that transfer happens cleanly is the first thing to inspect in generated output: open the copied ruff and mypy configuration and confirm it references your module names, not the template's.

The same shape applies to the two config files sitting side by side in the tree. There is a `pyproject.toml` and there is a `setup.cfg`, both tracked in the default branch. Ruff, mypy and the build system are configured in the TOML file, so the setup.cfg is doing something else, most likely packaging-era metadata retained from the template's earlier days. Nothing in the README documents which file wins for which setting, so if you find a lint rule behaving unexpectedly, compare both before assuming a bug.

A third detail belongs here. The dev dependency on `docker-image-size-limit` means image size is measured somewhere in the template's own checks. Combined with Docker being described as covering production as well as development, the generated service is expected to ship as a container, and the team wanted a regression guard on its size. That is a small signal about how they treat the artifact, and it is a reasonable thing to inherit.

Who this fits, and who should look elsewhere

The README asks users to add themselves to a wiki list of adopters and points at a code search for real open source projects built on the template. The repository is not archived, carries 21 open issues, and recorded a push on 2026-09-28. Nothing about the maintenance posture reads as abandoned, and the presence of dependabot automation plus dependabot badges suggests dependency freshness is a standing commitment rather than an occasional sweep.

The fit is narrow and worth naming precisely. This is a service scaffold: it assumes you want an API-oriented Django application with typed models, a test suite, containerised local development, a reverse proxy that handles TLS, and CI that runs on GitLab. Teams on GitHub Actions, or teams who need server-rendered templates and admin-heavy CRUD rather than a REST surface, will find substantial configured machinery they do not use. The wemake-python-styleguide alone is a strong stylistic commitment, and it is opinionated in ways that generate real discussion in a codebase that did not grow up under it.

The dependency on a git-sourced Jinja extension is another friction point. It gives you current Jinja behaviour, and it means your scaffolding step reaches out to a version control host for a dependency that has no release tag to pin. In an environment with restricted egress, that step can fail in a way a normal pip install would not.

What you get if you fit the profile is a large reduction in early decisions. Container orchestration, reverse proxy configuration, documentation scaffolding, typing configuration and a deploy-capable pipeline all arrive written. What you accept is that you have adopted someone's opinion set wholesale, and the exclusion above shows that opinion does not automatically extend to your code until you verify it does.

Evaluating the scaffold without running the project

You can get a long way inspecting the template statically, and that inspection is where the useful decisions come from. Start with `pyproject.toml`, because it is the only file that states the toolchain as machine-readable constraints rather than prose. The requires-python range, the ruff target, the selected rule families and the dev dependency majors together define the generated project's real shape, and the README's feature bullets are a summary of that file rather than an independent claim.

Next, look at what the exclusions tell you. A generator that excludes its own output directory from linting is a generator that assumes its output will be linted separately, which points you toward checking whether the generated project ships its own configuration. The presence of both `pyproject.toml` and `setup.cfg` suggests some settings live outside the file the README would lead you to.

Finally, read the release bodies as changelogs of intent. Each one names a version being abandoned and the reason, and the pipenv to poetry note is the most informative because it explains a decision affecting every downstream project at once. That history is the closest thing the project offers to a rationale document, and it explains why poetry appears in the feature list rather than pipenv.

None of this substitutes for generating a project and reading it. It does mean the first thing you look at after generation will be a file you have already read the intent of, which is a better position than starting cold.

Editorial conclusion

wemake-django-template is best understood as a set of defaults rather than a framework. It hands you a typed, linted, containerised Django service with a Caddy front and a GitLab pipeline already written, which removes an entire category of early project decisions. The parts to inspect before adopting are the ones the release list hides: the project targets Python 3.13 and Django 6.1 in the README and pyproject.toml, while the newest tagged release is from 2023 and announces Python 3.11. Read the template's own dependency files and rendered output, not the releases page, and you will know exactly what you are starting from.

Frequently asked questions

Does wemake-django-template target the latest Django and Python versions?

The README claims a bleeding edge django6.1 template supporting python3.13, and the pyproject.toml pins requires-python to >=3.13,<3.14 with a ruff target of py313. The newest published release, however, is the goodbye-3.9 tag from 2023-09-05, which announced a move to Python 3.11. Both are accurate about different things: the README and pyproject describe the current default branch, while the releases record version transitions as milestone tags. Check the dependency files in your generated output, not the releases page.

Is the generated Django project available on PyPI as a package?

No, and the repository is explicit about it. pyproject.toml sets version to 0.1.0, declares [tool.poetry] with package-mode = false, and includes a Private :: Do Not Upload classifier, which is Poetry's mechanism for refusing to publish. The repository itself is a cookiecutter template you run locally with the cookiecutter gh:wemake-services/wemake-django-template command. Only your generated project is versioned, and it is versioned by you under your own policy.

Why do the generated files not get linted by the template's own ruff configuration?

The ruff config in pyproject.toml lists extend-exclude with the pattern [{][{]cookiecutter.project_name[}][}], which matches the templated project directory. The reason is mechanical rather than philosophical: those files contain Jinja placeholders that are not valid Python until cookiecutter substitutes them, so every rule would fire on unrendered source. The practical consequence is that the advertised ruff and wemake-python-styleguide rules govern the generator itself, and your generated project only inherits them if the generator copies a working config into your project. Verify that after generating.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. wemake-services/wemake-django-template on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wemake-services-wemake-django-template.svg)](https://hysenlabs.com/projects/wemake-services-wemake-django-template)