Zibanu Django Template
Optional Zibanu Django package with helpers for the template layer: a context processor that
publishes site-wide values to every template, plus the zb_tags library of template tags and
filters.
Overview
zibanu.django.template has no models, no migrations and no REST endpoints. It is a
presentation-layer package only, and it consists of three things:
zb_context_data— the supported context processor, publishing eight site-wide keys to every template.full_static_uriandsiteare kept as deprecated wrappers that delegate to it and publish a single key each.zb_tags— one template tag library holding five tags (zb_concat,zb_query_filter,zb_static_uri,zb_subtotal_dict,zb_sum_dict) and three filters (zb_divide,zb_multiply,zb_replace).- Settings defaults —
ZB_FULL_URIand the fiveZB_SITE_*keys, applied inZbDjangoTemplate.ready(), which is the single source of those defaults.
The package imports only Django and the standard library: it depends neither on the
zibanu.django base package nor on any other optional Zibanu package.
Installation
Install the package from its standalone installer:
pip install ./installers/zibanu-django-template/
Add it to INSTALLED_APPS in mainApp/components/base.py (the Django app label is
zb_template):
INSTALLED_APPS = [
# ... existing apps ...
"zibanu.django.template",
]
No migrate step is needed — the package defines no models.
Installing the app is mandatory, not optional wiring: ZbDjangoTemplate.ready() is what
applies the ZB_* setting defaults that zb_context_data reads, and Django only discovers the
zb_tags library inside an application listed in INSTALLED_APPS. Without it,
{% load zb_tags %} fails to resolve and zb_context_data raises AttributeError on the first
missing ZB_* setting.
Quick Start
Wire the context processor into your TEMPLATES setting:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"OPTIONS": {
"context_processors": [
# ... Django's own processors ...
"django.template.context_processors.request",
"zibanu.django.template.context_processors.zb_context_data",
],
},
},
]
django.template.context_processors.request is required alongside it: the zb_static_uri tag
reads request from the render context and raises TemplateSyntaxError when it is absent.
Then, in a template:
{% load zb_tags %}
<img src="{% zb_static_uri "img/logo.png" %}" alt="{{ site_name }}">
<p>{{ site_description }} — v{{ site_version }}</p>
Features
- Eight site-wide context keys published by a single context processor.
- Absolute (scheme + host) static and media URIs, suitable for content that leaves the site —
e-mail bodies, PDF renderings — where Django's
{% static %}host-relative path is not enough. - Arithmetic filters that normalize both operands through
Decimal(str(x)), so string,int,floatandDecimaltemplate values can be mixed without float rounding artifacts. - Fail-soft template callables:
zb_divide,zb_multiplyandzb_query_filternever raise — bad input renders as nothing instead of turning a page into a 500. - Grouping and aggregation tags (
zb_subtotal_dict,zb_sum_dict) for report-style templates with subtotal lines. - Translatable error messages (
gettext_lazy); catalogs live inlocale/.
Usage
Context keys published by zb_context_data
| Key | Value |
|---|---|
full_static_uri |
Absolute URI of the static base, built from the request + STATIC_URL (falls back to / when STATIC_URL is unset or empty) |
full_media_uri |
Absolute URI of the media base, built from the request + MEDIA_URL (same / fallback) |
full_uri |
ZB_FULL_URI when non-empty, otherwise the request's absolute URI of / |
site_name |
ZB_SITE_NAME |
site_logo |
ZB_SITE_LOGO |
site_favicon |
ZB_SITE_FAVICON |
site_description |
ZB_SITE_DESCRIPTION |
site_version |
ZB_SITE_VERSION |
All eight are available in every template rendered with a RequestContext once the processor is
listed in TEMPLATES["OPTIONS"]["context_processors"].
Tags and filters at a glance
The kind of each name matters: a tag is invoked as {% name ... %}, a filter as
{{ value|name:"arg" }}. The table below matches the actual registrations in
templatetags/zb_tags/__init__.py.
| Name | Kind | Registration | Notes |
|---|---|---|---|
zb_concat |
tag | register.simple_tag |
Concatenates every argument through str(); a None renders as the literal None. |
zb_query_filter |
tag | register.simple_tag |
Filters (and optionally orders) a queryset. Requires as var. The order keyword is reserved for order_by. |
zb_static_uri |
tag | register.tag (compiled) |
Takes exactly one argument. Requires request in the context and a non-empty STATIC_URL. |
zb_subtotal_dict |
tag | register.simple_tag |
Groups a list of dicts by a control key and subtotals chosen keys. Requires as var. |
zb_sum_dict |
tag | register.simple_tag |
Sums one key across a list of dicts; returns a formatted string, so it renders inline. |
zb_divide |
filter | register.filter |
Decimal division. Returns Decimal, or "" on a non-numeric operand or a zero divisor. |
zb_multiply |
filter | register.filter |
Decimal multiplication. Returns Decimal, or "" on a non-numeric operand. |
zb_replace |
filter | register.filter |
Substring replacement driven by a single "what|to" argument. |
Basic Usage
{% load zb_tags %}
{# Absolute URI of a static resource — literal or context variable #}
<img src="{% zb_static_uri "img/logo.png" %}">
<img src="{% zb_static_uri resource_path %}">
{# String concatenation #}
{% zb_concat "order-" order.id %} {# order-42 #}
{# Arithmetic filters #}
{{ total|zb_divide:count }}
{{ price|zb_multiply:quantity }}
{# Substring replacement: the argument holds both operands, separated by a pipe #}
{{ "aaa"|zb_replace:"a|b" }} {# bbb #}
{{ code|zb_replace:"-|" }} {# empty replacement deletes the substring #}
{# Aggregation that renders inline #}
Total: {% zb_sum_dict rows "amount" ",.2f" %} {# 1,250.00 #}
Tags that return data structures need as var
zb_query_filter and zb_subtotal_dict return a QuerySet and a list respectively. As simple
tags, their return value is rendered directly unless it is captured, so they must be assigned
with as <var> and consumed afterwards; used bare, the template prints the object's repr
(<QuerySet [...]>, [{'control': ...}]).
{% zb_query_filter articles status="published" order="-created_at" as published %}
{% for article in published %}
<li>{{ article.title }}</li>
{% endfor %}
{% zb_subtotal_dict rows "zone" "amount" as groups %}
{% for group in groups %}
<h3>{{ group.control }}</h3>
{% for row in group.data %}<p>{{ row.amount }}</p>{% endfor %}
<strong>{{ group.totals.amount }}</strong>
{% endfor %}
zb_concat and zb_sum_dict return strings and are meant to render inline, so they do not need
as (they accept it, like any simple tag).
Advanced Usage — subtotals
zb_subtotal_dict(source_list, key_control, *args) returns one dictionary per group, each with
control (the shared value of the control key), totals (one entry per key named in *args)
and data (the group's records, copied without the control key). Behavior worth knowing before
using it:
- The input must already be sorted by the control key. A group is closed only when the value changes between adjacent records, so unsorted input yields one repeated group per run of equal values instead of a single group.
- Records lacking the control key are grouped under
control = None. - Only
int,floatandDecimalvalues feed a subtotal; booleans are excluded on purpose, and any other value is ignored for the total while still being kept indata. - A key listed in
*argsthat never appears keeps its initial subtotal of0; an empty input yields an empty list, with no spurious empty group. - Passing no subtotal key raises
TemplateSyntaxError— this is the one callable of the package that raises by design.
zb_sum_dict(source_list, key, format_string=None) applies the same numeric rules over a flat
list and always returns a string: non-dict records and a non-list source_list are skipped
(zero sum, no error), and an invalid format_string falls back to str(sum).
When to use zb_query_filter
This tag builds a database query in the template layer. That makes the query invisible to
view-level tests, makes N+1 traversals easy to write by accident, and degrades data errors to a
silently blank region (the failure is logged as a warning through the root logger and reported as
a None return). Reserve it for ad-hoc, report-style templates where the filtering is
genuinely presentational. Anything a view, a manager or a serializer can express belongs there
instead, where the query stays testable and its failure stays visible.
Note also that order is consumed as a control keyword and never applied as a lookup, so a model
field actually named order cannot be filtered through this tag.
API Reference
Context processors — zibanu.django.template.context_processors
zb_context_data(request) -> dict— the supported entry point. Returns the eight keys listed above. RaisesAttributeErrorif aZB_*setting is missing because the package is not inINSTALLED_APPSandZbDjangoTemplate.ready()never applied the defaults.full_static_uri(request) -> dict— deprecated. Returns{"full_static_uri": ...}by delegating tozb_context_data. Superseded by it; kept for backwards compatibility only.site(request) -> dict— deprecated. Returns{"site": ...}, taking the value fromzb_context_data'sfull_urikey (note the published key differs from the source key). Superseded byzb_context_data; kept for backwards compatibility only.
New projects should wire only zb_context_data: it already publishes both values.
Template library — zibanu.django.template.templatetags.zb_tags
Owns the register object Django loads for {% load zb_tags %} and exports register,
StaticNodeUri and the eight callables.
zb_concat(first_arg, *args) -> str— concatenation of every argument coerced withstr(); equalsstr(first_arg)when no extra value is given.zb_query_filter(queryset, **kwargs) -> QuerySet | str— applies**kwargsas lookups, except the reservedorderkey, whose value goes toorder_by. Returns""when the input is not a queryset or a lookup/value/ordering field is invalid (AttributeError,FieldError,TypeError,ValidationError,ValueErrorare caught and logged). The returned queryset stays lazy.zb_static_uri(parse, token) -> StaticNodeUri— compilation function registered withregister.tag; raisesTemplateSyntaxErrorunless the tag receives exactly one argument.StaticNodeUri(template.Node)— node produced by the above.render(context)returns the absoluteSTATIC_URLfollowed by the resolved resource path, and raisesTemplateSyntaxErrorat render time when the context has norequestor whenSTATIC_URLis undefined/empty. The resource path is kept as a compiled filter expression, so it may be a literal, a variable or a filtered expression, resolved on every render.zb_subtotal_dict(source_list, key_control, *args) -> list[dict]— see Advanced Usage. RaisesTemplateSyntaxErrorwhen no subtotal key is given.zb_sum_dict(source_list, key, format_string=None) -> str— see Advanced Usage.zb_divide(value, arg)/zb_multiply(value, arg)— decimal arithmetic overDecimal(str(x))operands; return""instead of raising on invalid input (and, forzb_divide, on a zero divisor).zb_replace(value, arg) -> str—@stringfilter;argis"<what>|<to>"and must split into exactly two parts, otherwisevalueis returned unchanged (a silent no-op, not an error). An empty<to>deletes the substring.
templatetags/__init__.py is an intentional package marker with no code: it must stay in place
for {% load zb_tags %} to resolve, and must not define a register of its own.
Application config — zibanu.django.template.apps
ZbDjangoTemplate(AppConfig)—name = "zibanu.django.template",label = "zb_template", translatableverbose_name.ready()is the single source of the package setting defaults: it applies eachZB_*default withgetattr(settings, key, default), so any value already declared by the project is preserved.
Configuration
All six settings are optional — ZbDjangoTemplate.ready() fills in the defaults below — and all
six are consumed by zb_context_data.
| Setting | Default | Purpose |
|---|---|---|
ZB_FULL_URI |
"" |
Canonical site URI; empty makes the processor fall back to the request's absolute URI of / |
ZB_SITE_NAME |
"Zibanu Django" |
Site name, published as site_name |
ZB_SITE_LOGO |
"" |
Logo location, published as site_logo |
ZB_SITE_FAVICON |
"" |
Favicon location, published as site_favicon |
ZB_SITE_DESCRIPTION |
"" |
Site description, published as site_description |
ZB_SITE_VERSION |
"0.0.0" |
Site version, published as site_version |
Following the project convention, declare them in their own component file under
mainApp/components/ and include it from mainApp/settings.py:
# mainApp/components/zb_template.py
ZB_FULL_URI = "https://app.example.com/"
ZB_SITE_NAME = "My Application"
ZB_SITE_VERSION = "2.1.0"
Setting ZB_FULL_URI explicitly is what keeps full_uri (and the deprecated site key) correct
behind a reverse proxy, or in worker-rendered output such as e-mail, where the request host is
not the public one.
Django settings read by the package but not owned by it: STATIC_URL (used by
zb_context_data and required, non-empty, by zb_static_uri) and MEDIA_URL.
Testing
python manage.py test unit_tests.template
There is currently no unit_tests/template/ module in this repository — the package sits at
0% test coverage. (This project has no local execution environment; tests run on the remote
server declared in .claude/project.config.json.)
Requirements
- Python 3.11+
- Django
Nothing else: the package imports only django and the standard library (decimal, logging).
It does not depend on the zibanu.django base package or on any other optional Zibanu package,
so it can be installed on its own.
License
GNU General Public License v3 — see the LICENSE file in this package and the headers in
apps.py. Developed by CQ Inversiones SAS. Copyright © 2019–2026.
Changelog
All notable changes to this package are documented here, one section per principal
version (x.y.z). Pre-releases of the same version are folded into its section.
[2.0.0] — 2026-10-01
Added
- Bilingual (EN/ES)
README.mddocumenting the package's tags, filters, settings and the eight context keys published byzb_context_data.
Changed
- Breaking: the app's Django label is now the explicit
zb_template(it was previously the implicittemplate, derived from the dotted app name), matching the naming convention of the sibling optional packages. Code that looked the app up by its old implicit label (e.g.apps.get_app_config("template")) needs updating. - Breaking:
zb_subtotal_dictno longer returns a placeholder group for an empty input list — it now correctly returns[]instead of a single entry with zeroed totals and no data. full_static_uriandsiteare now deprecated wrappers delegating tozb_context_data, the supported entry point; both keep returning the same keys/values as before, so existing callers are unaffected.full_static_uriandsitenow emit aDeprecationWarningon every call (.. deprecated:: 2.0.0); both wrappers are scheduled for removal in3.0.0.zb_static_urinow compiles its argument with Django's filter-expression compiler instead of manually stripping quotes off the raw token, which also allows the argument to be a template variable, not only a quoted literal.zb_divide/zb_multiplynow normalize both operands throughDecimal(str(x)), so numeric strings are accepted and mixed-type operands work;zb_multiplyno longer silently repeats a string operand as a sequence instead of rejecting it.zb_query_filter'sAttributeErrorfallback andzb_divide's divide-by-zero/non-numeric fallback now return""instead ofNone, so a failed calculation renders blank in a template instead of the literal string "None".site()now honoursZB_FULL_URI(by delegating tozb_context_data) instead of always returning the raw request URI, aligning it withzb_context_data's ownfull_uri.- Breaking:
zb_replacenow partitions its argument on the first pipe instead of splitting on every pipe, so the<to>operand may contain literal pipes ("x|a|b"replacesxwitha|b). An argument with two or more pipes was previously a silent no-op; it now performs a replacement. A pipe-less argument remains a documented no-op. zb_context_datanow re-raises a missingZB_*setting asImproperlyConfiguredwith an actionable message (addzibanu.django.templatetoINSTALLED_APPS) instead of a bareAttributeError, and memoises its dictionary on the request (request._zb_context_data) so delegating processors no longer recompute it.
Fixed
- Critical:
zb_static_uricould never render: it testedhasattr(context, "request"), which is never true on a Django templateContext, so the tag always raised "requires 'request' var in context" regardless of whether the request context processor was enabled. - Critical:
zb_context_datacarried a stray, unused import ofjinja2.compiler.has_safe_repr(left over from whenZB_FULL_URIsupport was added in1.0.4). In an environment withoutjinja2installed, importing this module — and therefore rendering any template using the context processor — raisedImportError. - Critical:
zb_sum_dict's numeric-type guard (type(x) == int or float) was always true regardless ofx's actual type (or floatmakes the expression unconditionally truthy), so a non-numeric value in the summed column raisedTypeErrorinstead of being skipped. zb_replacenow accepts non-string arguments (coerced via@stringfilter/str()) instead of raisingAttributeError.zb_sum_dict's optional format spec is now applied inside atry/except, so an invalid format string degrades to the plain value instead of raising.- Spanish translation catalog (
locale/es/) regenerated for the renamedzb_static_uriuser-facing error messages.
Documentation
- NumPy-style docstrings added across the package.
[1.0.4] — 2026-07-22
Added
ZB_FULL_URIsetting: when set,zb_context_datapublishes it verbatim asfull_uriinstead of deriving it from the current request's absolute URI — useful behind a reverse proxy where the request's own host doesn't match the public site URL.
[1.0.3] — 2026-05-15
Added
zb_context_data, a new context processor publishing eight keys in one call:full_static_uri,full_media_uri,full_uri,site_name,site_logo,site_favicon,site_descriptionandsite_version.ZB_SITE_NAME,ZB_SITE_LOGO,ZB_SITE_FAVICON,ZB_SITE_DESCRIPTIONandZB_SITE_VERSIONsettings, defaulted inZbDjangoTemplate.ready()so a consumer project gets them without declaring anything.
This version went through an
alpha/betapromotion cycle before its final release; no package changes beyond the version marker itself landed during that cycle.
[1.0.2] — 2025-08-27
Added
zb_query_filteraccepts anorderkeyword, applying.order_by()to the filtered queryset before returning it.
[1.0.1] — 2025-04-01
Added
zb_replacetemplate filter, to replace substrings in template output ({{ value|zb_replace:"needle|replacement" }}).
Fixed
- Critical:
zb_divideandzb_multiply, present since1.0.0, were registered with the wrong keyword argument (function=instead offilter_func=), which left them unusable as template filters.
[1.0.0] — 2025-03-14
Initial release. Provides the zb_tags template tag/filter library
(zb_concat, zb_divide, zb_multiply, zb_query_filter, zb_static_uri,
zb_subtotal_dict, zb_sum_dict) and the full_static_uri/site context
processors.
Metadata
Release files for zibanu-django-template 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zibanu_django_template-2.0.0.tar.gz | 51.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zibanu_django_template-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.4 kB
Release files / zibanu_django_template-2.0.0.tar.gz
| Download URL | zibanu_django_template-2.0.0.tar.gz |
|---|---|
| Size | 51.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
389a43cfc226f7329b2d8e0ca1abaf3a2361887718e050c0393002e6a64949e0
|
|
BLAKE2b-256 checksum How to use checksums |
6f4ca323e04327c5f2c4dcc6ae5faf75aa013b94f801f170a13d95ece263709c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / zibanu_django_template-2.0.0-py3-none-any.whl
| Download URL | zibanu_django_template-2.0.0-py3-none-any.whl |
|---|---|
| Size | 43.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7c3eb8213b59fad6698fc8c53e1eca0a96a63f648e3416756332495e571aeec8
|
|
BLAKE2b-256 checksum How to use checksums |
c85577133cd7ed3dea1ea3c16a4b0753c1484702ec287f4f9ebc7290ad0c0d47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|