Repository: wagtail/wagtail

Issue:
image tag runs query on template load
Theres a database query in the `__init__` method of `ImageNode`:

https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailimages/templatetags/wagtailimages_tags.py#L42

This causes a query to be generated every time the template is loaded. The filter is not actually needed until the node renders though.

This can cause crashes if you are trying to load a template without a database (such as doing offline compression of assets).



Discussion:
Offline compression has to render the template as well (so that references to `{{ STATIC_URL }}` within the script/link tags get expanded, for example), so surely that's going to fail in the absence of a database either way...?

Yes, that's true. I guess I'll have to work around this by making a temporary SQLite database. Thanks for the quick reply!

Thinking about this again. Does offline compression really render the template? How would it do it without a context?

According to https://github.com/torchbox/wagtail/issues/541#issuecomment-75430982 this bit of code is also causing issues for MySQL users

django-compressor renders the template using a predefined context defined in settings: http://django-compressor.readthedocs.org/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE_CONTEXT

According to that doc: "when rendering the _contents_ of `{% compress %}` template tags"

Will an `{% image %}` tag ever appear in the contents of a `{% compress %}` tag?

Good point! I was under the impression that it rendered the entire template in order to discover where the `{% compress %}` tags were, but on closer inspection I see there's code to [step through the parse tree specifically looking for compress nodes](https://github.com/django-compressor/django-compressor/blob/develop/compressor/offline/django.py#L137) and just render those. My mistake!


Resolving patch:
diff --git a/wagtail/wagtailcore/management/commands/fixtree.py b/wagtail/wagtailcore/management/commands/fixtree.py
--- a/wagtail/wagtailcore/management/commands/fixtree.py
+++ b/wagtail/wagtailcore/management/commands/fixtree.py
@@ -20,6 +20,11 @@ class Command(BaseCommand):
     )
     option_list = BaseCommand.option_list + base_options
 
+    def numberlist_to_string(self, numberlist):
+        # Converts a list of numbers into a string
+        # Doesn't put "L" after longs
+        return '[' + ', '.join(map(str, numberlist)) + ']'
+
     def handle(self, **options):
         any_problems_fixed = False
 
@@ -34,9 +39,9 @@ def handle(self, **options):
         (bad_alpha, bad_path, orphans, bad_depth, bad_numchild) = Page.find_problems()
 
         if bad_depth:
-            self.stdout.write("Incorrect depth value found for pages: %r" % bad_depth)
+            self.stdout.write("Incorrect depth value found for pages: %s" % self.numberlist_to_string(bad_depth))
         if bad_numchild:
-            self.stdout.write("Incorrect numchild value found for pages: %r" % bad_numchild)
+            self.stdout.write("Incorrect numchild value found for pages: %s" % self.numberlist_to_string(bad_numchild))
 
         if bad_depth or bad_numchild:
             Page.fix_tree(destructive=False)
@@ -89,15 +94,15 @@ def handle(self, **options):
         if any((bad_alpha, bad_path, orphans, bad_depth, bad_numchild)):
             self.stdout.write("Remaining problems (cannot fix automatically):")
             if bad_alpha:
-                self.stdout.write("Invalid characters found in path for pages: %r" % bad_alpha)
+                self.stdout.write("Invalid characters found in path for pages: %s" % self.numberlist_to_string(bad_alpha))
             if bad_path:
-                self.stdout.write("Invalid path length found for pages: %r" % bad_path)
+                self.stdout.write("Invalid path length found for pages: %s" % self.numberlist_to_string(bad_path))
             if orphans:
-                self.stdout.write("Orphaned pages found: %r" % orphans)
+                self.stdout.write("Orphaned pages found: %s" % self.numberlist_to_string(orphans))
             if bad_depth:
-                self.stdout.write("Incorrect depth value found for pages: %r" % bad_depth)
+                self.stdout.write("Incorrect depth value found for pages: %s" % self.numberlist_to_string(bad_depth))
             if bad_numchild:
-                self.stdout.write("Incorrect numchild value found for pages: %r" % bad_numchild)
+                self.stdout.write("Incorrect numchild value found for pages: %s" % self.numberlist_to_string(bad_numchild))
 
         elif any_problems_fixed:
             self.stdout.write("All problems fixed.")
diff --git a/wagtail/wagtailimages/templatetags/wagtailimages_tags.py b/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
--- a/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
+++ b/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
@@ -1,12 +1,10 @@
 from django import template
+from django.utils.functional import cached_property
 
 from wagtail.wagtailimages.models import Filter, SourceImageIOError
 
 register = template.Library()
 
-# Local cache of filters, avoid hitting the DB
-filters = {}
-
 
 @register.tag(name="image")
 def image(parser, token):
@@ -37,10 +35,12 @@ def __init__(self, image_var_name, filter_spec, output_var_name=None, attrs={}):
         self.image_var = template.Variable(image_var_name)
         self.output_var_name = output_var_name
         self.attrs = attrs
+        self.filter_spec = filter_spec
 
-        if filter_spec not in filters:
-            filters[filter_spec], _ = Filter.objects.get_or_create(spec=filter_spec)
-        self.filter = filters[filter_spec]
+    @cached_property
+    def filter(self):
+        _filter, _ = Filter.objects.get_or_create(spec=self.filter_spec)
+        return _filter
 
     def render(self, context):
         try:


Test patch:
diff --git a/wagtail/tests/settings.py b/wagtail/tests/settings.py
--- a/wagtail/tests/settings.py
+++ b/wagtail/tests/settings.py
@@ -13,6 +13,9 @@
     'default': {
         'ENGINE': os.environ.get('DATABASE_ENGINE', 'django.db.backends.sqlite3'),
         'NAME': os.environ.get('DATABASE_NAME', 'wagtail'),
+        'USER': os.environ.get('DATABASE_USER', None),
+        'PASSWORD': os.environ.get('DATABASE_PASS', None),
+        'HOST': os.environ.get('DATABASE_HOST', None),
     }
 }