Skip to content
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

2461 dynamic image url redux #4856

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 21 additions & 14 deletions docs/advanced_topics/images/image_serve_view.rst
Expand Up @@ -57,21 +57,10 @@ client over an API or used directly in the template.
One advantage of using dynamic image URLs in the template is that they do not
block the initial response while rendering like the ``{% image %}`` tag does.

.. code-block:: python

from django.urls import reverse
from wagtail.images.views.serve import generate_signature

def generate_image_url(image, filter_spec):
signature = generate_signature(image.id, filter_spec)
url = reverse('wagtailimages_serve', args=(signature, image.id, filter_spec))
The ``generate_image_url`` function in ``wagtail.images.views.serve`` is a convenience
method to generate a dynamic image URL.

# Append image's original filename to the URL (optional)
url += image.file.name[len('original_images/'):]

return url

And here's an example of this being used in a view:
Here's an example of this being used in a view:

.. code-block:: python

Expand All @@ -92,6 +81,24 @@ Image operations can be chained by joining them with a ``|`` character:
})


In your templates:

.. code-block:: html+django

{% load wagtailimages_tags %}
...

<!-- Get the url for the image scaled to a width of 400 pixels: -->
{% image_url page.photo "width-400" %}

<!-- Again, but this time as a square thumbnail: -->
{% image_url page.photo "fill-100x100|jpegquality-40" %}

<!-- This time using our custom image serve view: -->
{% image_url page.photo "width-400" "mycustomview_serve" %}

You can pass an optional view name that will be used to serve the image through. The default is ``wagtailimages_serve``

Advanced configuration
======================

Expand Down
2 changes: 2 additions & 0 deletions wagtail/images/jinja2tags.py
@@ -1,6 +1,7 @@
from jinja2.ext import Extension

from .shortcuts import get_rendition_or_not_found
from .templatetags.wagtailimages_tags import image_url


def image(image, filterspec, **attrs):
Expand All @@ -21,6 +22,7 @@ def __init__(self, environment):

self.environment.globals.update({
'image': image,
'image_url': image_url,
})


Expand Down
15 changes: 15 additions & 0 deletions wagtail/images/templatetags/wagtailimages_tags.py
@@ -1,10 +1,14 @@
import re

from django import template
from django.core.exceptions import ImproperlyConfigured
from django.urls import NoReverseMatch
from django.utils.functional import cached_property

from wagtail.images.models import Filter
from wagtail.images.shortcuts import get_rendition_or_not_found
from wagtail.images.views.serve import generate_image_url


register = template.Library()
allowed_filter_pattern = re.compile(r"^[A-Za-z0-9_\-\.]+$")
Expand Down Expand Up @@ -107,3 +111,14 @@ def render(self, context):
for key in self.attrs:
resolved_attrs[key] = self.attrs[key].resolve(context)
return rendition.img_tag(resolved_attrs)


@register.simple_tag()
def image_url(image, filter_spec, viewname='wagtailimages_serve'):
try:
return generate_image_url(image, filter_spec, viewname)
except NoReverseMatch:
raise ImproperlyConfigured(
"'image_url' tag requires the " + viewname + " view to be configured. Please see "
"https://docs.wagtail.io/en/stable/advanced_topics/images/image_serve_view.html#setup for instructions."
)
12 changes: 12 additions & 0 deletions wagtail/images/tests/test_jinja2.py
Expand Up @@ -78,3 +78,15 @@ def test_missing_image(self):
self.render('{{ image(myimage, "width-200") }}', {'myimage': self.bad_image}),
'<img alt="missing image" src="/media/not-found" width="0" height="0">'
)

def test_image_url(self):
self.assertRegex(
self.render('{{ image_url(myimage, "width-200") }}', {'myimage': self.image}),
'/images/.*/width-200/{}'.format(self.image.file.name.split('/')[-1]),
)

def test_image_url_custom_view(self):
self.assertRegex(
self.render('{{ image_url(myimage, "width-200", "wagtailimages_serve_custom_view") }}', {'myimage': self.image}),
'/testimages/custom_view/.*/width-200/{}'.format(self.image.file.name.split('/')[-1]),
)
32 changes: 32 additions & 0 deletions wagtail/images/tests/tests.py
Expand Up @@ -146,6 +146,38 @@ def test_no_image_filter_provided_but_attributes_provided(self):
context = template.Context({'image_obj': self.image})
temp.render(context)

def render_image_url_tag(self, image, view_name):
temp = template.Template(
'{% load wagtailimages_tags %}{% image_url image_obj "width-400" "' + view_name + '" %}'
)
context = template.Context({'image_obj': image})
return temp.render(context)

def test_image_url(self):
result = self.render_image_url_tag(self.image, 'wagtailimages_serve')
self.assertRegex(
result,
'/images/.*/width-400/{}'.format(self.image.file.name.split('/')[-1]),
)

def test_image_url_custom_view(self):
result = self.render_image_url_tag(self.image, 'wagtailimages_serve_custom_view')

self.assertRegex(
result,
'/testimages/custom_view/.*/width-400/{}'.format(self.image.file.name.split('/')[-1]),
)


def test_image_url_no_imageserve_view_added(self):
# if image_url tag is used, but the image serve view was not defined.
with self.assertRaises(ImproperlyConfigured):
temp = template.Template(
'{% load wagtailimages_tags %}{% image_url image_obj "width-400" "mynonexistingimageserve_view" %}'
)
context = template.Context({'image_obj': self.image})
temp.render(context)


class TestMissingImage(TestCase):
"""
Expand Down
1 change: 1 addition & 0 deletions wagtail/images/tests/urls.py
Expand Up @@ -7,6 +7,7 @@
url(r'^actions/serve/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(action='serve'), name='wagtailimages_serve_action_serve'),
url(r'^actions/redirect/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(action='redirect'), name='wagtailimages_serve_action_redirect'),
url(r'^custom_key/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(key='custom'), name='wagtailimages_serve_custom_key'),
url(r'^custom_view/([^/]*)/(\d*)/([^/]*)/[^/]*$', ServeView.as_view(), name='wagtailimages_serve_custom_view'),
url(r'^sendfile/(.*)/(\d*)/(.*)/[^/]*', SendFileView.as_view(), name='wagtailimages_sendfile'),
url(r'^sendfile-dummy/(.*)/(\d*)/(.*)/[^/]*', SendFileView.as_view(backend=dummy_sendfile_backend.sendfile), name='wagtailimages_sendfile_dummy'),
]
8 changes: 8 additions & 0 deletions wagtail/images/views/serve.py
Expand Up @@ -8,6 +8,7 @@
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.http import HttpResponse, HttpResponsePermanentRedirect, StreamingHttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text
from django.views.generic import View
Expand Down Expand Up @@ -36,6 +37,13 @@ def verify_signature(signature, image_id, filter_spec, key=None):
return force_text(signature) == generate_signature(image_id, filter_spec, key=key)


def generate_image_url(image, filter_spec, viewname='wagtailimages_serve', key=None):
signature = generate_signature(image.id, filter_spec, key)
url = reverse(viewname, args=(signature, image.id, filter_spec))
url += image.file.name[len('original_images/'):]
return url


class ServeView(View):
model = get_image_model()
action = 'serve'
Expand Down