Built-in Filters & Tags¶
django-render-static includes several built-in template filters and tags. These are
described here. All tags and filters are available on both the
StaticDjangoTemplates backend and the
jinja2StaticJinja2Templates backend.
Filters¶
split¶
This is a simple wrapper around Python’s split call. A frequent use case of this library might be passing lists of classes/modules/includes/excludes into the other tags in this library. This allows users to embed those lists directly in templates without having to provide them in a template context. The first argument is the string to split and the second is the separator:
{{ "my_app.mod.Class, my_app.mod.OtherClass"|split:"," }}
Tags¶
Tags on the StaticDjangoTemplates backend are django
template tags using the {% %} syntax. Using the
jinja2StaticJinja2Templates backend these tags are global
functions. For example, in Django templates urls_to_js might be called like so:
{% urls_to_js exclude=exclude %}
And the equivalent call in Jinja2 would be:
{{ urls_to_js(exclude=exclude) }}
Render static provides three python to javascript transpiler tags. These tags transpile defines, urls and enums into javascript, but they are all specializations of a base {% transpile %} tag. If you have a use case that requires another python structure to be transpiled I recommend using the base classes and specializing transpile. See the Reference for the details.
Because they share a common base, all of the transpiler tags accept similar arguments. They will traverse source trees to find the code they are looking for. All of them will accept either a list of modules, classes or import strings to either of the above. They will then traverse the source code, looking into nested classes if necessary to find the specific structures they know how to transpile.
defines_to_js¶
Converts a list of classes or modules and their defines-style attributes into JavaScript structures. Defines-style attributes are attributes that:
Are all upper case
Contain plain old data including iterables and dictionaries that contain json-serializbale types.
It accepts a number of different parameters:
defines A class, or classes or modules containing defines structures or an import string to any of the above. All of the enums found during traversal will be transpiled.
transpiler A string import path or a class that implements
render_static.transpilers.Transpiler. The transpiler walks the source tree and generates the JavaScript, users may customize the JavaScript generated by implementing their own transpiler class. One transpiler is included. The default,render_static.transpilers.defines_to_js.DefaultDefineTranspiler, transpiles to nested (if appropriate) object structures containing the defines.indent String to use for indentation in javascript, default: ‘\t’, If None or the empty string is specified, the generated code will not contain newlines.
depth The starting indentation depth, default: 0
- Any additional parameters that the configured transpiler accepts:
{% defines_to_js defines=class_list indent="\t" %}
For instance if the class_list context variable contained the following:
context: {
'class_list': ['myapp.defines.TestDefines']
}
And myapp.defines.TestDefines contained the following:
class TestDefines(object):
DEFINE1 = 'D1'
DEFINE2 = 'D2'
DEFINE3 = 'D3'
DEFINES = (
(DEFINE1, 'Define 1'),
(DEFINE2, 'Define 2'),
(DEFINE3, 'Define 3'),
)
DICTIONARY = {
'Key': 'value',
'Numeric': 0
}
The generated source would look like:
const defines = {
TestDefines: {
DEFINE1: "D1",
DEFINE2: "D2",
DEFINE3: "D3",
DEFINES: [["D1", "Define 1"], ["D2", "Define 2"], ["D3", "Define 3"]],
DICTIONARY: {"Key": "value", "Numeric": 0}
},
};
Note
The filter will also walk inheritance hierarchy and pull out any defines-style attributes in parent classes and add them to the JavaScript.
Overrides¶
The DefaultDefineTranspiler supports the
override block. The context available to override blocks is detailed here:
render_static.transpilers.defines_to_js.DefaultDefineTranspiler.context. More code
can be added to define variables or specific defines can be overridden by using their python
path:
{% defines_to_js defines='myapp' %}
{% override 'myapp.defines.TestDefines.DEFINE1' %}
"OVERRIDE"
{% endoverride %}
{% enddefines_to_js %}
urls_to_js¶
Often client side JavaScript needs to fetch site URLs asynchronously. These instances either necessitate using dynamic templating to reverse the url via the url tag or to hardcode the path into the JavaScript thereby violating the DRY principle. Frequently the need to generate these paths are the only thing driving the need to generate the JavaScript dynamically. But these paths might change only at deployment, not runtime, so the better approach is to generate JavaScript at deployment time and serve it statically. This tag makes that process even easier by automatically translating the site’s url configuration into a JavaScript utility that can be used in the same manner as Django’s URL reverse function.
It accepts a number of different parameters:
transpiler A string import path or a class that implements
render_static.transpilers.urls_to_js.URLTreeVisitor. The transpiler walks the URL tree and generates the JavaScript, users may customize the JavaScript generated by implementing their own transpiler class. Two transpilers are included. The default,render_static.transpilers.urls_to_js.ClassURLWriter, spits out an ES6 class that provides areversefunction directly analogous to Django’sreverse()function.render_static.transpilers.urls_to_js.SimpleURLWriterspits out an object structure that indexes paths by their namespaces.url_conf The root url module to dump urls from. Can be an import string or an actual module type. default:
ROOT_URLCONFindent String to use for indentation in javascript, default: ‘\t’, If None or the empty string is specified, the generated code will not contain newlines.
depth The starting indentation depth, default: 0
include A list of path names to include, namespaces without path names will be treated as every path under the namespace. Default: include everything
exclude A list of path names to exclude, namespaces without path names will be treated as every path under the namespace. Excludes override includes. Default: exclude nothing
- Any additional parameters that the configured transpiler accepts:
Includes and excludes are hierarchical strings that contain the fully qualified name of a namespace or path name. For instance namespace1:namespace2:url_name would include only patterns that are mapped to url_name under namespace2 that is in turn under namespace1. namespace1:namespace2 would include all paths in any namespace(s) at or under namespace1:namespace2 but it would not include paths directly under namespace1. Excludes always override includes. By default every path is included and no paths are excluded. If any includes are provided, then only those includes are included (everything else is by default excluded).
Note
When implementing custom URL transpilers, any additional named arguments passed to the urls_to_js
tag will be passed as kwargs to the URL transpiler when this tag instantiates it. These parameters
are meant to provide configuration toggles for the generated JavaScript.
Warning
All the URLs embedded in JavaScript are exposed client side. Its never a good idea to have site security dependent on path visibility, but if there are sensitive URLs that shouldn’t be generally known its best practice to exclude them from URL generation.
It is strongly encouraged as a best practice to use path instead of re_path. If an argument requires a regex that isn’t supported by the existing Django converter set it is very easy to implement new ones:
from django.urls.converters import register_converter
class YearConverter:
regex = '[0-9]{4}'
placeholder = 2000 # this attribute is used by `url_to_js` to reverse paths
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value)
register_converter(YearConverter, 'year')
urlpatterns = [
path('fetch/<year:year>', YearView.as_view(), name='fetch_year')
]
Note the placeholder attribute. This attribute is used by urls_to_js to reverse paths
for the generated JavaScript. By including the attribute on your converter you ensure that
anyone using your converter will be able to run urls_to_js without error. And you don’t
even have to include django-render-static as a dependency if you aren’t using it!
Alternatively if you’re using someone else’s converter and they haven’t supplied a
placeholder attribute, you can register one:
from render_static.placeholders import register_converter_placeholder
register_converter_placeholder(YearConverter, 2000)
Of if you’re using re_path instead:
from render_static.placeholders import register_variable_placeholder
app_name = 'year_app'
urlpatterns = [
re_path(r'^fetch/(?P<year>\d{4})/$', YearView.as_view(), name='fetch_year')
]
register_variable_placeholder('year', 2000, app_name=app_name)
Paths with unnamed arguments are also supported, but be kind to yourself and don’t use them.
Any number of placeholders may be registered against any number of variable/app_name combinations.
When urls_to_js is run it won’t give up until its tried all placeholders that might potentially
match the path.
Overly complex string parsing logic is avoided by reversing the urls and using the regular expression match objects to determine where argument substitutions are made. This keeps the code simple, reliable and avoids deep dependencies on Django’s url configuration code. Placeholders are the price paid for that reliability. Common default placeholders are attempted after all registered placeholders fail, and all of Django’s native path converters are supported. This should allow most urls to work out of the box.
Overrides¶
Both the ClassURLWriter and
SimpleURLWriter transpilers support the
override block. The contexts available to override blocks for each transpiler are
detailed here:
Any function on ClassURLWriter including the
constructor can be overridden and both transpilers allow adding to the class or object and
overriding the reversal code for specific url names. For instance:
{% urls_to_js transpiler='render_static.transpilers.SimpleURLWriter' %}
{% override 'namespace:path_name' %}
return "/an/overridden/path";
{% endoverride %}
{% endurls_to_js %}
ClassURLWriter (default)¶
A transpiler class that produces ES6 JavaScript class is now included. As of version 2 This
class is used by default. The ClassURLWriter
is guaranteed to produce output identical to Django’s reverse function. If it does not please
report a bug. To use the class writer:
{% urls_to_js transpiler='render_static.transpilers.ClassURLWriter' class_name='URLResolver' %}
<! the above is equivalent to the below -->
{% urls_to_js %}
This will generate an ES6 class by default:
/**
* A url resolver class that provides an interface very similar to Django's
* reverse() function. This interface is nearly identical to reverse() with
* a few caveats:
*
* - Python type coercion is not available, so care should be taken to pass
* in argument inputs that are in the expect string format.
* - Not all reversal behavior can be replicated but these are corner cases
* that are not likely to be correct url specification to begin with.
* - The reverse function also supports a query option to include url query
* parameters in the reversed url.
*
* @class
*/
class URLResolver {
/**
* Instantiate this url resolver.
*
* @param {Object} options - The options object.
* @param {string} options.namespace - When provided, namespace will
* prefix all reversed paths with the given namespace.
*/
constructor(options=null) {
this.options = options || {};
if (this.options.hasOwnProperty("namespace")) {
this.namespace = this.options.namespace;
if (!this.namespace.endsWith(":")) {
this.namespace += ":";
}
} else {
this.namespace = "";
}
}
/**
* Given a set of args and kwargs and an expected set of arguments and
* a default mapping, return True if the inputs work for the given set.
*
* @param {Object} kwargs - The object holding the reversal named arguments.
* @param {string[]} args - The array holding the positional reversal arguments.
* @param {string[]} expected - An array of expected arguments.
* @param {Object.<string, string>} defaults - An object mapping default arguments to their values.
*/
match(kwargs, args, expected, defaults={}) {
if (defaults) {
kwargs = Object.assign({}, kwargs);
for (const [key, val] of Object.entries(defaults)) {
if (kwargs.hasOwnProperty(key)) {
if (kwargs[key] !== val) { return false; }
if (!expected.includes(key)) { delete kwargs[key]; }
}
}
}
if (Array.isArray(expected)) {
return (
Object.keys(kwargs).length === expected.length &&
expected.every(value => kwargs.hasOwnProperty(value));
);
} else if (expected) {
return args.length === expected;
} else {
return Object.keys(kwargs).length === 0 && args.length === 0;
}
}
/**
* Reverse a Django url. This method is nearly identical to Django's
* reverse function, with an additional option for URL parameters. See
* the class docstring for caveats.
*
* @param {string} qname - The name of the url to reverse. Namespaces
* are supported using `:` as a delimiter as with Django's reverse.
* @param {Object} options - The options object.
* @param {string} options.kwargs - The object holding the reversal named arguments.
* @param {string[]} options.args - The array holding the reversal positional arguments.
* @param {Object.<string, string|string[]>} options.query - URL query parameters to add
* to the end of the reversed url.
*/
reverse(qname, options={}) {
if (this.namespace) {
qname = `${this.namespace}${qname.replace(this.namespace, "")}`;
}
const kwargs = options.kwargs || {};
const args = options.args || [];
const query = options.query || {};
let url = this.urls;
for (const ns of qname.split(':')) {
if (ns && url) { url = url.hasOwnProperty(ns) ? url[ns] : null; }
}
if (url) {
let pth = url(kwargs, args);
if (typeof pth === "string") {
if (Object.keys(query).length !== 0) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (value === null || value === '') continue;
if (Array.isArray(value)) value.forEach(element => params.append(key, element));
else params.append(key, value);
}
const qryStr = params.toString();
if (qryStr) return `${pth.replace(/\/+$/, '')}?${qryStr}`;
}
return pth;
}
}
throw new TypeError(`No reversal available for parameters at path: ${qname}`);
}
urls = {
"different": (kwargs={}, args=[]) => {
if (this.match(kwargs, args, ['arg1','arg2'])) { return `/different/${kwargs["arg1"]}/${kwargs["arg2"]}`; }
},
"simple": (kwargs={}, args=[]) => {
if (this.match(kwargs, args, ['arg1'])) { return `/simple/${kwargs["arg1"]}`; }
if (this.match(kwargs, args)) { return "/simple"; }
},
}
};
Which can be used as:
// /different/143/emma
const urls = new URLResolver();
urls.reverse('different', {kwargs: {'arg1': 143, 'arg2': 'emma'}});
Note that the reverse function takes an options dictionary containing named parameters instead of passing kwargs and args positionally:
For instance:
// /different/143/emma?intarg=0&listarg=A&listarg=B&listarg=C
url.reverse(
'different',
{
kwargs: {arg1: 143, arg2: 'emma'},
query: {
intarg: 0,
listarg: ['A', 'B', 'C']
}
}
);
The default class_name is URLResolver. Reverse should behave exactly as Django’s
reverse().
The URLResolver accepts an optional options object. This object currently supports one parameter: namespace which is a default namespace that will be prepended if it is not already present to any reverse requests made on the resolver:
const urls = new URLResolver({namespace: 'ns'});
// now these calls are equivalent
urls.reverse('ns:name1')
urls.reverse('name1')
enums_to_js¶
Transpile PEP 435 style Python enumerations. The default transpiler that generates ES6 style classes in the style of Axel Rauschmayer’s Enum pattern.
Converts a list of enums or modules and classes that contain enums into javascript. As with defines, it will recursively traverse that module or class you pass it and find any enumerations and transpile them.
It accepts a number of different parameters:
enums An enum class, or classes or modules containing enum classes or an import string to any of the above. All of the enums found during traversal will be transpiled.
transpiler A string import path or a class that implements
render_static.transpilers.enums_to_js.EnumTranspiler. The transpiler walks the URL tree and generates the JavaScript, users may customize the JavaScript generated by implementing their own transpiler class. One transpiler is included. The default,render_static.transpilers.EnumClassWriter, spits out an ES6 class for each Enum.indent String to use for indentation in javascript, default: ‘\t’, If None or the empty string is specified, the generated code will not contain newlines.
depth The starting indentation depth, default: 0
- Any additional parameters that the transpiler accepts:
Say instead of the usual choices tuple you’re using
PEP 435 style python enumerations as model fields using
django-enum and enum-properties. For
example we might define a simple color enumeration like so:
import typing as t
from django.db import models
from django_enum import EnumField
from django_enum.choices import TextChoices
from enum_properties import Symmetric, s
class ExampleModel(models.Model):
class Color(TextChoices):
rgb: t.Annotated[t.Tuple[int, int, int], Symmetric()]
hex: t.Annotated[str, Symmetric(case_fold=True)]
# name value label rgb hex
RED = 'R', 'Red', (1, 0, 0), 'ff0000'
GREEN = 'G', 'Green', (0, 1, 0), '00ff00'
BLUE = 'B', 'Blue', (0, 0, 1), '0000ff'
color = EnumField(Color, null=True, default=None)
If we define an enum.js template that looks like this:
{% enums_to_js enums="examples.models.ExampleModel.Color" %}
It will contain a javascript class transpilation of the Color enum that looks like this:
class Color {
static RED = new Color("R", "RED", "Red", [1, 0, 0], "ff0000");
static GREEN = new Color("G", "GREEN", "Green", [0, 1, 0], "00ff00");
static BLUE = new Color("B", "BLUE", "Blue", [0, 0, 1], "0000ff");
constructor (value, name, label, rgb, hex) {
this.value = value;
this.name = name;
this.label = label;
this.rgb = rgb;
this.hex = hex;
}
toString() {
return this.value;
}
static get(value) {
if (value instanceof this) {
return value;
}
for (const en of this) {
if (en.value === value) {
return en;
}
}
throw new TypeError(`No Color enumeration maps to value ${value}`);
}
static [Symbol.iterator]() {
return [Color.RED, Color.GREEN, Color.BLUE][Symbol.iterator]();
}
}
We can now use our enumeration like so:
Color.BLUE === Color.get('B');
for (const color of Color) {
console.log(color);
}
Overrides¶
You may add additional code to the class or override the following functions:
constructor
toString
get
ciCompare
[Symbol.iterator]
See render_static.transpilers.enums_to_js.EnumClassWriter.context for the context made
available by the transpiler to override blocks.
override¶
All of the transpilation tags accept child override blocks to override default transpilation of functions or objects or be used to add additional code to an object block or class. For example, if we wanted to override the default transpilation of the Color class above to allow instantiation off a cmyk value we could do so by adapting the get function and adding a new static utility function called cmykToRgb. We would do so like this:
{% enums_to_js enums="examples.models.ExampleModel.Color" %}
{# to override a function we must pass its name as the argument #}
{% override 'get' %}
static get(value) {
if (Array.isArray(value) && value.length === 4) {
value = Color.cmykToRgb(...value);
}
if (Array.isArray(value) && value.length === 3) {
for (const en of this) {
let i = 0;
for (; i < 3; i++) {
if (en.rgb[i] !== value[i]) break;
}
if (i === 3) return en;
}
}
{{ default_impl }}
}
{% endoverride %}
{# additions do not require a name argument #}
{% override %}
static cmykToRgb(c, m, y, k) {
let r = 255 * (1 - c / 100) * (1 - k / 100);
let g = 255 * (1 - m / 100) * (1 - k / 100);
let b = 255 * (1 - y / 100) * (1 - k / 100);
return [Math.round(r), Math.round(g), Math.round(b)]
}
{% endoverride %}
{% endenums_to_js %}
When a function is overridden, the default implementation is available in the template context as
the default_impl variable. This allows you to add the default implementation from code to your
override. The context available to an override block varies depending on the transpiler. See the
individual tag sections for details.
The above example will generate code that looks like this:
class Color {
static RED = new Color("R", "RED", "Red", [1, 0, 0], "ff0000");
static GREEN = new Color("G", "GREEN", "Green", [0, 1, 0], "00ff00");
static BLUE = new Color("B", "BLUE", "Blue", [0, 0, 1], "0000ff");
constructor (value, name, label, rgb, hex) {
this.value = value;
this.name = name;
this.label = label;
this.rgb = rgb;
this.hex = hex;
}
toString() {
return this.value;
}
static get(value) {
if (Array.isArray(value) && value.length === 4) {
value = Color.cmykToRgb(...value);
}
if (Array.isArray(value) && value.length === 3) {
for (const en of this) {
let i = 0;
for (; i < 3; i++) {
if (en.rgb[i] !== value[i]) break;
}
if (i === 3) return en;
}
}
if (value instanceof this) {
return value;
}
for (const en of this) {
if (en.value === value) {
return en;
}
}
throw new TypeError(`No Color enumeration maps to value ${value}`);
}
static [Symbol.iterator]() {
return [Color.RED, Color.GREEN, Color.BLUE][Symbol.iterator]();
}
static cmykToRgb(c, m, y, k) {
let r = (1 - c / 100) * (1 - k / 100);
let g = (1 - m / 100) * (1 - k / 100);
let b = (1 - y / 100) * (1 - k / 100);
return [Math.round(r), Math.round(g), Math.round(b)]
}
}
Note
The Jinja2 tags do not currently support overrides.