insert_above is integrated for adding js/css on the fly
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Wed, 21 Nov 2012 12:47:19 +0000 (13:47 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Wed, 21 Nov 2012 12:47:19 +0000 (13:47 +0100)
a dummy 'slice' app for playing with the rendering engine

19 files changed:
Makefile
auth/templates/auth.html [moved from templates/auth.html with 100% similarity]
devel/django-insert-above-1.0.4/PKG-INFO [new file with mode: 0644]
devel/django-insert-above-1.0.4/README [new file with mode: 0644]
devel/django-insert-above-1.0.4/setup.py [new file with mode: 0644]
devel/django-install.txt
insert_above/__init__.py [new file with mode: 0644]
insert_above/templatetags/__init__.py [new file with mode: 0644]
insert_above/templatetags/insert_tags.py [new file with mode: 0644]
myslice/settings.py
myslice/urls.py
slice/__init__.py [new file with mode: 0644]
slice/models.py [new file with mode: 0644]
slice/templates/foo-base.html [new file with mode: 0644]
slice/templates/foo-menu.html [new file with mode: 0644]
slice/templates/foo.html [new file with mode: 0644]
slice/tests.py [new file with mode: 0644]
slice/views.py [new file with mode: 0644]
templates/myslice.html [new file with mode: 0644]

index d4fd130..99fb321 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,7 @@
+# in general it's right to rely on the contents as reported by git
 tags:
        git ls-files | xargs etags
+
+# however sometimes we have stuff not yet added, so in this case
+ftags:
+       find . -type f | fgrep -v '/.git/' | xargs etags
similarity index 100%
rename from templates/auth.html
rename to auth/templates/auth.html
diff --git a/devel/django-insert-above-1.0.4/PKG-INFO b/devel/django-insert-above-1.0.4/PKG-INFO
new file mode 100644 (file)
index 0000000..b3686a6
--- /dev/null
@@ -0,0 +1,235 @@
+Metadata-Version: 1.0
+Name: django-insert-above
+Version: 1.0.4
+Summary: These django templatetags is a hack making possible to insert "content" in some (maybe above the current or parent template) places.
+Home-page: https://github.com/yunmanger1/django-insert-above/
+Author: German Ilyin
+Author-email: germanilyin@gmail.com
+License: WTFPL
+Description: WHAT IS IT?
+        -----------
+        
+        These templatetags is a hack making possible to insert 'content' in
+        some (maybe above the current or parent template) places.
+        
+        More specifically, when we use these tags, there are some Nodes called
+        'containers' that are rendered after all other nodes are rendered, but placed
+        in it's right posistion. Using this hack, 'containers' may render 
+        depending on variables in context that were generated by nodes placed anywhere in
+        template (maybe after it).
+        
+        It's very useful in cases when you are reusing some template parts
+        very often. For example displaying comment list and comment submit form.
+        We write some template and put it into comments.html. Then every time 
+        we need comments we just {% include "comments.html" %}.
+        But what if this part needs some js or css? Then we need to create 
+        some comments-jscss.html and override some {% block head %}. IMHO this
+        is quite inconvenient.
+        
+        Using this tool we can insert js and css into head 
+        directly from comments.html 
+        
+        MOTIVATION
+        ----------
+        
+        1. Create convenient way to include media resources in head of HTML page.
+        2. Handle repetition of resource includes.
+        3. Make it possible to require resources from included templates.
+        4. Keep the order of resource includes from different places.
+        
+        INSTALL
+        -------
+        
+        1. (required) add 'insert_above' in INSTALLED_APPS in your settings.py
+        
+        2. (optional) add these two lines of code somewhere in your project where
+        they will run for sure. For example in urls.py
+        
+        ~~~~
+        from django.template.loader import add_to_builtins
+        add_to_builtins('insert_above.templatetags.insert_tags')
+        ~~~~
+        
+        TAGS & FILTERS
+        --------------
+        
+        1. {% insert_handler %}
+        2. {% container name %}
+        3. {% media_container name %}
+        4. {% insert_str container str %}
+        5. {% insert container %}{% endinsert %}
+        6. media_tag filter simply converts `ga.js` into `<script type='text/javascript' src='/static/ga.js'></script>`
+        
+        RESTRICTIONS
+        ------------
+        
+        1. `{% container %}` or `{% media_container %}` tags must NOT be in other `{% block %}`.
+        2. `{% insert_handler %}` ought to be at ther very beginning of base template.
+        
+        VARIABLES
+        ---------
+        
+        1. `IA_USE_MEDIA_PREFIX`, by default True
+        2. `IA_MEDIA_PREFIX`, if not set `STATIC_URL` is used, if not set `MEDIA_URL` is used, if not set '/media/' is used
+        3. `DEBUG`, if True logs how much time spent on rendering
+        4. `IA_JS_FORMAT`, by default `<script type='text/javascript' src='{URL}'></script>`
+        5. `IA_CSS_FORMAT`, by default `<link rel='stylesheet' href='{URL}' type='text/css' />`
+        6. `IA_MEDIA_EXTENSION_FORMAT_MAP`, by default `{'css' : CSS_FORMAT, '.js' : JS_FORMAT}`
+        
+        EXAMPLE
+        -------
+        
+        Let's analyze an example. 
+        
+        base.html
+        
+        ~~~~{.html}
+        {% insert_handler %}
+        <html>
+        <head>
+        <script>
+        <script type='text/javascript' src='{{ MEDIA_URL }}js/jquery.min.js'></script> 
+        {% media_container media %}
+        
+        $(document).ready(function(){
+        {% container ready %}
+        });
+        </script>
+        </head>
+        <body>
+        {% block content %}
+        {% endblock %}
+        </body>
+        </html>
+        ~~~~
+        
+        Base template creating blocks and containers..
+        
+        blog/base.html
+        
+        ~~~~{.html}
+        {% extends "base.html" %}
+        
+        {% block content %}
+        {% insert_str media "js/mathjax.js" %}
+            {% block header %}{% endblock %}
+            {% block menu %}{% include "blog/menu.html" %}{% endblock %}
+            {% block text %}{% endblock %}
+            {% block footer %}{% endblock %}
+        {% endblock %}
+        ~~~~
+        
+        Extending content block. Requiring js/mathjax.js resource into 'media' container.
+        
+        blog/menu.html
+        
+        ~~~~{.html}
+        {% insert_str media "js/animated.menu.js" %}
+        {% insert_str media "css/animated.menu.css" %}
+        {% insert ready %}
+            $('ul.menu').each(function(){
+                $(this).superanimation();
+            });
+        {% endinsert %}
+        <ul id='blog-menu' class='menu'>
+         <li>link</li>
+         <li>link</li>
+         <li>link</li>
+        </ul>
+        ~~~~
+        
+        Requiring js/animated.menu.js and css/animated.menu.css into "media" container.
+        Inserting javascript code into "ready" container.
+        
+        blog/post_detail.html
+        
+        ~~~~{.html}
+        {% extends "blog/base.html" %}
+        
+        {% block header %}{{ title }}{% endblock %}
+        
+        {% block text %}
+        {% insert_str media "js/mathjax.js" %}
+        {{ text }}
+        {% endblock %}
+        
+        {% block footer %}
+        <hr>
+        {% endblock %}
+        ~~~~
+        
+        Implementing blocks and requiring js/mathjax.js into media.
+        
+        
+        So if we render 
+        Template('blog/post_detail.html').render(Context({'title': 'Hello', 'text': 'World'}))
+        we will get:
+        
+        ~~~~{.html}
+        <html>
+        <head>
+        <script>
+        <script type='text/javascript' src='/media/js/jquery.min.js'></script> 
+        <script type='text/javascript' src='/media/js/mathjax.js'></script>
+        <script type='text/javascript' src='/media/js/animated.menu.js'></script>
+        <link rel="stylesheet" href="/media/css/animated.menu.css" type="text/css" />
+        
+        $(document).ready(function(){
+            $('ul.menu').each(function(){
+                $(this).superanimation();
+            });
+        });
+        </script>
+        </head>
+        <body>
+        Hello
+        <ul id='blog-menu' class='menu'>
+         <li>link</li>
+         <li>link</li>
+         <li>link</li>
+        </ul>
+        World
+        <hr>
+        </body>
+        </html>
+        ~~~~
+        
+        What shall be noted?
+        -------------------
+        
+        1. `js/mathjax.js` automatically becomes `<script type='text/javascript' src='/media/js/mathjax.js'></script>`
+        and `css/animated.menu.css` becomes `<link rel="stylesheet" href="/media/css/animated.menu.css" type="text/css" />`
+        2. inserting from included template is possible
+        3. any text may be inserted to any container. Here we insert javascript code in  `$(document).ready(function(){});`
+        4. `js/mathjax.js` was required twice, but included only once.
+        5. The order of included resources is kept.
+        
+        FIXTURES
+        --------
+        
+        ### version 1.0.2
+        
+         + **fix MEDIA_URL setting**
+         if STATIC_URL is not set in settings, it's value is None by default in new versions of Django.
+         Now we check if STATIC_URL is None, then use MEDIA_URL
+        
+        ### version 1.0.4
+        
+         + added new tag `{% insert_form container form %}`
+         + added new tag `{% insert_form container form.media %}`
+        
+        ## TODOs
+        
+        1. testing
+        2. extending tags
+        3. resource bulking
+        
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Topic :: Utilities
+Classifier: Environment :: Plugins
+Classifier: Framework :: Django
+Classifier: Intended Audience :: Developers
+Classifier: License :: Freeware
+Classifier: Programming Language :: Python :: 2.6
diff --git a/devel/django-insert-above-1.0.4/README b/devel/django-insert-above-1.0.4/README
new file mode 100644 (file)
index 0000000..0aaa62d
--- /dev/null
@@ -0,0 +1,218 @@
+WHAT IS IT?
+-----------
+
+These templatetags is a hack making possible to insert 'content' in
+some (maybe above the current or parent template) places.
+
+More specifically, when we use these tags, there are some Nodes called
+'containers' that are rendered after all other nodes are rendered, but placed
+in it's right posistion. Using this hack, 'containers' may render 
+depending on variables in context that were generated by nodes placed anywhere in
+template (maybe after it).
+
+It's very useful in cases when you are reusing some template parts
+very often. For example displaying comment list and comment submit form.
+We write some template and put it into comments.html. Then every time 
+we need comments we just {% include "comments.html" %}.
+But what if this part needs some js or css? Then we need to create 
+some comments-jscss.html and override some {% block head %}. IMHO this
+is quite inconvenient.
+
+Using this tool we can insert js and css into head 
+directly from comments.html 
+
+MOTIVATION
+----------
+
+1. Create convenient way to include media resources in head of HTML page.
+2. Handle repetition of resource includes.
+3. Make it possible to require resources from included templates.
+4. Keep the order of resource includes from different places.
+
+INSTALL
+-------
+
+1. (required) add 'insert_above' in INSTALLED_APPS in your settings.py
+
+2. (optional) add these two lines of code somewhere in your project where
+they will run for sure. For example in urls.py
+
+~~~~
+from django.template.loader import add_to_builtins
+add_to_builtins('insert_above.templatetags.insert_tags')
+~~~~
+
+TAGS & FILTERS
+--------------
+
+1. {% insert_handler %}
+2. {% container name %}
+3. {% media_container name %}
+4. {% insert_str container str %}
+5. {% insert container %}{% endinsert %}
+6. media_tag filter simply converts `ga.js` into `<script type='text/javascript' src='/static/ga.js'></script>`
+
+RESTRICTIONS
+------------
+
+1. `{% container %}` or `{% media_container %}` tags must NOT be in other `{% block %}`.
+2. `{% insert_handler %}` ought to be at ther very beginning of base template.
+
+VARIABLES
+---------
+
+1. `IA_USE_MEDIA_PREFIX`, by default True
+2. `IA_MEDIA_PREFIX`, if not set `STATIC_URL` is used, if not set `MEDIA_URL` is used, if not set '/media/' is used
+3. `DEBUG`, if True logs how much time spent on rendering
+4. `IA_JS_FORMAT`, by default `<script type='text/javascript' src='{URL}'></script>`
+5. `IA_CSS_FORMAT`, by default `<link rel='stylesheet' href='{URL}' type='text/css' />`
+6. `IA_MEDIA_EXTENSION_FORMAT_MAP`, by default `{'css' : CSS_FORMAT, '.js' : JS_FORMAT}`
+
+EXAMPLE
+-------
+
+Let's analyze an example. 
+
+base.html
+
+~~~~{.html}
+{% insert_handler %}
+<html>
+<head>
+<script>
+<script type='text/javascript' src='{{ MEDIA_URL }}js/jquery.min.js'></script> 
+{% media_container media %}
+
+$(document).ready(function(){
+{% container ready %}
+});
+</script>
+</head>
+<body>
+{% block content %}
+{% endblock %}
+</body>
+</html>
+~~~~
+
+Base template creating blocks and containers..
+
+blog/base.html
+
+~~~~{.html}
+{% extends "base.html" %}
+
+{% block content %}
+{% insert_str media "js/mathjax.js" %}
+    {% block header %}{% endblock %}
+    {% block menu %}{% include "blog/menu.html" %}{% endblock %}
+    {% block text %}{% endblock %}
+    {% block footer %}{% endblock %}
+{% endblock %}
+~~~~
+
+Extending content block. Requiring js/mathjax.js resource into 'media' container.
+
+blog/menu.html
+
+~~~~{.html}
+{% insert_str media "js/animated.menu.js" %}
+{% insert_str media "css/animated.menu.css" %}
+{% insert ready %}
+    $('ul.menu').each(function(){
+        $(this).superanimation();
+    });
+{% endinsert %}
+<ul id='blog-menu' class='menu'>
+ <li>link</li>
+ <li>link</li>
+ <li>link</li>
+</ul>
+~~~~
+
+Requiring js/animated.menu.js and css/animated.menu.css into "media" container.
+Inserting javascript code into "ready" container.
+
+blog/post_detail.html
+
+~~~~{.html}
+{% extends "blog/base.html" %}
+
+{% block header %}{{ title }}{% endblock %}
+
+{% block text %}
+{% insert_str media "js/mathjax.js" %}
+{{ text }}
+{% endblock %}
+
+{% block footer %}
+<hr>
+{% endblock %}
+~~~~
+
+Implementing blocks and requiring js/mathjax.js into media.
+
+
+So if we render 
+Template('blog/post_detail.html').render(Context({'title': 'Hello', 'text': 'World'}))
+we will get:
+
+~~~~{.html}
+<html>
+<head>
+<script>
+<script type='text/javascript' src='/media/js/jquery.min.js'></script> 
+<script type='text/javascript' src='/media/js/mathjax.js'></script>
+<script type='text/javascript' src='/media/js/animated.menu.js'></script>
+<link rel="stylesheet" href="/media/css/animated.menu.css" type="text/css" />
+
+$(document).ready(function(){
+    $('ul.menu').each(function(){
+        $(this).superanimation();
+    });
+});
+</script>
+</head>
+<body>
+Hello
+<ul id='blog-menu' class='menu'>
+ <li>link</li>
+ <li>link</li>
+ <li>link</li>
+</ul>
+World
+<hr>
+</body>
+</html>
+~~~~
+
+What shall be noted?
+-------------------
+
+1. `js/mathjax.js` automatically becomes `<script type='text/javascript' src='/media/js/mathjax.js'></script>`
+and `css/animated.menu.css` becomes `<link rel="stylesheet" href="/media/css/animated.menu.css" type="text/css" />`
+2. inserting from included template is possible
+3. any text may be inserted to any container. Here we insert javascript code in  `$(document).ready(function(){});`
+4. `js/mathjax.js` was required twice, but included only once.
+5. The order of included resources is kept.
+
+FIXTURES
+--------
+
+### version 1.0.2
+
+ + **fix MEDIA_URL setting**
+ if STATIC_URL is not set in settings, it's value is None by default in new versions of Django.
+ Now we check if STATIC_URL is None, then use MEDIA_URL
+
+### version 1.0.4
+
+ + added new tag `{% insert_form container form %}`
+ + added new tag `{% insert_form container form.media %}`
+
+## TODOs
+
+1. testing
+2. extending tags
+3. resource bulking
+
diff --git a/devel/django-insert-above-1.0.4/setup.py b/devel/django-insert-above-1.0.4/setup.py
new file mode 100644 (file)
index 0000000..9517c67
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+import os
+from distutils.core import setup
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(name = 'django-insert-above',
+    version = '1.0.4',
+    description = 'These django templatetags is a hack making possible to insert "content" in some (maybe above the current or parent template) places.',
+    author = 'German Ilyin',
+    author_email = 'germanilyin@gmail.com',
+    url = 'https://github.com/yunmanger1/django-insert-above/',
+    license = 'WTFPL',
+    long_description = read('README'),
+    packages = ['insert_above', 'insert_above.templatetags'],
+    classifiers = [
+        "Development Status :: 3 - Alpha",
+        "Topic :: Utilities",
+        "Environment :: Plugins",
+        "Framework :: Django",
+        "Intended Audience :: Developers",
+        "License :: Freeware",
+        "Programming Language :: Python :: 2.6",
+    ],
+)
index 3bb153f..72e83d2 100644 (file)
@@ -42,3 +42,9 @@ and I was getting a lot of warnings relating to that anyways
 ==================== postinstall
 . manage.py syncdb
 . manage.py collectstatic --noinput
+
+======================================== django-insert-above
+this package was downloaded in source doce from github and installed
+as-is as insert_above, and enabled as the README has it
+
+======================================== 
diff --git a/insert_above/__init__.py b/insert_above/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/insert_above/templatetags/__init__.py b/insert_above/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/insert_above/templatetags/insert_tags.py b/insert_above/templatetags/insert_tags.py
new file mode 100644 (file)
index 0000000..475faa1
--- /dev/null
@@ -0,0 +1,395 @@
+"""
+This program is free software. It comes without any warranty, to
+the extent permitted by applicable law. You can redistribute it
+and/or modify it under the terms of the Do What The Fuck You Want
+To Public License, Version 2, as published by Sam Hocevar. See
+http://sam.zoy.org/wtfpl/COPYING for more details.
+"""
+
+from django import template
+from django.conf import settings
+from django.template import loader_tags
+from django.utils.encoding import force_unicode
+from django.utils.safestring import mark_safe
+import time
+from django.template.base import Variable
+from django import forms
+from django.utils.datastructures import SortedDict
+register = template.Library()
+
+try:
+    from common import logwrapper
+    log = logwrapper.defaultLogger(__file__)
+except ImportError:
+    import logging
+    log = logging.getLogger(__name__)
+
+INSERT_TAG_KEY = 'insert-demands'
+DEBUG = getattr(settings, 'IA_DEBUG', False)
+MEDIA_URL = getattr(settings, 'IA_MEDIA_PREFIX', None)
+if MEDIA_URL is None:
+    MEDIA_URL = getattr(settings, 'STATIC_URL', None)
+if MEDIA_URL is None:
+    MEDIA_URL = getattr(settings, 'MEDIA_URL', None)
+if MEDIA_URL is None:
+    MEDIA_URL = '/media/'
+USE_MEDIA_PREFIX = getattr(settings, 'IA_USE_MEDIA_PREFIX', True)
+JS_FORMAT = getattr(settings, 'IA_JS_FORMAT', "<script type='text/javascript' src='{URL}'></script>")
+CSS_FORMAT = getattr(settings, 'IA_CSS_FORMAT', "<link rel='stylesheet' href='{URL}' type='text/css' />")
+
+if hasattr(settings, 'IA_MEDIA_EXTENSION_FORMAT_MAP'):
+    MEDIA_EXTENSION_FORMAT_MAP = settings.IA_MEDIA_EXTENSION_FORMAT_MAP
+else:
+    # by convention key must be 3 characters length. This helps to optimize lookup process
+    MEDIA_EXTENSION_FORMAT_MAP = {
+        'css' : CSS_FORMAT,
+        '.js' : JS_FORMAT,
+    }
+
+def render_media(extension, ctx):
+    """
+    Renders media format. Used in media container.
+    """
+    fmt = MEDIA_EXTENSION_FORMAT_MAP[extension]
+    return fmt.format(**ctx)
+
+def get_from_context_root(context, KEY):
+    """
+    Gets or creates dictinoary in root context.
+    """
+    if not KEY in context.dicts[0]:
+        context.dicts[0].update({KEY : {}})
+    return context.dicts[0].get(KEY)
+
+def add_render_time(context, dt):
+    """
+    Adds value to root context, which will be used
+    later in insert handler node.
+    """
+    cache = get_from_context_root(context, INSERT_TAG_KEY)
+    t = cache.get('DEBUG_TIME', 0) + dt
+    cache.update({'DEBUG_TIME': t})
+
+def get_render_time(context):
+    cache = get_from_context_root(context, INSERT_TAG_KEY)
+    t = cache.get('DEBUG_TIME', 0)
+    return t
+
+def consider_time(f):
+    """
+    Decorator used to calculate 
+    how much time was spent on rendering
+    "insert_above" tags.
+    """
+    def nf(obj, context, *args, **kwargs):
+        t = time.time()
+        result = f(obj, context, *args, **kwargs)
+        dt = time.time() - t
+        add_render_time(context, dt)
+        return result
+    if DEBUG:
+        return nf
+    return f
+
+class OrderedItem(object):
+    """
+    String items all over the templates must be
+    rendered in the same order they were encountered.
+    """
+    order = 0
+
+    def __init__(self, item):
+        cur = OrderedItem.order
+        self.item, self.order = item, cur
+        OrderedItem.order = cur + 1
+
+    def __cmp__(self, o):
+        if self.item == o.item:
+            return 0
+        return self.order - o.order
+
+    def __unicode__(self):
+        return self.item
+
+    def __hash__(self):
+        return self.item.__hash__()
+
+    def __str__(self):
+        return self.__unicode__()
+
+class InsertHandlerNode(template.Node):
+    #must_be_first = True
+
+    def __init__(self, nodelist, *args, **kwargs):
+        super(InsertHandlerNode, self).__init__(*args, **kwargs)
+        self.nodelist = nodelist
+        self.blocks = dict([(n.name, n) for n in nodelist.get_nodes_by_type(template.loader_tags.BlockNode)])
+
+    def __repr__(self):
+        return '<MediaHandlerNode>'
+
+    def render_nodelist(self, nodelist, context):
+        bits = []
+        medias = []
+        index = 0
+        for node in nodelist:
+            if isinstance(node, ContainerNode):
+                node.index = index
+                bits.append('')
+                medias.append(node)
+            elif isinstance(node, template.Node):
+                bits.append(nodelist.render_node(node, context))
+            else:
+                bits.append(node)
+            index += 1
+        for node in medias:
+            bits[node.index] = nodelist.render_node(node, context)
+        if DEBUG:
+            log.debug("spent {0:.6f} ms on insert_tags".format(get_render_time(context)))
+        return mark_safe(''.join([force_unicode(b) for b in bits]))
+
+    def render(self, context):
+        if loader_tags.BLOCK_CONTEXT_KEY not in context.render_context:
+            context.render_context[loader_tags.BLOCK_CONTEXT_KEY] = loader_tags.BlockContext()
+        block_context = context.render_context[loader_tags.BLOCK_CONTEXT_KEY]
+
+        # Add the block nodes from this node to the block context
+        block_context.add_blocks(self.blocks)
+        return self.render_nodelist(self.nodelist, context)
+#        return self.nodelist.render(context)
+
+class InsertNode(template.Node):
+    def __init__(self, container_name, insert_string = None, subnodes = None, *args, **kwargs):
+        """
+        Note: `self.container_name, self.insert_line, self.subnodes` must not be changed during 
+        `render()` call. Method `render()` may be called multiple times. 
+        """
+        super(InsertNode, self).__init__(*args, **kwargs)
+        self.container_name, self.insert_line, self.subnodes = container_name, insert_string, subnodes
+        self.index = None
+        self.prev_context_hash = None
+
+    def __repr__(self):
+        return "<Media Require Node: %s>" % (self.insert_line)
+
+    def push_media(self, context):
+        if self.prev_context_hash == context.__hash__():
+            if DEBUG:
+                log.debug('same context: {0} == {1}'.format(self.prev_context_hash, context.__hash__()))
+            return
+        self.prev_context_hash = context.__hash__()
+        cache = get_from_context_root(context, INSERT_TAG_KEY)
+        reqset = cache.get(self.container_name, None)
+        if not reqset:
+            reqset = []
+            cache[self.container_name] = reqset
+        insert_content = None
+        if self.insert_line == None:
+            if self.subnodes == None:
+                raise AttributeError('insert_line or subnodes must be specified')
+            insert_content = self.subnodes.render(context)
+        else:
+            if self.subnodes != None:
+                raise AttributeError('insert_line or subnodes must be specified, not both')
+            var = True
+            insert_content = Variable(self.insert_line).resolve(context)
+        reqset.append(OrderedItem(insert_content))
+
+    @consider_time
+    def render(self, context):
+        self.push_media(context)
+        return ''
+
+class ContainerNode(template.Node):
+    def __init__(self, name, *args, **kwargs):
+        super(ContainerNode, self).__init__(*args, **kwargs)
+        self.name = name
+
+    def __repr__(self):
+        return "<Container Node: %s>" % (self.name)
+
+    @consider_time
+    def render(self, context):
+        reqset = get_from_context_root(context, INSERT_TAG_KEY).get(self.name, None)
+        if not reqset:
+            return ''
+        items = reqset
+        #items.sort()
+        return "\n".join([x.__unicode__() for x in items])
+
+def media_tag(url, **kwargs):
+    """
+    Usage: {{ url|media_tag }}
+    Simply wraps media url into appropriate HTML tag.
+    
+    Example: {{ "js/ga.js"|media_tag }} 
+    The result will be <script type='text/javascript' src='/static/js/ga.js'></script>
+    
+    Last 3 characters of url define which 
+    format string from MEDIA_EXTENSION_FORMAT_MAP will be used.  
+    """
+
+    url = url.split('\n')[0].strip()
+    ext = url[-3:]
+    full = url.startswith('http://') or url.startswith('https://')
+    if USE_MEDIA_PREFIX and not full:
+        link = '{0}{1}'.format(MEDIA_URL, url)
+    else:
+        link = url
+    return render_media(ext, {'URL' : link })
+
+
+def fetch_urls(item, url_set):
+    if isinstance(item, forms.Form):
+        item = getattr(item, 'media', None)
+        if item is None:
+            return
+
+    if isinstance(item, forms.Media):
+        css, js = None, None
+        css = getattr(item, '_css', {})
+        js = getattr(item, '_js', [])
+        if css:
+            for key, list in css.items():
+                for url in list:
+                    url_set[url] = key
+        if js:
+            for url in js:
+                url_set[url] = 1
+    elif isinstance(item, (str, unicode)):
+        url_set[item] = 1
+
+class MediaContainerNode(ContainerNode):
+
+    @consider_time
+    def render(self, context):
+        reqset = get_from_context_root(context, INSERT_TAG_KEY).get(self.name, None)
+        if not reqset:
+            return ''
+        items = reqset
+        items.sort()
+        url_set = SortedDict()
+        for obj in items:
+            fetch_urls(obj.item, url_set)
+        result = [media_tag(key) for key, value in url_set.items()]
+        if result:
+            return "\n".join(result)
+        return ''
+
+@register.tag
+def insert_handler(parser, token):
+    """
+    This is required tag for using insert_above tags. It must be 
+    specified in the very "base" template and at the very beginning.
+    
+    Simply, this tag controls the rendering of all tags after it. Note
+    that if any container node goes before this tag it won't be rendered
+    properly.
+    
+    {% insert_handler %}
+    """
+    bits = token.split_contents()
+    if len(bits) != 1:
+        raise template.TemplateSyntaxError("'%s' takes no arguments" % bits[0])
+    nodelist = parser.parse()
+    if nodelist.get_nodes_by_type(InsertHandlerNode):
+        raise template.TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])
+    return InsertHandlerNode(nodelist)
+
+@register.tag
+def container(parser, token):
+    """
+    This tag specifies some named block where items will be inserted
+    from all over the template.
+    
+    {% container js %}
+    
+    js - here is name of container
+    
+    It's set while inserting string
+     
+    {% insert_str js "<script src='js/jquery.js' type=...></script>" %}
+    """
+    bits = token.split_contents()
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError("'%s' takes one argument" % bits[0])
+    return ContainerNode(bits[1])
+
+@register.tag
+def media_container(parser, token):
+    """
+    This tag is an example of how ContainerNode might be overriden.
+    
+    {% media_container js %}
+    
+    js - here is name of container
+    
+    It's set while inserting string
+     
+    {% insert_str js "js/jquery.js" %}
+    {% insert_str js "css/style.css" %}
+    
+    Here only media urls are set. MediaContainerNode will identify
+    by last 3 characters and render on appropriate template.
+    
+    By default only '.js' and 'css' files are rendered. It can be extended
+    by setting MEDIA_EXTENSION_FORMAT_MAP variable in settings.
+    
+    """
+    bits = token.split_contents()
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError("'%s' takes one argument" % bits[0])
+    return MediaContainerNode(bits[1])
+
+@register.tag
+def insert_str(parser, token):
+    """
+    This tag inserts specified string in containers.
+    
+    Usage:     {% insert_str container_name string_to_insert %}
+    
+    Example: {% insert_str js "<script src="media/js/jquery.js"></script>" %}
+    
+    """
+    bits = token.split_contents()
+    if len(bits) != 3:
+        raise template.TemplateSyntaxError("'%s' takes two arguments" % bits[0])
+    return InsertNode(bits[1], bits[2])
+
+@register.tag
+def insert_form(parser, token):
+    """
+    This tag inserts specified string in containers.
+    
+    Usage:     {% insert_str container_name form %}
+    
+    Example: {% insert_form js form %}
+    
+    """
+    bits = token.split_contents()
+    if len(bits) != 3:
+        raise template.TemplateSyntaxError("'%s' takes two arguments" % bits[0])
+    return InsertNode(bits[1], bits[2])
+
+
+@register.tag
+def insert(parser, token):
+    """
+    This tag with end token allows to insert not only one string.
+    
+    {% insert js %}
+    <script>
+    $(document).ready(function(){
+        alert('hello, {{ user }}!');
+    });
+    </script>
+    {% endinsert %}
+    """
+    subnodes = parser.parse(('endinsert',))
+    parser.delete_first_token()
+    bits = token.contents.split()
+    if len(bits) < 2:
+        raise template.TemplateSyntaxError(u"'%r' tag requires 2 arguments." % bits[0])
+    return InsertNode(bits[1], subnodes = subnodes)
+
+register.filter('media_tag', media_tag)
index 6c9d949..31432e5 100644 (file)
@@ -9,7 +9,9 @@ ADMINS = (
 
 MANAGERS = ADMINS
 
-## guess if we run on the 'prod' site (:) or on a working laptop
+####################
+# guess if we run on the 'prod' site (:) that for now uses /root/myslice and run manage.py
+# or on a working laptop, in which case we use ~/git/myslice-django
 import os, os.path
 ROOT=''
 def init_root ():
@@ -127,7 +129,11 @@ INSTALLED_APPS = (
     'django.contrib.sites',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    # see details in devel/django-insert-above-1.0-4
+    'insert_above',
     'myslice',
+    'auth',
+    'slice',
     # Uncomment the next line to enable the admin:
     # 'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
@@ -164,3 +170,7 @@ LOGGING = {
 }
 
 AUTHENTICATION_BACKENDS = ( 'auth.backend.MyCustomBackend', )
+
+#################### for insert_above
+#IA_JS_FORMAT = "<script type='text/javascript' src='{URL}' />"
+IA_MEDIA_PREFIX = '/code/'
index 6f0ba43..26c81ae 100644 (file)
@@ -4,7 +4,12 @@ from django.conf.urls import patterns, include, url
 # from django.contrib import admin
 # admin.autodiscover()
 
-urlpatterns = patterns('',
+# to enable insert_above stuff
+from django.template.loader import add_to_builtins
+add_to_builtins('insert_above.templatetags.insert_tags')
+
+urlpatterns = patterns(
+    '',
     # Examples:
     # url(r'^$', 'myslice.views.home', name='home'),
     # url(r'^myslice/', include('myslice.foo.urls')),
@@ -15,4 +20,5 @@ urlpatterns = patterns('',
     # Uncomment the next line to enable the admin:
     # url(r'^admin/', include(admin.site.urls)),
     (r'^login/$', 'auth.views.login_user'),
+    (r'^slice/$', 'slice.views.foo'),
 )
diff --git a/slice/__init__.py b/slice/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/slice/models.py b/slice/models.py
new file mode 100644 (file)
index 0000000..71a8362
--- /dev/null
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/slice/templates/foo-base.html b/slice/templates/foo-base.html
new file mode 100644 (file)
index 0000000..8a245cb
--- /dev/null
@@ -0,0 +1,13 @@
+{% insert_handler %}
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>Foo - experimental page with foo= {{ foo }} </title>
+{% media_container media %}
+
+</head>
+
+<body>
+<h1>Again, foo = {{ foo }} </h1>
+<hr>
+{% block content %} and more stuff if needed {% endblock %}
+</body> </html>
diff --git a/slice/templates/foo-menu.html b/slice/templates/foo-menu.html
new file mode 100644 (file)
index 0000000..c13a6ab
--- /dev/null
@@ -0,0 +1,5 @@
+<ul>
+{% for k,v in menu_items.iteritems %}
+  <li> <a href="{{ v }}"> {{ k }} </a> </li>
+{% endfor %}
+</ul>
diff --git a/slice/templates/foo.html b/slice/templates/foo.html
new file mode 100644 (file)
index 0000000..eb00565
--- /dev/null
@@ -0,0 +1,8 @@
+{% extends 'foo-base.html' %}
+
+{% block content %}
+{% insert_str media "js/mathjax.js" %}
+{% insert_str media "css/mathjax.css" %}
+{{ content_string }}
+{% endblock %}
+
diff --git a/slice/tests.py b/slice/tests.py
new file mode 100644 (file)
index 0000000..501deb7
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/slice/views.py b/slice/views.py
new file mode 100644 (file)
index 0000000..9bfe787
--- /dev/null
@@ -0,0 +1,18 @@
+# Create your views here.
+from django.core.context_processors import csrf
+from django.template import RequestContext
+from django.template.loader import render_to_string
+from django.shortcuts import render_to_response
+
+def foo (request):
+    
+    content_string = render_to_string ('foo-menu.html',
+                                       {'menu_items' : {'item1':'/url1/', 'item2':'/url2',},
+                                        'current_item': 'item1'})
+    
+    result=render_to_response('foo.html',{'foo':'bar', 'content_string' : content_string },
+                              context_instance=RequestContext(request))
+
+    print 'foo : result=',result
+
+    return result
diff --git a/templates/myslice.html b/templates/myslice.html
new file mode 100644 (file)
index 0000000..ff08592
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title> {{ title }} </title>
+{{ 
+</head>
+
+<body>
+<div class='wrapper'>
+
+  <div class='top'> 
+  {% block top %}
+        <div class="header">
+          <a href="/" alt="Home"><img class="logo" src="{{ STATIC_URL }}myslice-logo.png" alt="MySlice" /></a>
+        </div>
+  {% endblock top %}
+  </div>
+  <div class='menubar'> 
+  {% block menubar %}
+    <h1> Empty menubar for now </h1>
+  {% endblock menubar %}
+  </div>
+  <div class='centered content'> 
+  {% block content %}
+    Welcome...
+  {% endblock content %}
+  </div>
+</div>
+</body> </html>