Skip to content

Jinja support - rebased #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ This package automates the maintenance of UI pattern libraries or styleguides fo
- Create reusable patterns by creating Django templates files as usual.
- All patterns automatically show up in the pattern library’s interface.
- Define data as YAML files for the templates to render with the relevant Django context.
- Override Django templates tags as needed to mock the template’s dependencies.
- Override Django Templates tags as needed to mock the template’s dependencies.
- Document your patterns with Markdown.
- Experimental: support for Jinja templates.

## Why you need this

Expand Down
3 changes: 1 addition & 2 deletions docs/community/related-projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ Here are other projects that are related to django-pattern-library, and may be r
- [Storybook](https://storybook.js.org/), and in particular [Storybook for Server](https://github.com/storybookjs/storybook/tree/master/app/server) – Storybook integration with server-rendered UI components.
- [Pattern Lab](http://patternlab.io/) – PHP or Node pattern library, from which this project is heavily inspired.
- [Astrum](http://astrum.nodividestudio.com/) – Similar to Pattern Lab, Node based.
- [rikki-patterns](https://github.com/springload/rikki-patterns) – Experimental Django-friendly pattern library generator, for Jinja2 and Nunjucks templates
- [rikki-patterns](https://github.com/springload/rikki-patterns) – Experimental Django-friendly pattern library generator, for Jinja and Nunjucks templates
- [django-lookbook](https://github.com/rails-inspire-django/django-lookbook) - Empower your Django development with this pluggable app for creating a robust component library. Includes preview system, documentation engine, and parameter editor for building modular UI effortlessly.


## Pattern libraries based on Django

Here are open-source projects that maintain pattern libraries for Django.
Expand Down
13 changes: 11 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We support:

- Django 4.2, 5.0, 5.1
- Python 3.9, 3.10, 3.11, 3.12
- Django Templates only, no Jinja support
- Django Templates and Jinja (experimental)
- Modern “evergreen” desktop and mobile browsers

## Configuration
Expand All @@ -36,7 +36,7 @@ INSTALLED_APPS = [
]
```

Also add `pattern_library.loader_tags` to `OPTIONS["builtins"]` into the `TEMPLATES` setting:
For Django Templates, add `pattern_library.loader_tags` to `OPTIONS["builtins"]` into the `TEMPLATES` setting:

```python hl_lines="13 14 15"
TEMPLATES = [
Expand All @@ -59,6 +59,15 @@ TEMPLATES = [
]
```

Experimental: for Jinja support, call `override_jinja_tags` in the file that contains your Jinja environment:

```python
from pattern_library.monkey_utils import override_jinja_tags

if apps.is_installed("pattern_library"):
override_jinja_tags()
```

To see the detailed error pages generated by Django when you have `DEBUG = True` in the pattern library, you'll need to make sure you have `X_FRAME_OPTIONS` set, or your browser will block the response:

```python
Expand Down
14 changes: 7 additions & 7 deletions docs/guides/overriding-template-tags.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Overriding template tags

The package overrides the following Django tags:
The package overrides Django’s `extends` and `include` tags, implementing custom behaviour for these tags only when rendering in the pattern library. It falls back to Django's standard behaviour on all other cases. This makes it possible to define fake template contexts once and then have it reused everywhere a template partial is included.

- `{% 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:
We can also override other template tags in YAML files. The override process has two parts:

1. Override your template tag with a mock implementation
2. Define fake result for your tag in a YAML file

!!! warning "No Jinja support"

Overriding arbitrary template tags or functions is currently unsupported for Jinja templates.

## 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.
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ The [django-pattern-library](https://pypi.org/project/django-pattern-library/) p
- Create reusable patterns by creating Django templates files as usual.
- All patterns automatically show up in the pattern library’s interface.
- Define data as YAML files for the templates to render with the relevant Django context.
- Override Django templates tags as needed to mock the template’s dependencies.
- Override Django Templates tags as needed to mock the template’s dependencies.
- Document your patterns with Markdown.
- Experimental: support for Jinja templates.

Here is a screenshot of the pattern library in action:

Expand Down Expand Up @@ -45,4 +46,3 @@ To learn more about how this package can be used, have a look at our talk:
[Reusable UI components: A journey from React to Wagtail](https://www.youtube.com/watch?v=isrOufI7TKc)

[![Reusable UI components: A journey from React to Wagtail](images/pattern-library-talk-youtube.webp)](https://www.youtube.com/watch?v=isrOufI7TKc)

13 changes: 12 additions & 1 deletion docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ context:
- 2
- 3
# Mapping from tag names to tag overrides.
# Currently unsupported for Jinja templates.
tags:
error_tag:
include:
Expand Down Expand Up @@ -135,14 +136,24 @@ PATTERN_LIBRARY = {

### `override_tag`

This function tells the pattern library which Django tags to override, and optionally supports providing a default value. See [Overriding template tags](../guides/overriding-template-tags.md) for more information.
This function tells the pattern library which Django Templates tags to override, and optionally supports providing a default value. See [Overriding template tags](../guides/overriding-template-tags.md) for more information.

```python
from pattern_library.monkey_utils import override_tag

override_tag(register, 'a_tag_name', default_html="https://example.com/")
```

### `override_jinja_tags`

🚧 Experimental. Optionally override `extends` and `include` in Jinja templates, so context for partials can be defined once and reused everywhere. See [Overriding template tags](../guides/overriding-template-tags.md). Call this in your Django settings file or at the top level of the file defining your Jinja environment.

```python
from pattern_library.monkey_utils import override_jinja_tags

override_jinja_tags()
```

## `register_context_modifier`

This decorator makes it possible to override or create additional context data with Django / Python code, rather than being limited to YAML. It has to be called from within a `pattern_contexts` module, which can be at the root of any Django app. See [Modifying template contexts with Python](../guides/defining-template-context.md#modifying-template-contexts-with-python) for more information.
Expand Down
10 changes: 7 additions & 3 deletions docs/reference/known-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ django-pattern-library has a few known limitations due to its design, which are

## Overriding filters is not supported

See [#114](https://github.com/torchbox/django-pattern-library/issues/114). PRs welcome!
See [#114](https://github.com/torchbox/django-pattern-library/issues/114) for Django Templates. PRs welcome!

## Can’t override context in a child template

Expand Down Expand Up @@ -62,12 +62,16 @@ See [#138](https://github.com/torchbox/django-pattern-library/issues/138). For e

This can’t be mocked for all usage of `include_block`.

## Jinja2 support
## Jinja2 overrides

Or lack thereof! If you’re interested in this, please share your thoughts with us on [#180](https://github.com/torchbox/django-pattern-library/discussions/180).
There is experimental support, excluding overrides of arbitrary tags, functions, and filters. If you’re interested in this, please share your thoughts with us on [#180](https://github.com/torchbox/django-pattern-library/discussions/180).

## Past limitations

### Jinja2 support

🎉 This is now addressed as of v1.4.0, though with only experimental support, and no capability to override tags, functions, filters (see above).

### No way to specify objects that have attributes and support iteration

🎉 This is now addressed as of v0.5.0, with the [context modifiers in Python](../guides/defining-template-context.md#modifying-template-contexts-with-python) API. View our [pagination](../recipes/pagination.md) recipe.
Expand Down
48 changes: 48 additions & 0 deletions pattern_library/loader_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,51 @@ def do_include(parser, token):
extra_context=namemap,
isolated_context=isolated_context,
)


def visit_extends(self, node, frame):
"""This method overrides the jinja extends tag
Is called as part of the compiler CodeGenerator
and adds a line to use the template_new_context as
part of the runtime render to pull in the dpl context
Handles visiting extends
"""
from .monkey_utils import jinja_visit_Extends

jinja_visit_Extends(self, node, frame)
# addition to update the context with dpl context
# calls the template_new_context method below when
# invoked at runtime
self.writeline(
"parent_template.new_context(context.get_all(), True,"
f" {self.dump_local_context(frame)})"
)


def template_new_context(
self,
vars=None,
shared=False,
locals=None,
):
"""This method overrides the jinja include tag
Is called as part of Template.render by jinja2 and is updated
to pull in the dpl context
Create a new :class:`Context` for this template. The vars
provided will be passed to the template. Per default the globals
are added to the context. If shared is set to `True` the data
is passed as is to the context without adding the globals.

`locals` can be a dict of local variables for internal usage.
"""
from jinja2.runtime import new_context

if is_pattern_library_context(vars or {}) and (
pattern_context := get_pattern_context(self.name)
):
for k, v in pattern_context.items():
vars.setdefault(k, v)

return new_context(
self.environment, self.name, self.blocks, vars, shared, self.globals, locals
)
13 changes: 5 additions & 8 deletions pattern_library/management/commands/render_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
from django.test.client import RequestFactory

from pattern_library import get_base_template_names, get_pattern_base_template_name
from pattern_library.utils import (
get_pattern_context,
get_pattern_templates,
get_template_ancestors,
render_pattern,
)
from pattern_library.utils import get_pattern_context, get_renderer, render_pattern


class Command(BaseCommand):
Expand Down Expand Up @@ -44,7 +39,8 @@ def handle(self, **options):
self.wrap_fragments = options["wrap_fragments"]
self.output_dir = options["output_dir"]

templates = get_pattern_templates()
renderer = get_renderer()
templates = renderer.get_pattern_templates()

factory = RequestFactory()
request = factory.get("/")
Expand Down Expand Up @@ -106,7 +102,8 @@ def render_pattern(self, request, pattern_template_name):
if not self.wrap_fragments:
return rendered_pattern

pattern_template_ancestors = get_template_ancestors(
renderer = get_renderer()
pattern_template_ancestors = renderer.get_template_ancestors(
pattern_template_name,
context=get_pattern_context(pattern_template_name),
)
Expand Down
24 changes: 24 additions & 0 deletions pattern_library/monkey_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,27 @@ def node_render(context):
return original_node

return tag_func


# have to export the original jinja visit Extends
# in the case jinja tags are being overriden
jinja_visit_Extends = None


def override_jinja_tags():
"""
Experimental. Overrides jinja extends and include tags for use in your pattern library.
Call it in your settings to override tags
"""
global jinja_visit_Extends
try:
from jinja2.compiler import CodeGenerator as JinjaCodeGenerator
from jinja2.environment import Template as JinjaTemplate
except ModuleNotFoundError:
ModuleNotFoundError("install jinja2 to override jinja tags")

from pattern_library.loader_tags import template_new_context, visit_extends

jinja_visit_Extends = JinjaCodeGenerator.visit_Extends
JinjaTemplate.new_context = template_new_context
JinjaCodeGenerator.visit_Extends = visit_extends
Loading
Loading