vintasend-django-templates-manager
The Django storage backend for
vintasend-managed-templates: a
DjangoTemplateManager implementing the storage seam, the models behind it, and an admin for the
people who edit the copy.
vintasend-managed-templates defines what a managed template is — versions, a
draft/active/inactive/archived lifecycle with an audit trail, tags, filtering, and composition. It
deliberately does not say where any of it lives. This package puts it in your Django database.
Install
poetry add vintasend-django-templates-manager
# or
pip install vintasend-django-templates-manager
Python 3.12–3.14, Django 4.2–6.0.
Add the app and migrate:
# settings.py
INSTALLED_APPS = [
...,
"vintasend_django_templates_manager",
]
python manage.py migrate
Wiring it up
DjangoTemplateManager is the backend; hand it to the renderer and the service from
vintasend-managed-templates:
from vintasend_managed_templates.managed_template_renderer import ManagedTemplateEmailRenderer
from vintasend_managed_templates.managed_template_service import ManagedTemplateService
from vintasend_django_templates_manager.django_templates_manager import DjangoTemplateManager
manager_backend = DjangoTemplateManager()
renderer = ManagedTemplateEmailRenderer(manager_backend, inner_renderer)
service = ManagedTemplateService(manager_backend, renderer)
inner_renderer is any vintasend email renderer. It receives template source rather than a
template name, so a renderer that resolves names through a loader —
DjangoTemplatedEmailRenderer included — needs a loader that accepts source. See
What the inner renderer has to do.
Everything else — creating templates, publishing versions, tagging, filtering — is
ManagedTemplateService's API, unchanged.
What is stored
| Model | What it holds |
|---|---|
ManagedTemplate |
One version of a template. (key, version) is unique, and a key has as many rows as it has versions. Nothing about a row changes once it exists except its status, its tags, and the derived is_abstract flag. |
ManagedTemplateStatusRecord |
The audit trail: who moved a version to which status, and when. |
ManagedTemplateTag |
A label shared across templates, identified by the slug vintasend_managed_templates.tags.slugify_tag derives from its text. |
Versions are the reason a published template can never change under a notification that already
referenced it: update_template inserts the next version and leaves its predecessor exactly as it
was, content, status and history alike.
The admin
All three models are registered.
- Templates —
keyandversionlock once the row exists, since they are its identity. Every status change made here is written to the audit trail, which is inlined read-only on the page. - Tags — the slug is derived from the text on save rather than typed in, and collides safely
(
black-friday-2). Archive and restore are bulk actions; archiving retires a tag from the pickers without severing it from the templates carrying it. - Status history — browsable, never editable.
Composition
A template stored here can build on another one: extend a base, fill its hole, override its blocks, splice in a shared fragment. All of it is resolved before the template engine runs, so what Django's engine receives is one flat string with its own syntax untouched.
base-email <html><body>
{% managed_block header %}<h1>Acme</h1>{% managed_endblock %}
{% managed_children %}
{% managed_include "footer" %}
</body></html>
welcome {% managed_extends "base-email" %}
{% managed_block header %}<h1>Welcome!</h1>{% managed_endblock %}
<p>Hi {{ name }}, welcome aboard.</p>
The tag language, the abstract-template rule and the version semantics are
vintasend-managed-templates': see
Composition.
Composition itself is read off the template source — there is no separate model for it, no join,
and nothing to keep in step except the one flag below.
What this package adds is the editing side:
- The admin refuses what will not compose. Saving a template runs every one of its three
sources through the composer against the database. A base that does not exist, a block left
open, a chain of bases that loops back to the row being edited — each becomes an error on the
field that carries it, rather than a notification that fails to send days later. Set
validate_composition = Falseon aManagedTemplateAdminFormsubclass to save a template whose base has not been written yet. - The change form previews the result. A read-only composed body under the content fields shows what the engine will actually receive, resolved against whatever the referenced templates are now.
- The changelist marks the bases, and filters on them, through the stored flag below.
is_abstract
Whether a template is a base to build on rather than one to send is a fact about its source. It is also the thing a "pick a template" screen most needs to filter on, so it is denormalized onto a column:
ManagedTemplate.objects.sendable() # what a picker should offer
ManagedTemplate.objects.abstract() # the bases
service.get_filtered_templates({"is_abstract": False}) # the same, through the library
ManagedTemplate.save() derives it, so every write path keeps it honest — the admin,
DjangoTemplateManager, a data migration, a shell session. It is editable=False: nobody types it
in, and a template whose composition tags are malformed saves as concrete rather than blowing up a
write (the admin form has already refused that template anyway).
To recompute rather than trust the column — for a row edited in memory, or one written before the column existed:
from vintasend_django_templates_manager.composition import template_is_abstract
template_is_abstract(template) # raises on a malformed tag
template_is_abstract(template, strict=False) # reads an unparseable template as concrete
Composition resolves references through this backend, which means an unpinned {% managed_extends "base-email" %} picks up the latest version of that key, draft included — the same rule
get_template(key) follows everywhere else. Pin it with version=2 when a template has to keep
composing against an exact base.
Pinning the notification rather than the base is vintasend's job, through
requested_template_version — see
Template Version Pinning.
This app stores those templates; it does not store the notifications.
Development
poetry install
poetry run pytest
poetry run mypy
poetry run tox # the Python × Django matrix
poetry run pre-commit run --all-files # ruff lint + format
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 vintasend_django_templates_manager-3.1.0.tar.gz.
File metadata
- Download URL: vintasend_django_templates_manager-3.1.0.tar.gz
- Upload date:
- Size: 26.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a654702da0fbe994ee9e82c5186b1f09a40965a8fbb5a9856110eba22fcc9374
|
|
| MD5 |
f3b08a975db0ffc0246560cc140ecd00
|
|
| BLAKE2b-256 |
5932e6985fee4b317e649e2982835f47949be0a44b596adc9ad60561f6a70aa4
|
Provenance
The following attestation bundles were made for vintasend_django_templates_manager-3.1.0.tar.gz:
Publisher:
publish.yml on vintasoftware/vintasend-django-templates-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vintasend_django_templates_manager-3.1.0.tar.gz -
Subject digest:
a654702da0fbe994ee9e82c5186b1f09a40965a8fbb5a9856110eba22fcc9374 - Sigstore transparency entry: 2555688292
- Sigstore integration time:
-
Permalink:
vintasoftware/vintasend-django-templates-manager@32d75b1de769fe0f6335b7a711830304210c6f8f -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/vintasoftware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32d75b1de769fe0f6335b7a711830304210c6f8f -
Trigger Event:
push
-
Statement type:
File details
Details for the file vintasend_django_templates_manager-3.1.0-py3-none-any.whl.
File metadata
- Download URL: vintasend_django_templates_manager-3.1.0-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ac421ffcd46d4f20b75532c8929ef3be06aa7fff6db3b3040d2d1a6b22a1fbf
|
|
| MD5 |
efe9f227e68b6cf4022f7a4453fc2d69
|
|
| BLAKE2b-256 |
a43f79d64d2f6cbd3df590ea6878d5355ebe3da6e8b3bf88211b481fa914fa57
|
Provenance
The following attestation bundles were made for vintasend_django_templates_manager-3.1.0-py3-none-any.whl:
Publisher:
publish.yml on vintasoftware/vintasend-django-templates-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vintasend_django_templates_manager-3.1.0-py3-none-any.whl -
Subject digest:
1ac421ffcd46d4f20b75532c8929ef3be06aa7fff6db3b3040d2d1a6b22a1fbf - Sigstore transparency entry: 2555688920
- Sigstore integration time:
-
Permalink:
vintasoftware/vintasend-django-templates-manager@32d75b1de769fe0f6335b7a711830304210c6f8f -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/vintasoftware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32d75b1de769fe0f6335b7a711830304210c6f8f -
Trigger Event:
push
-
Statement type: