Overriding template tags¶
The package overrides the following Django tags:
{% extends %}
{% include %}
It's required to allow us to define fake template context and override other template tags in YAML files. This package uses custom behaviour for these tags only when rendering pattern library and falls back to Django's standard behaviour on all other cases.
The override process has two parts:
- Override your template tag with a mock implementation
- Define fake result for your tag in a YAML file
Providing a default value for template tags¶
To provide a default for a template tag, you need to provide a keyword argument default_html when overriding your tag.
from pattern_library.monkey_utils import override_tag
override_tag(register, 'a_tag_name', default_html="https://example.com/")
This default is used for any tag that's not passed its own context, allowing specificity for those elements that need it while preventing the tag from breaking when it's not structural to the component.
Limitation¶
Currently this feature only supports providing a default for the output of the tag, this does not support modifying context in templates such as {% an_example_tag page.url as example_variable %}
.
When do I need to override a template tag?¶
Ideally your pattern library should be independent, so it doesn't fail when you run it with a project that has no entries in DB or on a local machine without internet connection. This means that you need to override a template tag when it hits DB or any other resource (cache, or requests URL, for example).
You may also need to override template tags in other cases, when data provided by the pattern library’s context mocking is of a different type to what Django would expect – this is because the pattern library only uses data types that are de-serializable from YAML.
Override modes¶
There are two options when it comes to template tag overriding:
- Render another template or pattern (a template with own fake context) instead of calling the original template tag
- Return raw data. For example, you can return a string, that will be rendered instead of the original template tag. You can also return a structure (dict or list) which is useful when overriding "Simple tags" or a custom tag that returns an object or dict
Output into a variable¶
Some tags can set their value into a variable like:
{# renders something #}
{% my_tag some_arg %}
{# Outputs into a variable for later use #}
{% my_tag some_arg as result_var %}
{{ result_var.some_attr }}
The package automatically detects an output variable
(result_var
in our example) when a custom template tag is a
"Simple tag",
so you don't need to worry about these tags.
But when you need to override a tag which sets result into a variable in
it's custom django.template.Node
you would need to specify output var name manually. We will look into how
to do that later in the examples section.
Overriding examples¶
Let's assume that we want to override the {% image image resize_rule %}
template tag from the some_package.image_utils
template tag set
(you have something like {% load image_utils %}
at the top of your template).
This template tag resizes an image
accordingly
to a specific resize_rule
and outputs the <img>
html tag.
It's also possible to assign an image object into a variable
using this syntax: {% image image resize_rule as my_var_name %}
.
In this case tag doesn't render the <img>
tag, but you can access
image object's properties (my_var_name.url
, for example).
First, we need to override a template tag with fake implementation. Note that the fake implementation will only be used when viewing the pattern library: you will be using the actual implementation in our production code.
Assuming that you already have module installed in your project, to define a fake implementation we need to:
First, create a templatetags
package in one of your apps. Note that your app should be defined in INSTALLED_APPS
and it should be defined after the package you are overriding (some_package
in our case).
Then, create image_utils.py
in this package with the following code:
from some_package.templatetags.image_utils import register
from pattern_library.monkey_utils import override_tag
# We are monkey patching here
# Note that `register` should be an instance of `django.template.Library`
# and it's important to the instance where the original tag is defined.
override_tag(register, name='image')
Note: it's recommended to have a single app that contains all template tag
overrides, so it's easy to exclude it from INSTALLED_APPS
in production,
if you want to.
Now we need to define fake result for each instance of our template tag.
Let's assume that we have a template with two calls of the
{% image %}
template tag:
{% load image_utils %}
<div class="user">
<div class="user-avatar">
{% image avatar fill-200x200 %}
</div>
<div class="user-data">
<div>Username: {{ username }}</div>
<div>
<div>User photo:</div>
{% image avatar fill-200x400 %}
</div>
</div>
</div>
Render another template or pattern¶
Our yaml
will similar to this:
# Override template tags
tags:
# Name of the template tag we are overriding
image:
# Arguments of the template tag
# Override {% image avatar fill-200x200 %}
avatar fill-200x200:
template_name: "patterns/atoms/images/image.html"
# Override {% image avatar fill-200x400 %}
avatar fill-200x400:
template_name: "patterns/atoms/images/image.html"
# Override context, if needed
In this example, we override both template tags and
render the same template: patterns/atoms/images/image.html
.
This template is a regular Django template. If it's a pattern,
like in our example, it will be rendered with its own fake
content defined in patterns/atoms/images/image.yaml
.
The downside of this approach is that we render the
same template where an image can be rendered in a different size.
So, if patterns/atoms/images/image.html
has something like
<img src="https://source.unsplash.com/200x200?ocean" width="200" height="200" alt="">
inside, this means that we will render image of size 200x200
few times.
Probably, in the majority of cases, it's ok to render the same template, but not when we are rendering images of different sizes.
There are two approaches for this problem:
- Create a template for every image size you need. It can be a template that you will be only using for pattern library: no production use. This is a good option, when you want define a fake result for a template tag that renders a big piece of HTML code. Also it's useful when the template tag renders some other pattern, which is a common situation.
- For tags that render something small like
<img>
tag, there is an alternative option: you can define raw data in youryaml
file
Return raw data¶
Let's update our yaml
to use raw data:
# Override template tags
tags:
# Name of the template tag we are overriding
image:
# Arguments of the template tag
# Override {% image avatar fill-200x200 %}
avatar fill-200x200:
raw: >
<img src="https://source.unsplash.com/200x200?ocean" width="200" height="200" alt="">
# Override {% image avatar fill-200x400 %}
avatar fill-200x400:
raw: >
<img src="https://source.unsplash.com/200x400?ocean" width="200" height="400" alt="">
# Override context, if needed
Now we make both {% image %}
tags return different strings
without creating a separate templates for them.
The raw
field, can contain any data supported by
PyYAML
without creating a custom object type.
For example, if we have a template like this:
{% comment %}
The following tag assigns result into avatar_thumbnail
for later use and renders nothing (empty string).
{% endcomment %}
{% image avatar fill-200x200 as avatar_thumbnail %}
Avatar file path: {{ avatar_thumbnail.file }}
Avatar URL: {{ avatar_thumbnail.url }}
Avatar: <img src="{{ avatar_thumbnail.url }}" alt="{{ username }}">
We can define our yaml
like this:
tags:
image:
# Override {% image avatar fill-200x200 as avatar_thumbnail %}
avatar fill-200x200 as avatar_thumbnail:
raw:
file: "/path/to/avatar/file"
url: "https://source.unsplash.com/200x200?ocean"
Note that the example above will only work if our image
is a
"Simple tag".
If it's a more custom implementation, we will need to specify output
variable name explicitly like this:
tags:
image:
# Override {% image avatar fill-200x200 as avatar_thumbnail %}
avatar fill-200x200 as avatar_thumbnail:
target_var: avatar_thumbnail
raw:
file: "/path/to/avatar/file"
url: "https://source.unsplash.com/200x200?ocean"
Note the target_var
field.