From 0a68a2994be17ed2669accead331881cb0be41dd Mon Sep 17 00:00:00 2001
From: Jannis Leidel <jannis@leidel.info>
Date: Sat, 7 Jul 2012 15:30:25 +0200
Subject: [PATCH] Fixed #18254 -- Added ability to the static template tags to
 store the result in a contextt variable. Many thanks to Andrei Antoukh for
 the initial patch.

---
 .../staticfiles/templatetags/staticfiles.py   | 30 ++++++++-
 django/templatetags/static.py                 | 63 +++++++++++++++++--
 docs/ref/contrib/staticfiles.txt              | 11 ++++
 docs/ref/templates/builtins.txt               | 11 ++++
 .../staticfiles_tests/tests.py                | 12 ++--
 tests/regressiontests/templates/tests.py      |  2 +
 6 files changed, 118 insertions(+), 11 deletions(-)

diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py
index 788f06ec16..71339ea8cd 100644
--- a/django/contrib/staticfiles/templatetags/staticfiles.py
+++ b/django/contrib/staticfiles/templatetags/staticfiles.py
@@ -1,13 +1,37 @@
 from django import template
+from django.templatetags.static import StaticNode
 from django.contrib.staticfiles.storage import staticfiles_storage
 
 register = template.Library()
 
 
-@register.simple_tag
-def static(path):
+class StaticFilesNode(StaticNode):
+
+    def url(self, context):
+        path = self.path.resolve(context)
+        return staticfiles_storage.url(path)
+
+
+@register.tag('static')
+def do_static(parser, token):
     """
     A template tag that returns the URL to a file
     using staticfiles' storage backend
+
+    Usage::
+
+        {% static path [as varname] %}
+
+    Examples::
+
+        {% static "myapp/css/base.css" %}
+        {% static variable_with_path %}
+        {% static "myapp/css/base.css" as admin_base_css %}
+        {% static variable_with_path as varname %}
+
     """
-    return staticfiles_storage.url(path)
+    return StaticFilesNode.handle_token(parser, token)
+
+
+def static(path):
+    return StaticNode.handle_simple(path)
diff --git a/django/templatetags/static.py b/django/templatetags/static.py
index cba44378c3..4b2d66d521 100644
--- a/django/templatetags/static.py
+++ b/django/templatetags/static.py
@@ -1,9 +1,11 @@
 from urlparse import urljoin
 from django import template
+from django.template.base import Node
 from django.utils.encoding import iri_to_uri
 
 register = template.Library()
 
+
 class PrefixNode(template.Node):
 
     def __repr__(self):
@@ -48,6 +50,7 @@ class PrefixNode(template.Node):
         context[self.varname] = prefix
         return ''
 
+
 @register.tag
 def get_static_prefix(parser, token):
     """
@@ -66,6 +69,7 @@ def get_static_prefix(parser, token):
     """
     return PrefixNode.handle_token(parser, token, "STATIC_URL")
 
+
 @register.tag
 def get_media_prefix(parser, token):
     """
@@ -84,19 +88,70 @@ def get_media_prefix(parser, token):
     """
     return PrefixNode.handle_token(parser, token, "MEDIA_URL")
 
-@register.simple_tag
-def static(path):
+
+class StaticNode(Node):
+    def __init__(self, varname=None, path=None):
+        if path is None:
+            raise template.TemplateSyntaxError(
+                "Static template nodes must be given a path to return.")
+        self.path = path
+        self.varname = varname
+
+    def url(self, context):
+        path = self.path.resolve(context)
+        return self.handle_simple(path)
+
+    def render(self, context):
+        url = self.url(context)
+        if self.varname is None:
+            return url
+        context[self.varname] = url
+        return ''
+
+    @classmethod
+    def handle_simple(cls, path):
+        return urljoin(PrefixNode.handle_simple("STATIC_URL"), path)
+
+    @classmethod
+    def handle_token(cls, parser, token):
+        """
+        Class method to parse prefix node and return a Node.
+        """
+        bits = token.split_contents()
+
+        if len(bits) < 2:
+            raise template.TemplateSyntaxError(
+                "'%s' takes at least one argument (path to file)" % bits[0])
+
+        path = parser.compile_filter(bits[1])
+
+        if len(bits) >= 2 and bits[-2] == 'as':
+            varname = bits[3]
+        else:
+            varname = None
+
+        return cls(varname, path)
+
+
+@register.tag('static')
+def do_static(parser, token):
     """
     Joins the given path with the STATIC_URL setting.
 
     Usage::
 
-        {% static path %}
+        {% static path [as varname] %}
 
     Examples::
 
         {% static "myapp/css/base.css" %}
         {% static variable_with_path %}
+        {% static "myapp/css/base.css" as admin_base_css %}
+        {% static variable_with_path as varname %}
 
     """
