Repository: wagtail/wagtail

Issue:
wagtail.images.views.serve.generate_signature should be relocated
I've encountered an issue with wagtail.images.views.serve.generate_signature.  When importing it into a models.py file, the class wagtail.images.views.serve.ServeView is created, which invokes a call to get_image_model, which in turn leads to a AppRegistryNotReady exception.

The functions generate_signature and verify_signature are not used at all in wagtail.images.views.serve. In fact, generate_signature is used in wagtail itself only in wagtail.images.views.images, and verify_signature is not used at all. It would make more sense to move these two functions to wagtail.images.views, or wagtail.images.utils, so that importing them does not result in the aforementioned side-effects.

One could then import these functions into wagtail.images.views.serve so as not to break existing code.


Discussion:
Same problem here - it makes it impossible to use `generate_signature` in a models.py file without using a lazy import - my use case it adding a custom property to a Model that gets an image url using `generate_signature` and returning it via an `APIField` for that model.
I do not see why not move the two functions to wagtail.images.utils
You are welcome to open a pull request ;)

Resolving patch:
diff --git a/wagtail/images/utils.py b/wagtail/images/utils.py
--- a/wagtail/images/utils.py
+++ b/wagtail/images/utils.py
@@ -1,7 +1,13 @@
-# Helper functions for migrating the Rendition.filter foreign key to the filter_spec field,
-# and the corresponding reverse migration
+import base64
+import hashlib
+import hmac
+
+from django.conf import settings
+from django.utils.encoding import force_str
 
 
+# Helper functions for migrating the Rendition.filter foreign key to the filter_spec field,
+# and the corresponding reverse migration
 def get_fill_filter_spec_migrations(app_name, rendition_model_name):
 
     def fill_filter_spec_forward(apps, schema_editor):
@@ -60,3 +66,21 @@ def parse_color_string(color_string):
         raise ValueError('Color string must be either 3 or 6 hexadecimal digits long')
 
     return r, g, b
+
+
+def generate_signature(image_id, filter_spec, key=None):
+    if key is None:
+        key = settings.SECRET_KEY
+
+    # Key must be a bytes object
+    if isinstance(key, str):
+        key = key.encode()
+
+    # Based on libthumbor hmac generation
+    # https://github.com/thumbor/libthumbor/blob/b19dc58cf84787e08c8e397ab322e86268bb4345/libthumbor/crypto.py#L50
+    url = '{}/{}/'.format(image_id, filter_spec)
+    return force_str(base64.urlsafe_b64encode(hmac.new(key, url.encode(), hashlib.sha1).digest()))
+
+
+def verify_signature(signature, image_id, filter_spec, key=None):
+    return force_str(signature) == generate_signature(image_id, filter_spec, key=key)
diff --git a/wagtail/images/views/images.py b/wagtail/images/views/images.py
--- a/wagtail/images/views/images.py
+++ b/wagtail/images/views/images.py
@@ -20,7 +20,7 @@
 from wagtail.images.forms import URLGeneratorForm, get_image_form
 from wagtail.images.models import Filter, SourceImageIOError
 from wagtail.images.permissions import permission_policy
-from wagtail.images.views.serve import generate_signature
+from wagtail.images.utils import generate_signature
 from wagtail.search import index as search_index
 
 permission_checker = PermissionPolicyChecker(permission_policy)
diff --git a/wagtail/images/views/serve.py b/wagtail/images/views/serve.py
--- a/wagtail/images/views/serve.py
+++ b/wagtail/images/views/serve.py
@@ -1,42 +1,20 @@
-import base64
-import hashlib
-import hmac
 import imghdr
 from wsgiref.util import FileWrapper
 
-from django.conf import settings
 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_str
 from django.views.generic import View
 
 from wagtail.images import get_image_model
 from wagtail.images.exceptions import InvalidFilterSpecError
 from wagtail.images.models import SourceImageIOError
+from wagtail.images.utils import generate_signature, verify_signature
 from wagtail.utils.sendfile import sendfile
 
 
-def generate_signature(image_id, filter_spec, key=None):
-    if key is None:
-        key = settings.SECRET_KEY
-
-    # Key must be a bytes object
-    if isinstance(key, str):
-        key = key.encode()
-
-    # Based on libthumbor hmac generation
-    # https://github.com/thumbor/libthumbor/blob/b19dc58cf84787e08c8e397ab322e86268bb4345/libthumbor/crypto.py#L50
-    url = '{}/{}/'.format(image_id, filter_spec)
-    return force_str(base64.urlsafe_b64encode(hmac.new(key, url.encode(), hashlib.sha1).digest()))
-
-
-def verify_signature(signature, image_id, filter_spec, key=None):
-    return force_str(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))


Test patch:
diff --git a/wagtail/images/tests/test_admin_views.py b/wagtail/images/tests/test_admin_views.py
--- a/wagtail/images/tests/test_admin_views.py
+++ b/wagtail/images/tests/test_admin_views.py
@@ -9,7 +9,7 @@
 
 from wagtail.core.models import Collection, GroupCollectionPermission
 from wagtail.images.models import UploadedImage
-from wagtail.images.views.serve import generate_signature
+from wagtail.images.utils import generate_signature
 from wagtail.tests.testapp.models import CustomImage, CustomImageWithAuthor
 from wagtail.tests.utils import WagtailTestUtils
 
diff --git a/wagtail/images/tests/tests.py b/wagtail/images/tests/tests.py
--- a/wagtail/images/tests/tests.py
+++ b/wagtail/images/tests/tests.py
@@ -14,7 +14,8 @@
 from wagtail.images.forms import get_image_form
 from wagtail.images.models import Image as WagtailImage
 from wagtail.images.rect import Rect, Vector
-from wagtail.images.views.serve import ServeView, generate_signature, verify_signature
+from wagtail.images.utils import generate_signature, verify_signature
+from wagtail.images.views.serve import ServeView
 from wagtail.tests.testapp.models import CustomImage, CustomImageFilePath
 from wagtail.tests.utils import WagtailTestUtils