kopia lustrzana https://github.com/wagtail/wagtail
rodzic
cce05fb1a3
commit
10c1e12285
|
@ -61,6 +61,7 @@ Changelog
|
|||
* Maintenance: Update Willow upper bound to 2.x (Dan Braghis)
|
||||
* Maintenance: Removed support for Django < 4.2 (Dan Braghis)
|
||||
* Maintenance: Refactor page explorer index template to extend generic index template (Sage Abdullah)
|
||||
* Maintenance: Replace template components implementation with standalone `laces` library (Tibor Leupold)
|
||||
|
||||
|
||||
5.2.2 (06.12.2023)
|
||||
|
|
|
@ -81,6 +81,7 @@ This release adds support for Django 5.0. The support has also been backported t
|
|||
* Upgrade `ruff` and replace `black` with `ruff format` (John-Scott Atlakson)
|
||||
* Update Willow upper bound to 2.x (Dan Braghis)
|
||||
* Refactor page explorer index template to extend generic index template (Sage Abdullah)
|
||||
* Replace template components implementation with standalone `laces` library (Tibor Leupold)
|
||||
|
||||
## Upgrade considerations - removal of deprecated features from Wagtail 4.2 - 5.1
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -36,6 +36,7 @@ install_requires = [
|
|||
"openpyxl>=3.0.10,<4.0",
|
||||
"anyascii>=0.1.5",
|
||||
"telepath>=0.1.1,<1",
|
||||
"laces>=0.1,<0.2",
|
||||
]
|
||||
|
||||
# Testing dependencies
|
||||
|
|
|
@ -19,11 +19,12 @@ from django.urls import reverse
|
|||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.html import avoid_wrapping, conditional_escape, json_script
|
||||
from django.utils.html import avoid_wrapping, json_script
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timesince import timesince
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from laces.templatetags.laces import component
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.admin.admin_url_finder import AdminURLFinder
|
||||
|
@ -934,109 +935,6 @@ def resolve_url(url):
|
|||
return ""
|
||||
|
||||
|
||||
class ComponentNode(template.Node):
|
||||
def __init__(
|
||||
self,
|
||||
component,
|
||||
extra_context=None,
|
||||
isolated_context=False,
|
||||
fallback_render_method=None,
|
||||
target_var=None,
|
||||
):
|
||||
self.component = component
|
||||
self.extra_context = extra_context or {}
|
||||
self.isolated_context = isolated_context
|
||||
self.fallback_render_method = fallback_render_method
|
||||
self.target_var = target_var
|
||||
|
||||
def render(self, context: Context) -> str:
|
||||
# Render a component by calling its render_html method, passing request and context from the
|
||||
# calling template.
|
||||
# If fallback_render_method is true, objects without a render_html method will have render()
|
||||
# called instead (with no arguments) - this is to provide deprecation path for things that have
|
||||
# been newly upgraded to use the component pattern.
|
||||
|
||||
component = self.component.resolve(context)
|
||||
|
||||
if self.fallback_render_method:
|
||||
fallback_render_method = self.fallback_render_method.resolve(context)
|
||||
else:
|
||||
fallback_render_method = False
|
||||
|
||||
values = {
|
||||
name: var.resolve(context) for name, var in self.extra_context.items()
|
||||
}
|
||||
|
||||
if hasattr(component, "render_html"):
|
||||
if self.isolated_context:
|
||||
html = component.render_html(context.new(values))
|
||||
else:
|
||||
with context.push(**values):
|
||||
html = component.render_html(context)
|
||||
elif fallback_render_method and hasattr(component, "render"):
|
||||
html = component.render()
|
||||
else:
|
||||
raise ValueError(f"Cannot render {component!r} as a component")
|
||||
|
||||
if self.target_var:
|
||||
context[self.target_var] = html
|
||||
return ""
|
||||
else:
|
||||
if context.autoescape:
|
||||
html = conditional_escape(html)
|
||||
return html
|
||||
|
||||
|
||||
@register.tag(name="component")
|
||||
def component(parser, token):
|
||||
bits = token.split_contents()[1:]
|
||||
if not bits:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'component' tag requires at least one argument, the component object"
|
||||
)
|
||||
|
||||
component = parser.compile_filter(bits.pop(0))
|
||||
|
||||
# the only valid keyword argument immediately following the component
|
||||
# is fallback_render_method
|
||||
flags = token_kwargs(bits, parser)
|
||||
fallback_render_method = flags.pop("fallback_render_method", None)
|
||||
if flags:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'component' tag only accepts 'fallback_render_method' as a keyword argument"
|
||||
)
|
||||
|
||||
extra_context = {}
|
||||
isolated_context = False
|
||||
target_var = None
|
||||
|
||||
while bits:
|
||||
bit = bits.pop(0)
|
||||
if bit == "with":
|
||||
extra_context = token_kwargs(bits, parser)
|
||||
elif bit == "only":
|
||||
isolated_context = True
|
||||
elif bit == "as":
|
||||
try:
|
||||
target_var = bits.pop(0)
|
||||
except IndexError:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'component' tag with 'as' must be followed by a variable name"
|
||||
)
|
||||
else:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'component' tag received an unknown argument: %r" % bit
|
||||
)
|
||||
|
||||
return ComponentNode(
|
||||
component,
|
||||
extra_context=extra_context,
|
||||
isolated_context=isolated_context,
|
||||
fallback_render_method=fallback_render_method,
|
||||
target_var=target_var,
|
||||
)
|
||||
|
||||
|
||||
class FragmentNode(template.Node):
|
||||
def __init__(self, nodelist, target_var):
|
||||
self.nodelist = nodelist
|
||||
|
@ -1247,3 +1145,8 @@ def human_readable_date(date, description=None, placement="top"):
|
|||
"description": description,
|
||||
"placement": placement,
|
||||
}
|
||||
|
||||
|
||||
# Shadow the laces `component` tag which was extracted from Wagtail. The shadowing
|
||||
# is useful to avoid having to update all the templates that use the `component` tag.
|
||||
register.tag("component", component)
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.template import Context, Template, TemplateSyntaxError
|
|||
from django.test import SimpleTestCase, TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from freezegun import freeze_time
|
||||
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
|
@ -20,7 +19,6 @@ from wagtail.admin.templatetags.wagtailadmin_tags import (
|
|||
timesince_simple,
|
||||
)
|
||||
from wagtail.admin.templatetags.wagtailadmin_tags import locales as locales_tag
|
||||
from wagtail.admin.ui.components import Component
|
||||
from wagtail.images.tests.utils import get_test_image_file
|
||||
from wagtail.models import Locale
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
@ -222,89 +220,6 @@ class TestTimesinceTags(SimpleTestCase):
|
|||
self.assertIn('data-w-tooltip-placement-value="bottom"', html)
|
||||
|
||||
|
||||
class TestComponentTag(SimpleTestCase):
|
||||
def test_passing_context_to_component(self):
|
||||
class MyComponent(Component):
|
||||
def render_html(self, parent_context):
|
||||
return format_html(
|
||||
"<h1>{} was here</h1>", parent_context.get("first_name", "nobody")
|
||||
)
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}{% with first_name='Kilroy' %}{% component my_component %}{% endwith %}"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "<h1>Kilroy was here</h1>")
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}{% component my_component with first_name='Kilroy' %}"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "<h1>Kilroy was here</h1>")
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}{% with first_name='Kilroy' %}{% component my_component with surname='Silk' only %}{% endwith %}"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "<h1>nobody was here</h1>")
|
||||
|
||||
def test_fallback_render_method(self):
|
||||
class MyComponent(Component):
|
||||
def render_html(self, parent_context):
|
||||
return format_html("<h1>I am a component</h1>")
|
||||
|
||||
class MyNonComponent:
|
||||
def render(self):
|
||||
return format_html("<h1>I am not a component</h1>")
|
||||
|
||||
template = Template("{% load wagtailadmin_tags %}{% component my_component %}")
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "<h1>I am a component</h1>")
|
||||
with self.assertRaises(ValueError):
|
||||
template.render(Context({"my_component": MyNonComponent()}))
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}{% component my_component fallback_render_method=True %}"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "<h1>I am a component</h1>")
|
||||
html = template.render(Context({"my_component": MyNonComponent()}))
|
||||
self.assertEqual(html, "<h1>I am not a component</h1>")
|
||||
|
||||
def test_component_escapes_unsafe_strings(self):
|
||||
class MyComponent(Component):
|
||||
def render_html(self, parent_context):
|
||||
return "Look, I'm running with scissors! 8< 8< 8<"
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}<h1>{% component my_component %}</h1>"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(
|
||||
html, "<h1>Look, I'm running with scissors! 8< 8< 8<</h1>"
|
||||
)
|
||||
|
||||
def test_error_on_rendering_non_component(self):
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}<h1>{% component my_component %}</h1>"
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
template.render(Context({"my_component": "hello"}))
|
||||
self.assertEqual(str(cm.exception), "Cannot render 'hello' as a component")
|
||||
|
||||
def test_render_as_var(self):
|
||||
class MyComponent(Component):
|
||||
def render_html(self, parent_context):
|
||||
return format_html("<h1>I am a component</h1>")
|
||||
|
||||
template = Template(
|
||||
"{% load wagtailadmin_tags %}{% component my_component as my_html %}The result was: {{ my_html }}"
|
||||
)
|
||||
html = template.render(Context({"my_component": MyComponent()}))
|
||||
self.assertEqual(html, "The result was: <h1>I am a component</h1>")
|
||||
|
||||
|
||||
@override_settings(
|
||||
WAGTAIL_CONTENT_LANGUAGES=[
|
||||
("en", "English"),
|
||||
|
|
|
@ -1,36 +1,2 @@
|
|||
from typing import Any, MutableMapping
|
||||
|
||||
from django.forms import Media, MediaDefiningClass
|
||||
from django.template import Context
|
||||
from django.template.loader import get_template
|
||||
|
||||
|
||||
class Component(metaclass=MediaDefiningClass):
|
||||
def get_context_data(
|
||||
self, parent_context: MutableMapping[str, Any]
|
||||
) -> MutableMapping[str, Any]:
|
||||
return {}
|
||||
|
||||
def render_html(self, parent_context: MutableMapping[str, Any] = None) -> str:
|
||||
if parent_context is None:
|
||||
parent_context = Context()
|
||||
context_data = self.get_context_data(parent_context)
|
||||
if context_data is None:
|
||||
raise TypeError("Expected a dict from get_context_data, got None")
|
||||
|
||||
template = get_template(self.template_name)
|
||||
return template.render(context_data)
|
||||
|
||||
|
||||
class MediaContainer(list):
|
||||
"""
|
||||
A list that provides a ``media`` property that combines the media definitions
|
||||
of its members.
|
||||
"""
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
media = Media()
|
||||
for item in self:
|
||||
media += item.media
|
||||
return media
|
||||
# Import components from the Laces library which was extracted from Wagtail.
|
||||
from laces.components import Component, MediaContainer # noqa: F401
|
||||
|
|
Ładowanie…
Reference in New Issue