kopia lustrzana https://github.com/wagtail/wagtail
rodzic
68ac218c4f
commit
d1fbb2e262
|
|
@ -14,6 +14,7 @@ Changelog
|
|||
* Use `requests` to access oEmbed endpoints, for more robust SSL certificate handling (Matt Westcott)
|
||||
* Ensure that bulk deletion views respect protected foreign keys (Sage Abdullah)
|
||||
* Add minimum length validation for `RichTextBlock` and `RichTextField` (Alec Baron)
|
||||
* Support `preserve-svg` in Jinja2 image tags (Vishesh Garg)
|
||||
* Fix: Handle lazy translation strings as `preview_value` for `RichTextBlock` (Seb Corbin)
|
||||
* Fix: Fix handling of newline-separated choices in form builder when using non-windows newline characters (Baptiste Mispelon)
|
||||
* Fix: Ensure `WAGTAILADMIN_LOGIN_URL` is respected when logging out of the admin (Antoine Rodriguez, Ramon de Jezus)
|
||||
|
|
|
|||
|
|
@ -887,6 +887,7 @@
|
|||
* Gorlik
|
||||
* manu
|
||||
* Maciek Baron
|
||||
* Vishesh Garg
|
||||
|
||||
## Translators
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,12 @@ Or resize an image and retrieve the resized image object (rendition) for more be
|
|||
<div class="wrapper" style="background-image: url({{ background.url }});"></div>
|
||||
```
|
||||
|
||||
When working with SVG images, you can use `preserve_svg` in the filter string to prevent operations that would require rasterizing the SVG. When preserve_svg is set to True and the image is an SVG, operations that would require rasterization (like format conversion) will be automatically filtered out, ensuring SVGs remain as vector graphics. This is especially useful in loops processing both raster images and SVGs.
|
||||
|
||||
```html+jinja
|
||||
{{ image(page.svg_image, "width-400|format-webp|preserve_svg") }}
|
||||
```
|
||||
|
||||
See [](image_tag) for more information
|
||||
|
||||
### `srcset_image()`
|
||||
|
|
@ -108,6 +114,12 @@ Or resize an image and retrieve the renditions for more bespoke use:
|
|||
<div class="wrapper" style="background-image: image-set(url({{ bg.renditions[0].url }}) 1x, url({{ bg.renditions[1].url }}) 2x);"></div>
|
||||
```
|
||||
|
||||
When working with SVG images, you can use `preserve_svg` in the filter string to prevent operations that would require rasterizing the SVG.
|
||||
|
||||
```html+jinja
|
||||
{{ srcset_image(page.svg_image, "width-400|format-webp|preserve_svg") }}
|
||||
```
|
||||
|
||||
### `picture()`
|
||||
|
||||
Resize or convert an image, rendering a `<picture>` tag including multiple `source` formats with `srcset` for multiple sizes, and a fallback `<img>` tag.
|
||||
|
|
@ -152,6 +164,12 @@ Or resize an image and retrieve the renditions for more bespoke use:
|
|||
<div class="wrapper" style="background-image: image-set(url({{ bg.formats['avif'][0].url }}) 1x type('image/avif'), url({{ bg.formats['avif'][1].url }}) 2x type('image/avif'), url({{ bg.formats['jpeg'][0].url }}) 1x type('image/jpeg'), url({{ bg.formats['jpeg'][1].url }}) 2x type('image/jpeg'));"></div>
|
||||
```
|
||||
|
||||
For SVG images, you can use `preserve_svg` in the filter string to ensure they remain as vector graphics:
|
||||
|
||||
```html+jinja
|
||||
{{ picture(page.header_image, "format-{avif,webp,jpeg}|width-{400,800}|preserve_svg", sizes="80vw") }}
|
||||
```
|
||||
|
||||
### `|richtext`
|
||||
|
||||
Transform Wagtail's internal HTML representation, expanding internal references to pages and images.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ depth: 1
|
|||
* Use `requests` to access oEmbed endpoints, for more robust SSL certificate handling (Matt Westcott)
|
||||
* Ensure that bulk deletion views respect protected foreign keys (Sage Abdullah)
|
||||
* Add minimum length validation for `RichTextBlock` and `RichTextField` (Alec Baron)
|
||||
* Support `preserve-svg` in Jinja2 image tags (Vishesh Garg)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from jinja2.ext import Extension
|
|||
from .models import Filter, Picture, ResponsiveImage
|
||||
from .shortcuts import get_rendition_or_not_found, get_renditions_or_not_found
|
||||
from .templatetags.wagtailimages_tags import image_url
|
||||
from .utils import to_svg_safe_spec
|
||||
|
||||
|
||||
def image(image, filterspec, **attrs):
|
||||
|
|
@ -16,6 +17,10 @@ def image(image, filterspec, **attrs):
|
|||
"(given filter: {})".format(filterspec)
|
||||
)
|
||||
|
||||
preserve_svg = "preserve-svg" in filterspec.split("|")
|
||||
if preserve_svg and image.is_svg():
|
||||
filterspec = to_svg_safe_spec(filterspec)
|
||||
|
||||
rendition = get_rendition_or_not_found(image, filterspec)
|
||||
|
||||
if attrs:
|
||||
|
|
@ -34,6 +39,10 @@ def srcset_image(image, filterspec, **attrs):
|
|||
"(given filter: {})".format(filterspec)
|
||||
)
|
||||
|
||||
preserve_svg = "preserve-svg" in filterspec.split("|")
|
||||
if preserve_svg and image.is_svg():
|
||||
filterspec = to_svg_safe_spec(filterspec)
|
||||
|
||||
specs = Filter.expand_spec(filterspec)
|
||||
renditions = get_renditions_or_not_found(image, specs)
|
||||
|
||||
|
|
@ -50,6 +59,10 @@ def picture(image, filterspec, **attrs):
|
|||
"(given filter: {})".format(filterspec)
|
||||
)
|
||||
|
||||
preserve_svg = "preserve-svg" in filterspec.split("|")
|
||||
if preserve_svg and image.is_svg():
|
||||
filterspec = to_svg_safe_spec(filterspec)
|
||||
|
||||
specs = Filter.expand_spec(filterspec)
|
||||
renditions = get_renditions_or_not_found(image, specs)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from wagtail.images.models import Image
|
||||
from wagtail.images.tests.utils import (
|
||||
get_test_image_file,
|
||||
get_test_image_file_svg,
|
||||
)
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestJinja2SVGSupport(WagtailTestUtils, TestCase):
|
||||
"""Test SVG support in Jinja2 templates with preserve-svg filter."""
|
||||
|
||||
def setUp(self):
|
||||
# Create a real test engine
|
||||
from django.template.loader import engines
|
||||
|
||||
self.engine = engines["jinja2"]
|
||||
|
||||
# Create a raster image
|
||||
self.raster_image = Image.objects.create(
|
||||
title="Test raster image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
# Create an SVG image
|
||||
self.svg_image = Image.objects.create(
|
||||
title="Test SVG image",
|
||||
file=get_test_image_file_svg(),
|
||||
)
|
||||
|
||||
def render(self, string, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
template = self.engine.from_string(string)
|
||||
return template.render(context)
|
||||
|
||||
def test_image_with_raster_image(self):
|
||||
"""Test that raster images work normally without preserve-svg."""
|
||||
html = self.render(
|
||||
'{{ image(img, "width-200|format-webp") }}', {"img": self.raster_image}
|
||||
)
|
||||
|
||||
self.assertIn('width="200"', html)
|
||||
self.assertIn(".webp", html) # Format conversion applied
|
||||
|
||||
def test_image_with_svg_without_preserve(self):
|
||||
"""Test that without preserve-svg, SVGs get all operations (which would fail in production)."""
|
||||
with self.assertRaises(AttributeError):
|
||||
self.render(
|
||||
'{{ image(img, "width-200|format-webp") }}', {"img": self.svg_image}
|
||||
)
|
||||
|
||||
def test_image_with_svg_with_preserve(self):
|
||||
"""Test that with preserve-svg filter, SVGs only get safe operations."""
|
||||
html = self.render(
|
||||
'{{ image(img, "width-200|format-webp|preserve-svg") }}',
|
||||
{"img": self.svg_image},
|
||||
)
|
||||
|
||||
# Check the SVG is preserved
|
||||
self.assertIn(".svg", html)
|
||||
self.assertNotIn(".webp", html)
|
||||
|
||||
def test_srcset_image_with_svg_preserve(self):
|
||||
"""Test that preserve-svg works with srcset_image function."""
|
||||
html = self.render(
|
||||
'{{ srcset_image(img, "width-{200,400}|format-webp|preserve-svg", sizes="100vw") }}',
|
||||
{"img": self.svg_image},
|
||||
)
|
||||
|
||||
# Should preserve SVG format
|
||||
self.assertIn(".svg", html)
|
||||
self.assertNotIn(".webp", html)
|
||||
|
||||
def test_picture_with_svg_preserve(self):
|
||||
"""Test that preserve-svg works with picture function."""
|
||||
html = self.render(
|
||||
'{{ picture(img, "format-{avif,webp,jpeg}|width-400|preserve-svg") }}',
|
||||
{"img": self.svg_image},
|
||||
)
|
||||
|
||||
# Should preserve SVG format
|
||||
self.assertIn(".svg", html)
|
||||
self.assertNotIn(".webp", html)
|
||||
self.assertNotIn(".avif", html)
|
||||
self.assertNotIn(".jpeg", html)
|
||||
|
||||
def test_preserve_svg_with_multiple_operations(self):
|
||||
"""Test preserve-svg with multiple operations, some safe, some unsafe for SVGs."""
|
||||
html = self.render(
|
||||
'{{ image(img, "width-300|height-200|format-webp|fill-100x100|jpegquality-80|preserve-svg") }}',
|
||||
{"img": self.svg_image},
|
||||
)
|
||||
|
||||
# Should preserve SVG format
|
||||
self.assertIn(".svg", html)
|
||||
self.assertNotIn(".webp", html)
|
||||
self.assertNotIn("jpegquality-80", html)
|
||||
|
||||
def test_preserve_svg_with_custom_attributes(self):
|
||||
"""Test preserve-svg works with custom HTML attributes."""
|
||||
html = self.render(
|
||||
'{{ image(img, "width-200|format-webp|preserve-svg", class="my-image", alt="Custom alt") }}',
|
||||
{"img": self.svg_image},
|
||||
)
|
||||
|
||||
# Check custom attributes are present
|
||||
self.assertIn('class="my-image"', html)
|
||||
self.assertIn('alt="Custom alt"', html)
|
||||
|
||||
# SVG should be preserved
|
||||
self.assertNotIn(".webp", html)
|
||||
self.assertIn(".svg", html)
|
||||
|
|
@ -117,6 +117,7 @@ def to_svg_safe_spec(filter_specs):
|
|||
"""
|
||||
if isinstance(filter_specs, str):
|
||||
filter_specs = filter_specs.split("|")
|
||||
|
||||
svg_preserving_specs = [
|
||||
"max",
|
||||
"min",
|
||||
|
|
@ -126,9 +127,17 @@ def to_svg_safe_spec(filter_specs):
|
|||
"fill",
|
||||
"original",
|
||||
]
|
||||
|
||||
# Keep only safe operations and remove preserve-svg
|
||||
safe_specs = [
|
||||
x
|
||||
for x in filter_specs
|
||||
if any(x.startswith(prefix) for prefix in svg_preserving_specs)
|
||||
and x != "preserve-svg"
|
||||
]
|
||||
|
||||
# If no safe operations remain, use 'original'
|
||||
if not safe_specs:
|
||||
return "original"
|
||||
|
||||
return "|".join(safe_specs)
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue