mirror of
https://github.com/django-components/django-components.git
synced 2025-08-25 00:14:04 +00:00
Patch TemplateProxy to restore compatibility with template_partials (#1328)
Co-authored-by: EmilStenstrom <224130+EmilStenstrom@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
This commit is contained in:
parent
e9b7470850
commit
aa14e3698d
8 changed files with 159 additions and 13 deletions
|
@ -1,5 +1,6 @@
|
|||
django
|
||||
djc-core-html-parser
|
||||
django-template-partials
|
||||
tox
|
||||
pytest
|
||||
pytest-asyncio
|
||||
|
|
|
@ -19,7 +19,11 @@ class ComponentsConfig(AppConfig):
|
|||
from django_components.component_registry import registry
|
||||
from django_components.components.dynamic import DynamicComponent
|
||||
from django_components.extension import extensions
|
||||
from django_components.util.django_monkeypatch import monkeypatch_include_node, monkeypatch_template_cls
|
||||
from django_components.util.django_monkeypatch import (
|
||||
monkeypatch_include_node,
|
||||
monkeypatch_template_cls,
|
||||
monkeypatch_template_proxy_cls,
|
||||
)
|
||||
|
||||
# NOTE: This monkeypatch is applied here, before Django processes any requests.
|
||||
# To make django-components work with django-debug-toolbar-template-profiler
|
||||
|
@ -27,6 +31,10 @@ class ComponentsConfig(AppConfig):
|
|||
monkeypatch_template_cls(Template)
|
||||
monkeypatch_include_node(IncludeNode)
|
||||
|
||||
# This makes django-components work with django-template-partials
|
||||
# NOTE: Delete when Django 5.2 reaches end of life
|
||||
monkeypatch_template_proxy_cls()
|
||||
|
||||
# Import modules set in `COMPONENTS.libraries` setting
|
||||
import_libraries()
|
||||
|
||||
|
|
|
@ -3250,7 +3250,7 @@ class Component(metaclass=ComponentMeta):
|
|||
),
|
||||
)
|
||||
```
|
||||
""" # noqa: 501
|
||||
""" # noqa: E501
|
||||
|
||||
# TODO_v1 - Remove, superseded by `deps_strategy`
|
||||
if type is not None:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from typing import Any, Optional, Type
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.template import Context, NodeList, Template
|
||||
from django.template.base import Node, Origin, Parser
|
||||
from django.template.loader_tags import IncludeNode
|
||||
|
@ -117,10 +118,14 @@ def monkeypatch_template_compile_nodelist(template_cls: Type[Template]) -> None:
|
|||
)
|
||||
|
||||
try:
|
||||
# ---------------- ADDED IN Django v5.1 - See https://github.com/django/django/commit/35bbb2c9c01882b1d77b0b8c737ac646144833d4 # noqa: E501
|
||||
nodelist = parser.parse()
|
||||
self.extra_data = getattr(parser, "extra_data", {})
|
||||
# ---------------- END OF ADDED IN Django v5.1 ----------------
|
||||
if DJANGO_VERSION >= (5, 1):
|
||||
# ---------------- ADDED IN Django v5.1 - See https://github.com/django/django/commit/35bbb2c9c01882b1d77b0b8c737ac646144833d4 # noqa: E501
|
||||
# NOTE: This must be enabled ONLY for 5.1 and later. Previously it was also for older
|
||||
# Django versions, and this led to compatibility issue with django-template-partials.
|
||||
# See https://github.com/carltongibson/django-template-partials/pull/85#issuecomment-3187354790
|
||||
self.extra_data = getattr(parser, "extra_data", {})
|
||||
# ---------------- END OF ADDED IN Django v5.1 ----------------
|
||||
return nodelist
|
||||
except Exception as e:
|
||||
if self.engine.debug:
|
||||
|
@ -243,5 +248,33 @@ def monkeypatch_include_render(include_node_cls: Type[Node]) -> None:
|
|||
include_node_cls.render = _include_render
|
||||
|
||||
|
||||
# NOTE: Remove once Django v5.2 reaches end of life
|
||||
# See https://github.com/django-components/django-components/issues/1323#issuecomment-3163478287
|
||||
def monkeypatch_template_proxy_cls() -> None:
|
||||
# Patch TemplateProxy if template_partials is installed
|
||||
# See https://github.com/django-components/django-components/issues/1323#issuecomment-3164224042
|
||||
try:
|
||||
from template_partials.templatetags.partials import TemplateProxy
|
||||
except ImportError:
|
||||
# template_partials is in INSTALLED_APPS but not actually installed
|
||||
# This is fine, just skip the patching
|
||||
return
|
||||
|
||||
if is_cls_patched(TemplateProxy):
|
||||
return
|
||||
|
||||
monkeypatch_template_proxy_render(TemplateProxy)
|
||||
TemplateProxy._djc_patched = True
|
||||
|
||||
|
||||
def monkeypatch_template_proxy_render(template_proxy_cls: Type[Any]) -> None:
|
||||
# NOTE: TemplateProxy.render() is same logic as Template.render(), just duplicated.
|
||||
# So we can instead reuse Template.render()
|
||||
def _template_proxy_render(self: Any, context: Context, *args: Any, **kwargs: Any) -> str:
|
||||
return Template.render(self, context)
|
||||
|
||||
template_proxy_cls.render = _template_proxy_render
|
||||
|
||||
|
||||
def is_cls_patched(cls: Type[Any]) -> bool:
|
||||
return getattr(cls, "_djc_patched", False)
|
||||
|
|
13
tests/templates/integration_template_partials.html
Normal file
13
tests/templates/integration_template_partials.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!-- TODO: Delete after Django 5.2 reaches end of life -->
|
||||
{% load component_tags %}
|
||||
{% load partials %}
|
||||
|
||||
{% partialdef test___partial inline %}
|
||||
|
||||
{% component_css_dependencies %}
|
||||
|
||||
{% component "calendar" date=date / %}
|
||||
|
||||
{% component_js_dependencies %}
|
||||
|
||||
{% endpartialdef %}
|
84
tests/test_integration_template_partials.py
Normal file
84
tests/test_integration_template_partials.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
"""
|
||||
Tests for template_partials integration with django-components.
|
||||
|
||||
See https://github.com/django-components/django-components/issues/1327
|
||||
and https://github.com/django-components/django-components/issues/1323.
|
||||
|
||||
This file can be deleted after Django 5.2 reached end of life.
|
||||
See https://github.com/django-components/django-components/issues/1323#issuecomment-3163478287.
|
||||
"""
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
import pytest
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpRequest
|
||||
|
||||
from django_components import Component, register
|
||||
from django_components.testing import djc_test
|
||||
from .testutils import setup_test_config
|
||||
|
||||
try:
|
||||
from template_partials.templatetags.partials import TemplateProxy
|
||||
except ImportError:
|
||||
TemplateProxy = None
|
||||
|
||||
|
||||
setup_test_config(components={"autodiscover": False})
|
||||
|
||||
|
||||
# See https://github.com/django-components/django-components/issues/1323#issuecomment-3156654329
|
||||
@djc_test(
|
||||
django_settings={"INSTALLED_APPS": ("template_partials", "django_components", "tests.test_app")}
|
||||
)
|
||||
class TestTemplatePartialsIntegration:
|
||||
@pytest.mark.skipif(TemplateProxy is None, reason="template_partials not available")
|
||||
def test_render_partial(self):
|
||||
@register("calendar")
|
||||
class Calendar(Component):
|
||||
template = """
|
||||
<div class="calendar-component">
|
||||
<div>Today's date is <span>{{ date }}</span></div>
|
||||
</div>
|
||||
"""
|
||||
css = """
|
||||
.calendar-component { width: 200px; background: pink; }
|
||||
.calendar-component span { font-weight: bold; }
|
||||
"""
|
||||
js = """
|
||||
(function(){
|
||||
if (document.querySelector(".calendar-component")) {
|
||||
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
|
||||
}
|
||||
})()
|
||||
""" # noqa: E501
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
date: str
|
||||
|
||||
def get_template_data(self, args, kwargs: Kwargs, slots, context):
|
||||
return {
|
||||
"date": kwargs.date,
|
||||
}
|
||||
|
||||
# NOTE: When a full template is rendered (without the `#` syntax), the output should be as usual.
|
||||
request = HttpRequest()
|
||||
result = render(request, "integration_template_partials.html")
|
||||
content = result.content
|
||||
|
||||
assert b"<!-- _RENDERED" not in content
|
||||
assert b"width: 200px;" in content
|
||||
assert b'alert("Clicked calendar!")' in content
|
||||
|
||||
# NOTE: When a partial is rendered with the `#` syntax, what *actually*
|
||||
# gets rendered is `TemplateProxy` from template_partials, instead of Django's own `Template` class.
|
||||
# Hence, the monkeypatching that we've done on the Template class does NOT apply to TemplateProxy.
|
||||
# So we want to check that the result HAS its CSS/JS processed, which means that the monkeypatching
|
||||
# works as expected.
|
||||
request2 = HttpRequest()
|
||||
result2 = render(request2, "integration_template_partials.html#test___partial")
|
||||
content2 = result2.content
|
||||
|
||||
assert b"<!-- _RENDERED" not in content2
|
||||
assert b"width: 200px;" in content2
|
||||
assert b'alert("Clicked calendar!")' in content2
|
|
@ -581,16 +581,18 @@ class TestExtendsCompat:
|
|||
# NOTE: It's important that the <script> tags are rendered outside of <div> and <outer> tags,
|
||||
# because that tells us that the JS/CSS is rendered by the parent template, not the component
|
||||
# inside the include.
|
||||
expected = """
|
||||
# NOTE 2: The IDs differ when rendered as part of whole test suite vs as a single test.
|
||||
comp_id = "ca1bc41" if "ca1bc41" in rendered else "ca1bc40"
|
||||
expected = f"""
|
||||
<body>
|
||||
<outer>
|
||||
<div data-djc-id-ca1bc41>Hello</div>
|
||||
<div data-djc-id-{comp_id}>Hello</div>
|
||||
</outer>
|
||||
<script src="django_components/django_components.min.js"></script>
|
||||
<script type="application/json" data-djc>{"loadedCssUrls": ["c3R5bGUuY3Nz"],
|
||||
<script type="application/json" data-djc>{{"loadedCssUrls": ["c3R5bGUuY3Nz"],
|
||||
"loadedJsUrls": ["c2NyaXB0Lmpz"],
|
||||
"toLoadCssTags": [],
|
||||
"toLoadJsTags": []}</script>
|
||||
"toLoadJsTags": []}}</script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
"""
|
||||
|
@ -636,17 +638,20 @@ class TestExtendsCompat:
|
|||
template_obj = Template(template)
|
||||
context = Context()
|
||||
rendered = template_obj.render(context)
|
||||
expected = """
|
||||
|
||||
# NOTE: The IDs differ when rendered as part of whole test suite vs as a single test.
|
||||
comp_id = "ca1bc45" if "ca1bc45" in rendered else "ca1bc44"
|
||||
expected = f"""
|
||||
<html>
|
||||
<body data-djc-id-ca1bc43>
|
||||
<outer>
|
||||
<div data-djc-id-ca1bc45>Hello</div>
|
||||
<div data-djc-id-{comp_id}>Hello</div>
|
||||
</outer>
|
||||
<script src="django_components/django_components.min.js"></script>
|
||||
<script type="application/json" data-djc>{"loadedCssUrls": ["c3R5bGUuY3Nz"],
|
||||
<script type="application/json" data-djc>{{"loadedCssUrls": ["c3R5bGUuY3Nz"],
|
||||
"loadedJsUrls": ["c2NyaXB0Lmpz"],
|
||||
"toLoadCssTags": [],
|
||||
"toLoadJsTags": []}</script>
|
||||
"toLoadJsTags": []}}</script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
2
tox.ini
2
tox.ini
|
@ -44,6 +44,8 @@ deps =
|
|||
requests
|
||||
types-requests
|
||||
whitenoise
|
||||
# NOTE: Delete when Django 5.2 reaches end of life
|
||||
django-template-partials
|
||||
commands = pytest {posargs}
|
||||
|
||||
[testenv:flake8]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue