Contribution guidelines¶
Who is this for: anyone sending a change to django-db-tenants.
Checklist for every change¶
- Discuss larger changes in an issue first.
- Work on a branch (Git workflow).
- Add or update tests; they must pass on SQLite, PostgreSQL and MySQL (Testing strategy).
- Update the pages of this guide that describe what you changed, in the same change (see Keep the docs in sync).
- Run
uv run tox -p auto, or at leastuv run tox -e py313-dj52-sqlite,lint,type,docs(CI/CD and build).
Code standards¶
| Topic | Rule | Enforced by |
|---|---|---|
| Python | Code must run on Python 3.11 and newer (requires-python, ruff target-version = "py311") |
ruff, tox matrix |
| Django | Code must run on Django 4.2, 5.2, 6.0 and 6.1 | tox matrix, django-upgrade --target-version 4.2 |
| Databases | Features must work on SQLite, PostgreSQL and MySQL, or fail with a clear error (as TemplateOverlay.create_database does with NotImplementedError) |
tox matrix |
| Style | ruff format, line length 120; rules E, W, F, I, B, UP, DJ, BLE, C4, PIE, PT, SIM, RET, T20, RUF, S | ruff.toml, pre-commit |
| Imports | isort order with django_db_tenants, testutils, demo, notes as first-party |
ruff I |
| Types | The package ships py.typed; new public functions get type hints |
ty check |
| Logging | Use the module logger (logging.getLogger(__name__)), never print |
ruff T20 |
| Settings | Every option goes through DB_TENANTS and conf.app_settings; no new top-level Django settings |
review |
| Security | Secrets in models use EncryptedTextField or EncryptedJSONField |
ruff S, review |
Silence a lint rule only on the line that needs it, with the rule code and a reason: # noqa: BLE001 - report per tenant and continue.
Recipes¶
Change a model¶
- Edit
src/django_db_tenants/models.py. -
Generate the migration from the demo project, which has the library installed:
cd tests/demo && uv run python manage.py makemigrations django_db_tenants -
Update the Model reference page and the data-model diagram.
tests/test_migrations.pyfails until the migration exists.
Remember that library models live in the shared database only; they must not be referenced by foreign keys from tenant apps.
Add a setting¶
- Add the key and its default, with a comment, to
DEFAULTSinconf.py. If the value is a dotted path to a callable, add the key to theimport_stringsgiven toAppSettings(app_settings = AppSettings(DEFAULTS, frozenset({...}))). - Read it with
app_settings.<KEY>at the point of use (not at import time), sooverride_settingsworks in tests. - Test both the default and an override.
- Add a row to the Settings reference.
Add a resolver¶
- Add a function
(request) -> Client | Nonetoresolvers.py. Use the cached lookups inregistry.py; raiseTenantNotFound/TenantInactiveto refuse a request. - Test it through the middleware with
override_settings(DB_TENANTS={..., "RESOLVER": ...}), astest_middleware.pydoes forresolve_by_header. - Document it on the Resolvers page.
Add a management command¶
- Add
management/commands/<name>.pywith aCommand(BaseCommand); give every argument ahelptext. - Query library models with
.using(app_settings.TEMPLATE_ALIAS); switch tenants withtenant_context()orfor_each_tenant(). - Make failures visible in the exit status, as
migrate_tenantsdoes. - Test it with
call_command(seetest_provisioning.py). - Add a page to the CLI reference (
docs/development-guide/cli/<name>.md) and an entry tonavinzensical.toml(thezensical-navhook fails without it).
Add a database strategy¶
- Subclass
DatabaseStrategy(orTemplateOverlay) instrategies.pyfor a built-in one, or in your project for a custom one. Declarelabelandparam_keys; implementconnection_settings(), andcreate_database()/drop_database()if it supports provisioning (the contract). - Built-in:
registry.register(...)it instrategies.py. Custom: list it inDB_TENANTS["DATABASE_STRATEGIES"]. - Test it like
test_strategies.pydoes: a strategy used by tests must be importable by dotted path (put it intestutils/strategies.py). - Document it on Database strategies.
Registering a strategy needs no migration.
Support another database backend¶
- Add a branch for the vendor to
TemplateOverlay.create_database()anddrop_database()instrategies.py. - Teach
tests/_extra/testutils/tenants.pyto create, clone and drop databases on it, andtestutils/databases.pyto configure it fromTEST_<BACKEND>_*variables. - Add a service to
docker-compose.yml, variables to.env.example, a factor totox.ini, atest-<backend>job with a service container to.github/workflows/ci.yml, and an extra topyproject.toml. - Update Database strategies and Local environment setup.
Add to the public API¶
- Implement the function in its module, with type hints and a docstring: the docstring is published on the module's API page.
- If it belongs to the stable surface, import it in
src/django_db_tenants/__init__.pyand list it in the module docstring there. - Document it on the module's page in API reference; for a new module, add a page
docs/development-guide/api/<module>.mdwith adjango_db_tenants.<module>block, an entry tonavinzensical.toml(thezensical-navhook fails without it), and a row in the module table of the API reference.
Keep the docs in sync¶
| You changed | Update |
|---|---|
| A model or field | Model reference page, data-model diagram, migration |
A DB_TENANTS key |
Settings |
A database strategy, or db_params keys |
Database strategies, Client |
| A management command or option | CLI reference page |
| A public function or class | its API reference page (docstring and text) |
| Request flow, routing, caching, registration | Architecture |
| Test fixtures or utilities | Testing strategy |
| The tox matrix | tox.ini and .github/workflows/ci.yml, Testing strategy |
| Tooling, tox, hooks, dependencies | Local environment setup, CI/CD and build, Git workflow |
| A new term | Glossary |
Link to other pages with relative site paths ending in / (for example ../api/settings/), not with .md file names.
Release a version¶
Maintainers must follow the release process described in RELEASE.md, at the root of the repository.
Next steps¶
Return to the Development Guide index.