Skip to main content

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.

  • Templateskey and version lock 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 = False on a ManagedTemplateAdminForm subclass 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

vintasend_django_templates_manager-3.1.0.tar.gz (26.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file vintasend_django_templates_manager-3.1.0.tar.gz.

File metadata

File hashes

Hashes for vintasend_django_templates_manager-3.1.0.tar.gz
Algorithm Hash digest
SHA256 a654702da0fbe994ee9e82c5186b1f09a40965a8fbb5a9856110eba22fcc9374
MD5 f3b08a975db0ffc0246560cc140ecd00
BLAKE2b-256 5932e6985fee4b317e649e2982835f47949be0a44b596adc9ad60561f6a70aa4

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vintasend_django_templates_manager-3.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for vintasend_django_templates_manager-3.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1ac421ffcd46d4f20b75532c8929ef3be06aa7fff6db3b3040d2d1a6b22a1fbf
MD5 efe9f227e68b6cf4022f7a4453fc2d69
BLAKE2b-256 a43f79d64d2f6cbd3df590ea6878d5355ebe3da6e8b3bf88211b481fa914fa57

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

3.1.1

2 files

This release

3.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page