-    return urljoin(PrefixNode.handle_simple("STATIC_URL"), path)
+    return StaticNode.handle_token(parser, token)
+
+
+def static(path):
+    return StaticNode.handle_simple(path)
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
index 126bcdd4e6..f5557dff91 100644
--- a/docs/ref/contrib/staticfiles.txt
+++ b/docs/ref/contrib/staticfiles.txt
@@ -387,6 +387,17 @@ The previous example is equal to calling the ``url`` method of an instance of
 useful when using a non-local storage backend to deploy files as documented
 in :ref:`staticfiles-from-cdn`.
 
+.. versionadded:: 1.5
+
+If you'd like to retrieve a static URL without displaying it, you can use a
+slightly different call::
+
+.. code-block:: html+django
+
+    {% load static from staticfiles %}
+    {% static "images/hi.jpg" as myphoto %}
+    <img src="{{ myphoto }}" alt="Hi!" />
+
 Other Helpers
 =============
 
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index cf228d72f6..71f57acdbf 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -2354,6 +2354,17 @@ It is also able to consume standard context variables, e.g. assuming a
     {% load static %}
     <link rel="stylesheet" href="{% static user_stylesheet %}" type="text/css" media="screen" />
 
+If you'd like to retrieve a static URL without displaying it, you can use a
+slightly different call::
+
+.. versionadded:: 1.5
+
+.. code-block:: html+django
+
+    {% load static %}
+    {% static "images/hi.jpg" as myphoto %}
+    <img src="{{ myphoto }}"></img>
+
 .. note::
 
     The :mod:`staticfiles<django.contrib.staticfiles>` contrib app also ships
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
index 8321fc2365..7678d7f100 100644
--- a/tests/regressiontests/staticfiles_tests/tests.py
+++ b/tests/regressiontests/staticfiles_tests/tests.py
@@ -87,14 +87,16 @@ class BaseStaticFilesTestCase(object):
             template = loader.get_template_from_string(template)
         return template.render(Context(kwargs)).strip()
 
-    def static_template_snippet(self, path):
+    def static_template_snippet(self, path, asvar=False):
+        if asvar:
+            return "{%% load static from staticfiles %%}{%% static '%s' as var %%}{{ var }}" % path
         return "{%% load static from staticfiles %%}{%% static '%s' %%}" % path
 
-    def assertStaticRenders(self, path, result, **kwargs):
-        template = self.static_template_snippet(path)
+    def assertStaticRenders(self, path, result, asvar=False, **kwargs):
+        template = self.static_template_snippet(path, asvar)
         self.assertEqual(self.render_template(template, **kwargs), result)
 
-    def assertStaticRaises(self, exc, path, result, **kwargs):
+    def assertStaticRaises(self, exc, path, result, asvar=False, **kwargs):
         self.assertRaises(exc, self.assertStaticRenders, path, result, **kwargs)
 
 
@@ -368,6 +370,8 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
                                 "/static/does/not/exist.png")
         self.assertStaticRenders("test/file.txt",
                                  "/static/test/file.dad0999e4f8f.txt")
+        self.assertStaticRenders("test/file.txt",
+                                 "/static/test/file.dad0999e4f8f.txt", asvar=True)
         self.assertStaticRenders("cached/styles.css",
                                  "/static/cached/styles.93b1147e8552.css")
         self.assertStaticRenders("path/",
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 35d01221ab..4aa71f9709 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -1616,6 +1616,8 @@ class Templates(unittest.TestCase):
             'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL),
             'static-statictag01': ('{% load static %}{% static "admin/base.css" %}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')),
             'static-statictag02': ('{% load static %}{% static base_css %}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')),
+            'static-statictag03': ('{% load static %}{% static "admin/base.css" as foo %}{{ foo }}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')),
+            'static-statictag04': ('{% load static %}{% static base_css as foo %}{{ foo }}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')),
 
             # Verbatim template tag outputs contents without rendering.
             'verbatim-tag01': ('{% verbatim %}{{bare   }}{% endverbatim %}', {}, '{{bare   }}'),