pax_global_header00006660000000000000000000000064116505455350014523gustar00rootroot0000000000000052 comment=0a7ec69cff333251c3a463bf7d4e523c6d2aff4b django-sekizai-0.5.0/000077500000000000000000000000001165054553500144245ustar00rootroot00000000000000django-sekizai-0.5.0/.gitignore000066400000000000000000000001001165054553500164030ustar00rootroot00000000000000*.pyc *~ .* !.gitignore /dist/ *egg-info* /htmlcov/ docs/_build django-sekizai-0.5.0/LICENSE000066400000000000000000000027161165054553500154370ustar00rootroot00000000000000Copyright (c) 2010, Jonas Obrist All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Jonas Obrist nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JONAS OBRIST BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.django-sekizai-0.5.0/README.rst000066400000000000000000000020361165054553500161140ustar00rootroot00000000000000Please refer to the documentation in the docs/ directory for help. About this project: The main reason I started this project was the lack of a good media (css/js) framework in django and the django-cms. Yes there is the Media class used in forms in django, but really that doesn't work that well. Usually the frontend guys want to decide on css and javascript files to be included and they don't want to have to edit Python files to change that neither did I want them to change my Python files. Therefor there was a need to allow you to edit contents of templates which are before or after the point where you are now. Also I wanted duplicates to be removed. As a result I wrote django-sekizai, which does exactly that. It's similar to blocks, just instead of inheriting them, you extend them. There are some issue/restrictions with this implementation due to how the django template language works, but if used properly it can be very useful and there are plans to make it the default media framework for plugins in the django-cms by version 2.2. django-sekizai-0.5.0/docs/000077500000000000000000000000001165054553500153545ustar00rootroot00000000000000django-sekizai-0.5.0/docs/Makefile000066400000000000000000000061061165054553500170170ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-sekizai.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-sekizai.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-sekizai-0.5.0/docs/conf.py000066400000000000000000000142701165054553500166570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # django-sekizai documentation build configuration file, created by # sphinx-quickstart on Tue Jun 29 23:12:20 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'django-sekizai' copyright = u'2010, Jonas Obrist' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. release = '0.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'django-sekizaidoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'django-sekizai.tex', u'django-sekizai Documentation', u'Jonas Obrist', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True django-sekizai-0.5.0/docs/example.rst000066400000000000000000000103661165054553500175470ustar00rootroot00000000000000####### Example ####### .. highlight:: html+django A full example on how to use django-sekizai and when. Let's assume you have a website, where all templates extend base.html, which just contains your basic HTML structure. Now you also have a small template which gets included on some pages. This template needs to load a javascript library and execute some specific javascript code. Your "base.html" might look like this:: {% load sekizai_tags %} Your website {% render_block "css" %} {% block "content" %} {% endblock %} {% render_block "js" %} As you can see, we load ``sekizai_tags`` at the very beginning. We have two sekizai namespaces: "css" and "js". The "css" namespace is rendered in the head right after the base css files, the "js" namespace is rendered at the very bottom of the body, right after we load jQuery. Now to our included template. We assume there's a context variable called ``userid`` which will be used with the javascript code. Your template ("inc.html") might look like this:: {% load sekizai_tags %}
    {% addtoblock "js" %} {% endaddtoblock %} {% addtoblock "js" %} {% endaddtoblock %} The important thing to notice here is that we split the javascript into two ``addtoblock`` blocks. Like this, the library 'mylib.js' is only included once, and the userid specific code will be included once per userid. Now to put it all together let's assume we render a third template with ``[1, 2, 3]`` as ``my_userids`` variable. The third template looks like this:: {% extends "base.html" %} {% block "content" %} {% for userid in my_userids %} {% include "inc.html" %} {% endfor %} {% endblock %} And here's the rendered template:: Your website
          django-sekizai-0.5.0/docs/helpers.rst000066400000000000000000000010531165054553500175470ustar00rootroot00000000000000####### Helpers ####### ********************** :mod:`sekizai.helpers` ********************** .. function:: get_namespaces(template) Returns a list of all sekizai namespaces found in ``template``, which should be the name of a template. This method also checks extended templates. .. function:: valdiate_template(template, namespaces) Returns ``True`` if all namespaces given are found in the template given. Useful to check that the namespaces required by your application are available, so you can failfast if they're not.django-sekizai-0.5.0/docs/index.rst000066400000000000000000000020031165054553500172100ustar00rootroot00000000000000.. django-sekizai documentation master file, created by sphinx-quickstart on Tue Jun 29 23:12:20 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to django-sekizai's documentation! ========================================== Contents: .. toctree:: :maxdepth: 2 usage restrictions helpers example Sekizai means "blocks" in Japanese, and that's what this app provides. A fresh look at blocks. With django-sekizai you can define placeholders where your blocks get rendered and at different places in your templates append to those blocks. This is especially useful for css and javascript. Your subtemplates can now define css and javscript files to be included, and the css will be nicely put at the top and the javascript to the bottom, just like you should. Also sekizai will ignore any duplicate content in a single block. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-sekizai-0.5.0/docs/restrictions.rst000066400000000000000000000022541165054553500206410ustar00rootroot00000000000000############ Restrictions ############ ******* General ******* If ``TEMPLATE_DEBUG`` is True and you do not use either ``SekizaiContext`` or ``RequestContext`` and the ``sekizai`` context processor, a ``TemplateSyntaxError`` will be raised in any template using a sekizai tag. .. _render-block-restrictions: ************ render_block ************ Following restrictions apply to this tag: * The tag **must** be in the base template. It cannot be used in an included template. * The tag **must not** be placed within a block tag (a template tag with an end tag, for example ``{% block name %}...{% endblock %}``). Generally it is recommended to have all ``render_block`` tags in your base template (the one that get's extended by all others). Having it in a template that itself extends another template is **not** recommended. Since the most common use case for django-sekizai is css and javascript files/snippets, you would most likely want those in your base template anyway. ********** addtoblock ********** Following restrictions apply to this tag: * If used in a template which *extends another template*, the ``addtoblock`` tag **must** be within one of the ``block`` tags.django-sekizai-0.5.0/docs/usage.rst000066400000000000000000000127331165054553500172200ustar00rootroot00000000000000##### Usage ##### ************* Configuration ************* In order to get started with django-sekizai, you'll need to do the following steps: * Put 'sekizai' into your ``INSTALLED_APPS`` setting. * Use one of the following: * Put ``sekizai.context_processors.sekizai`` into your ``TEMPLATE_CONTEXT_PROCESSORS`` setting and use ``django.template.RequestContext`` when rendering your templates. * Use ``sekizai.context.SekizaiContext`` when rendering your templates. ********************** Template Tag Reference ********************** .. highlight:: html+django .. note:: All sekizai template tags require the ``sekizai_tags`` template tag library to be loaded. Handling code snippets ====================== Sekizai uses ``render_block`` and ``addtoblock`` to handle unique code snippets. Define your blocks using ``{% render_block %}`` and add data to that block using ``{% addtoblock %}...{% endaddotblock %}``. Example Template:: {% load sekizai_tags %} {% render_block "css" %} Your content comes here. Maybe you want to throw in some css: {% addtoblock "css" %} {% endaddtoblock %} Some more content here. {% addtoblock "js" %} {% endaddtoblock %} And even more content. {% render_block "js" %} Above example would roughly render like this:: Your content comes here. Maybe you want to throw in some css: Some more content here. And even more content. Handling data ============= Sometimes you might not want to use code snippets but rather just add a value to a list. For this purpose there are the ``{% with_data as %}...{% end_with_data %}`` and ``{% add_data %}`` template tags. Example:: {% load sekizai_tags %} {% with_data "css-data" as stylesheets %} {% for stylesheet in stylesheets %} {% endfor %} {% end_with_data %} Your content comes here. Maybe you want to throw in some css: {% add_data "css-data" "css/stylesheet.css" %} Some more content here. Above example would roughly render like this:: Your content comes here. Maybe you want to throw in some css: Some more content here. And even more content. Sekizai data is unique ====================== All data in sekizai is enforced to be unique within its block namespace. This is because the main purpose of sekizai is to handle javascript and css dependencies in templates. A simple example using ``addtoblock`` and ``render_block`` would be:: {% load sekizai_tags %} {% addtoblock "js" %} {% endaddtoblock %} {% addtoblock "js" %} {% endaddtoblock %} {% addtoblock "js" %} {% endaddtoblock %} {% addtoblock "js" %} {% endaddtoblock %} {% render_block "js" %} Above template would roughly render to:: .. versionadded:: 0.5 Processing sekizai data ======================= Because of the :ref:`render-block-restrictions` restrictions it is not possible to use sekizai with libraries such as django-compressor directly. For that reason, sekizai added postprocessing capabilities to ``render_block`` in version 0.5. Postprocessors are callable Python objects (usually functions) that get the data in a sekizai namespace and the name of the namespace passed as arguments and should return a string. An example for a processor that uses the Django builtin spaceless functionality would be: .. code-block:: python def spaceless_post_processor(data, namespace): from django.utils.html import strip_spaces_between_tags return strip_spaces_between_tags(data) To use this post processor you have to tell ``render_block`` where it's located. If above code sample lives in the Python module ``myapp.sekizai_processors`` you could use it like this:: ... {% render_block "js" postprocessor "myapp.sekizai_processors.spaceless_post_processor" %} ... django-sekizai-0.5.0/runtests.py000066400000000000000000000023061165054553500166660ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import sys urlpatterns = [] TEMPLATE_DEBUG = True DATABASES = { 'default': { 'ENGINE': 'sqlite3', 'NAME': ':memory:' } } INSTALLED_APPS = [ 'sekizai', ] TEMPLATE_DIRS = [ os.path.join(os.path.dirname(__file__), 'test_templates'), ] TEMPLATE_CONTEXT_PROCESSORS = [ 'sekizai.context_processors.sekizai', ] ROOT_URLCONF = 'runtests' def runtests(): from django.conf import settings settings.configure( INSTALLED_APPS = INSTALLED_APPS, ROOT_URLCONF = ROOT_URLCONF, DATABASES = DATABASES, TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner', TEMPLATE_DIRS = TEMPLATE_DIRS, TEMPLATE_CONTEXT_PROCESSORS = TEMPLATE_CONTEXT_PROCESSORS, TEMPLATE_DEBUG = TEMPLATE_DEBUG ) # Run the test suite, including the extra validation tests. from django.test.utils import get_runner TestRunner = get_runner(settings) test_runner = TestRunner(verbosity=1, interactive=False, failfast=False) failures = test_runner.run_tests(INSTALLED_APPS) return failures if __name__ == "__main__": failures = runtests() if failures: sys.exit(bool(failures)) django-sekizai-0.5.0/sekizai/000077500000000000000000000000001165054553500160635ustar00rootroot00000000000000django-sekizai-0.5.0/sekizai/__init__.py000066400000000000000000000000241165054553500201700ustar00rootroot00000000000000__version__ = '0.5' django-sekizai-0.5.0/sekizai/context.py000066400000000000000000000005661165054553500201300ustar00rootroot00000000000000from django.template import Context from sekizai.context_processors import sekizai class SekizaiContext(Context): """ An alternative context to be used instead of RequestContext in places where no request is available. """ def __init__(self, *args, **kwargs): super(SekizaiContext, self).__init__(*args, **kwargs) self.update(sekizai()) django-sekizai-0.5.0/sekizai/context_processors.py000066400000000000000000000005331165054553500224040ustar00rootroot00000000000000from django.conf import settings from sekizai.data import SekizaiDictionary def sekizai(request=None): """ Simple context processor which makes sure that the SekizaiDictionary is available in all templates. """ varname = getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') return {varname: SekizaiDictionary()} django-sekizai-0.5.0/sekizai/data.py000066400000000000000000000016371165054553500173550ustar00rootroot00000000000000class SekizaiList(list): """ A sekizai namespace in a template. """ def __init__(self, namespace): self._namespace = namespace super(SekizaiList, self).__init__() def append(self, obj): """ When content gets added, run the filters for this namespace. """ if obj not in self: super(SekizaiList, self).append(obj) def render(self, between='\n'): """ When the data get's rendered, run the postprocess filters. """ return between.join(self) class SekizaiDictionary(dict): """ A dictionary which auto fills itself instead of raising key errors. """ def __init__(self): super(SekizaiDictionary, self).__init__() def __getitem__(self, item): if item not in self: self[item] = SekizaiList(item) return super(SekizaiDictionary, self).__getitem__(item) django-sekizai-0.5.0/sekizai/helpers.py000066400000000000000000000066441165054553500201110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.conf import settings from django.template import TextNode, VariableNode, NodeList from django.template.loader import get_template from django.template.loader_tags import BlockNode, ExtendsNode from sekizai.templatetags.sekizai_tags import RenderBlock def _extend_blocks(extend_node, blocks): """ Extends the dictionary `blocks` with *new* blocks in the parent node (recursive) """ # we don't support variable extensions if extend_node.parent_name_expr: return parent = extend_node.get_parent(None) # Search for new blocks for node in parent.nodelist.get_nodes_by_type(BlockNode): if not node.name in blocks: blocks[node.name] = node else: # set this node as the super node (for {{ block.super }}) block = blocks[node.name] seen_supers = [] while hasattr(block.super, 'nodelist') and block.super not in seen_supers: seen_supers.append(block.super) block = block.super block.super = node # search for further ExtendsNodes for node in parent.nodelist.get_nodes_by_type(ExtendsNode): _extend_blocks(node, blocks) break def _extend_nodelist(extend_node): """ Returns a list of namespaces found in the parent template(s) of this ExtendsNode """ # we don't support variable extensions if extend_node.parent_name_expr: return [] blocks = extend_node.blocks _extend_blocks(extend_node, blocks) found = [] for block in blocks.values(): found += _scan_namespaces(block.nodelist, block, blocks.keys()) parent_template = extend_node.get_parent({}) # if this is the topmost template, check for namespaces outside of blocks if not parent_template.nodelist.get_nodes_by_type(ExtendsNode): found += _scan_namespaces(parent_template.nodelist, None, blocks.keys()) else: found += _scan_namespaces(parent_template.nodelist, extend_node, blocks.keys()) return found def _scan_namespaces(nodelist, current_block=None, ignore_blocks=[]): found = [] for node in nodelist: # check if this is RenderBlock node if isinstance(node, RenderBlock): # resolve it's name against a dummy context found.append(node.kwargs['name'].resolve({})) found += _scan_namespaces(node.blocks['nodelist'], node) # handle {% extends ... %} tags if check_inheritance is True elif isinstance(node, ExtendsNode): found += _extend_nodelist(node) # in block nodes we have to scan for super blocks elif isinstance(node, VariableNode) and current_block: if node.filter_expression.token == 'block.super': if hasattr(current_block.super, 'nodelist'): found += _scan_namespaces(current_block.super.nodelist, current_block.super) return found def get_namespaces(template): compiled_template = get_template(template) return _scan_namespaces(compiled_template.nodelist) def validate_template(template, namespaces): """ Validates that a template (or it's parents if check_inheritance is True) contain all given namespaces """ if getattr(settings, 'SEKIZAI_IGNORE_VALIDATION', False): return True found = get_namespaces(template) for namespace in namespaces: if namespace not in found: return False return True django-sekizai-0.5.0/sekizai/models.py000066400000000000000000000000001165054553500177060ustar00rootroot00000000000000django-sekizai-0.5.0/sekizai/templatetags/000077500000000000000000000000001165054553500205555ustar00rootroot00000000000000django-sekizai-0.5.0/sekizai/templatetags/__init__.py000066400000000000000000000000001165054553500226540ustar00rootroot00000000000000django-sekizai-0.5.0/sekizai/templatetags/sekizai_tags.py000066400000000000000000000101271165054553500236050ustar00rootroot00000000000000from classytags.arguments import Argument from classytags.core import Tag, Options from classytags.parser import Parser from django import template from django.conf import settings from django.utils.importlib import import_module register = template.Library() def validate_context(context): """ Validates a given context. Returns True if the context is valid. Returns False if the context is invalid but the error should be silently ignored. Raises a TemplateSyntaxError if the context is invalid and we're in debug mode. """ if getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') in context: return True if not settings.TEMPLATE_DEBUG: return False raise template.TemplateSyntaxError( "You must enable the 'sekizai.context_processors.sekizai' template " "context processor or use 'sekizai.context.SekizaiContext' to " "render your templates." ) def import_processor(import_path): if '.' not in import_path: raise TypeError("Import paths must contain at least one '.'") module_name, object_name = import_path.rsplit('.', 1) module = import_module(module_name) return getattr(module, object_name) class SekizaiParser(Parser): def parse_blocks(self): super(SekizaiParser, self).parse_blocks() self.blocks['nodelist'] = self.parser.parse() class AddtoblockParser(Parser): def parse_blocks(self): name = self.kwargs['name'].var.token self.blocks['nodelist'] = self.parser.parse( ('endaddtoblock', 'endaddtoblock %s' % name) ) self.parser.delete_first_token() class SekizaiTag(Tag): def render(self, context): if validate_context(context): return super(SekizaiTag, self).render(context) return '' class RenderBlock(Tag): name = 'render_block' options = Options( Argument('name'), 'postprocessor', Argument('postprocessor', required=False, default=None, resolve=False), parser_class=SekizaiParser, ) def render_tag(self, context, name, postprocessor, nodelist): if not validate_context(context): return nodelist.render(context) rendered_contents = nodelist.render(context) varname = getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') data = context[varname][name].render() if postprocessor: func = import_processor(postprocessor) data = func(data, name) return '%s\n%s' % (data, rendered_contents) register.tag(RenderBlock) class AddData(SekizaiTag): name = 'add_data' options = Options( Argument('key'), Argument('value'), ) def render_tag(self, context, key, value): varname = getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') context[varname][key].append(value) return '' register.tag(AddData) class WithData(SekizaiTag): name = 'with_data' options = Options( Argument('name'), 'as', Argument('variable', resolve=False), blocks=[ ('end_with_data', 'inner_nodelist'), ], parser_class=SekizaiParser, ) def render_tag(self, context, name, variable, inner_nodelist, nodelist): rendered_contents = nodelist.render(context) varname = getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') data = context[varname][name] context.push() context[variable] = data inner_contents = inner_nodelist.render(context) context.pop() return '%s\n%s' % (inner_contents, rendered_contents) register.tag(WithData) class Addtoblock(SekizaiTag): name = 'addtoblock' options = Options( Argument('name'), parser_class=AddtoblockParser, ) def render_tag(self, context, name, nodelist): rendered_contents = nodelist.render(context) varname = getattr(settings, 'SEKIZAI_VARNAME', 'SEKIZAI_CONTENT_HOLDER') context[varname][name].append(rendered_contents) return "" register.tag(Addtoblock) django-sekizai-0.5.0/sekizai/tests.py000066400000000000000000000245311165054553500176040ustar00rootroot00000000000000from __future__ import with_statement from difflib import SequenceMatcher from django import template from django.conf import settings from django.template.loader import render_to_string from sekizai.context import SekizaiContext from sekizai.helpers import validate_template, get_namespaces from sekizai.templatetags.sekizai_tags import (validate_context, import_processor) from unittest import TestCase def null_processor(data, namespace): return '' def namespace_processor(data, namespace): return namespace class SettingsOverride(object): """ Overrides Django settings within a context and resets them to their inital values on exit. Example: with SettingsOverride(DEBUG=True): # do something """ class NULL: pass def __init__(self, **overrides): self.overrides = overrides def __enter__(self): self.old = {} for key, value in self.overrides.items(): self.old[key] = getattr(settings, key, self.NULL) setattr(settings, key, value) def __exit__(self, type, value, traceback): for key, value in self.old.items(): if value is self.NULL: delattr(settings, key) else: setattr(settings, key, value) class Match(tuple): # pragma: no cover @property def a(self): return self[0] @property def b(self): return self[1] @property def size(self): return self[2] def _backwards_compat_match(thing): # pragma: no cover if isinstance(thing, tuple): return Match(thing) return thing class BitDiffResult(object): def __init__(self, status, message): self.status = status self.message = message class BitDiff(object): """ Visual aid for failing tests """ def __init__(self, expected): self.expected = [repr(unicode(bit)) for bit in expected] def test(self, result): result = [repr(unicode(bit)) for bit in result] if self.expected == result: return BitDiffResult(True, "success") else: # pragma: no cover longest = max([len(x) for x in self.expected] + [len(x) for x in result] + [len('Expected')]) sm = SequenceMatcher() sm.set_seqs(self.expected, result) matches = sm.get_matching_blocks() lasta = 0 lastb = 0 data = [] for match in [_backwards_compat_match(match) for match in matches]: unmatcheda = self.expected[lasta:match.a] unmatchedb = result[lastb:match.b] unmatchedlen = max([len(unmatcheda), len(unmatchedb)]) unmatcheda += ['' for x in range(unmatchedlen)] unmatchedb += ['' for x in range(unmatchedlen)] for i in range(unmatchedlen): data.append((False, unmatcheda[i], unmatchedb[i])) for i in range(match.size): data.append((True, self.expected[match.a + i], result[match.b + i])) lasta = match.a + match.size lastb = match.b + match.size padlen = (longest - len('Expected')) padding = ' ' * padlen line1 = '-' * padlen line2 = '-' * (longest - len('Result')) msg = '\nExpected%s | | Result' % padding msg += '\n--------%s-|---|-------%s' % (line1, line2) for success, a, b in data: pad = ' ' * (longest - len(a)) if success: msg += '\n%s%s | | %s' % (a, pad, b) else: msg += '\n%s%s | ! | %s' % (a, pad, b) return BitDiffResult(False, msg) class SekizaiTestCase(TestCase): def _render(self, tpl, ctx={}, ctxclass=SekizaiContext): return render_to_string(tpl, ctxclass(ctx)) def _get_bits(self, tpl, ctx={}, ctxclass=SekizaiContext): rendered = self._render(tpl, ctx, ctxclass) bits = [bit for bit in [bit.strip('\n') for bit in rendered.split('\n')] if bit] return bits, rendered def _test(self, tpl, res, ctx={}, ctxclass=SekizaiContext): """ Helper method to render template and compare it's bits """ bits, rendered = self._get_bits(tpl, ctx, ctxclass) differ = BitDiff(res) result = differ.test(bits) self.assertTrue(result.status, result.message) return rendered def test_basic_dual_block(self): """ Basic dual block testing """ bits = ['my css file', 'some content', 'more content', 'final content', 'my js file'] self._test('basic.html', bits) def test_named_endaddtoblock(self): """ Testing with named endaddblock """ bits = ["mycontent"] self._test('named_end.html', bits) def test_eat_content_before_render_block(self): """ Testing that content get's eaten if no render_blocks is available """ bits = ["mycontent"] self._test("eat.html", bits) def test_sekizai_context_required(self): """ Test that the template tags properly fail if not used with either SekizaiContext or the context processor. """ self.assertRaises(template.TemplateSyntaxError, self._render, 'basic.html', {}, template.Context) def test_complex_template_inheritance(self): """ Test that (complex) template inheritances work properly """ bits = [ "head start", "some css file", "head end", "include start", "inc add js", "include end", "block main start", "extinc", "block main end", "body pre-end", "inc js file", "body end" ] self._test("inherit/extend.html", bits) """ Test that blocks (and block.super) work properly with sekizai """ bits = [ "head start", "visible css file", "some css file", "head end", "include start", "inc add js", "include end", "block main start", "block main base contents", "more contents", "block main end", "body pre-end", "inc js file", "body end" ] self._test("inherit/super_blocks.html", bits) def test_namespace_isolation(self): """ Tests that namespace isolation works """ bits = ["the same file", "the same file"] self._test('namespaces.html', bits) def test_variable_namespaces(self): """ Tests variables and filtered variables as block names. """ bits = ["file one", "file two"] self._test('variables.html', bits, {'blockname': 'one'}) def test_invalid_addtoblock(self): """ Tests that template syntax errors are raised properly in templates rendered by sekizai tags """ self.assertRaises(template.TemplateSyntaxError, self._render, 'errors/failadd.html') def test_invalid_renderblock(self): self.assertRaises(template.TemplateSyntaxError, self._render, 'errors/failrender.html') def test_invalid_include(self): self.assertRaises(template.TemplateSyntaxError, self._render, 'errors/failinc.html') def test_invalid_basetemplate(self): self.assertRaises(template.TemplateSyntaxError, self._render, 'errors/failbase.html') def test_invalid_basetemplate_two(self): self.assertRaises(template.TemplateSyntaxError, self._render, 'errors/failbase2.html') def test_with_data(self): """ Tests the with_data/add_data tags. """ bits = ["1", "2"] self._test('with_data.html', bits) def test_easy_inheritance(self): self.assertEqual('content', self._render("easy_inherit.html").strip()) def test_validate_context(self): sekizai_ctx = SekizaiContext() django_ctx = template.Context() self.assertRaises(template.TemplateSyntaxError, validate_context, django_ctx) self.assertEqual(validate_context(sekizai_ctx), True) with SettingsOverride(TEMPLATE_DEBUG=False): self.assertEqual(validate_context(django_ctx), False) self.assertEqual(validate_context(sekizai_ctx), True) bits = ['some content', 'more content', 'final content'] self._test('basic.html', bits, ctxclass=template.Context) def test_post_processor_null(self): bits = ['header', 'footer'] self._test('processors/null.html', bits) def test_post_processor_namespace(self): bits = ['header', 'footer', 'js'] self._test('processors/namespace.html', bits) def test_import_processor_failfast(self): self.assertRaises(TypeError, import_processor, 'invalidpath') class HelperTests(TestCase): def test_validate_template(self): self.assertTrue(validate_template('basic.html', ['js', 'css'])) self.assertTrue(validate_template('basic.html', ['js'])) self.assertTrue(validate_template('basic.html', ['css'])) self.assertTrue(validate_template('basic.html', [])) self.assertFalse(validate_template('basic.html', ['notfound'])) def test_get_namespaces(self): self.assertEqual(get_namespaces('easy_inherit.html'), ['css']) self.assertEqual(get_namespaces('inherit/chain.html'), ['css', 'js']) self.assertEqual(get_namespaces('inherit/spacechain.html'), ['css', 'js']) self.assertEqual(get_namespaces('inherit/varchain.html'), []) self.assertEqual(get_namespaces('inherit/subvarchain.html'), []) self.assertEqual(get_namespaces('inherit/nullext.html'), []) def test_deactivate_validate_template(self): with SettingsOverride(SEKIZAI_IGNORE_VALIDATION=True): self.assertTrue(validate_template('basic.html', ['js', 'css'])) self.assertTrue(validate_template('basic.html', ['js'])) self.assertTrue(validate_template('basic.html', ['css'])) self.assertTrue(validate_template('basic.html', [])) self.assertTrue(validate_template('basic.html', ['notfound'])) django-sekizai-0.5.0/setup.py000066400000000000000000000006571165054553500161460ustar00rootroot00000000000000from setuptools import setup, find_packages version = __import__('sekizai').__version__ setup( name = 'django-sekizai', version = version, description = 'Django Sekizai', author = 'Jonas Obrist', author_email = 'jonas.obrist@divio.ch', url = 'http://github.com/ojii/django-sekizai', packages = find_packages(), zip_safe=False, install_requires = [ 'django-classy-tags>=0.3.1', ], )django-sekizai-0.5.0/test_templates/000077500000000000000000000000001165054553500174615ustar00rootroot00000000000000django-sekizai-0.5.0/test_templates/basic.html000066400000000000000000000003441165054553500214310ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "css" %} some content {% addtoblock "css" %} my css file {% endaddtoblock %} more content {% addtoblock "js" %} my js file {% endaddtoblock %} final content {% render_block "js" %} django-sekizai-0.5.0/test_templates/css.html000066400000000000000000000002411165054553500211340ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "css-to-file" %} {% addtoblock "css-to-file" %} {% endaddtoblock %}django-sekizai-0.5.0/test_templates/css2.html000066400000000000000000000004041165054553500212170ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "css-onefile" %} {% addtoblock "css-onefile" %}{% endaddtoblock %} {% addtoblock "css-onefile" %}{% endaddtoblock %}django-sekizai-0.5.0/test_templates/easy_base.html000066400000000000000000000001301165054553500222740ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "css" %} {% block main %}content{% endblock %}django-sekizai-0.5.0/test_templates/easy_inherit.html000066400000000000000000000001171165054553500230310ustar00rootroot00000000000000{% extends "easy_base.html" %} {% block main %}{{ block.super }}{% endblock %}django-sekizai-0.5.0/test_templates/eat.html000066400000000000000000000001321165054553500211140ustar00rootroot00000000000000{% load sekizai_tags %} {% addtoblock "css" %} my css file {% endaddtoblock %} mycontentdjango-sekizai-0.5.0/test_templates/errors/000077500000000000000000000000001165054553500207755ustar00rootroot00000000000000django-sekizai-0.5.0/test_templates/errors/failadd.html000066400000000000000000000001061165054553500232440ustar00rootroot00000000000000{% load sekizai_tags %} {% addtoblock %} file one {% endaddtoblock %}django-sekizai-0.5.0/test_templates/errors/failbase.html000066400000000000000000000002111165054553500234230ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "js" %} {% include "errors/failinc.html" %} {% addtoblock "js" %} file one {% endaddtoblock %}django-sekizai-0.5.0/test_templates/errors/failbase2.html000066400000000000000000000002111165054553500235050ustar00rootroot00000000000000{% load sekizai_tags %} {% include "errors/failinc.html" %} {% addtoblock "js" %} file one {% endaddtoblock %} {% render_block "js" %}django-sekizai-0.5.0/test_templates/errors/failinc.html000066400000000000000000000001121165054553500232620ustar00rootroot00000000000000{% load sekizai_tags %} {% addtoblock "js %} file two {% endaddtoblock %}django-sekizai-0.5.0/test_templates/errors/failrender.html000066400000000000000000000000531165054553500237740ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block %}django-sekizai-0.5.0/test_templates/inherit/000077500000000000000000000000001165054553500211235ustar00rootroot00000000000000django-sekizai-0.5.0/test_templates/inherit/base.html000066400000000000000000000005201165054553500227200ustar00rootroot00000000000000{% load sekizai_tags %} head start {% render_block "css" %} head end include start {% include "inherit/baseinc.html" %} include end block main start {% block "main" %} block main base contents {% endblock %} block main end body pre-end {% render_block "js" %} body end {% addtoblock "css" %} some css file {% endaddtoblock %}django-sekizai-0.5.0/test_templates/inherit/baseinc.html000066400000000000000000000001321165054553500234110ustar00rootroot00000000000000{% load sekizai_tags %} inc add js {% addtoblock "js" %} inc js file {% endaddtoblock %}django-sekizai-0.5.0/test_templates/inherit/chain.html000066400000000000000000000001511165054553500230700ustar00rootroot00000000000000{% extends "inherit/extend.html" %} {% block "main" %} {{ block.super }} {{ other_var }} {% endblock %}django-sekizai-0.5.0/test_templates/inherit/extend.html000066400000000000000000000003011165054553500232720ustar00rootroot00000000000000{% extends "inherit/base.html" %} {% load sekizai_tags %} {% addtoblock "css" %} invisible css file {% endaddtoblock %} {% block "main" %} {% include "inherit/extinc.html" %} {% endblock %}django-sekizai-0.5.0/test_templates/inherit/extinc.html000066400000000000000000000000401165054553500232750ustar00rootroot00000000000000{% load sekizai_tags %} extinc django-sekizai-0.5.0/test_templates/inherit/nullbase.html000066400000000000000000000000001165054553500236040ustar00rootroot00000000000000django-sekizai-0.5.0/test_templates/inherit/nullext.html000066400000000000000000000001271165054553500235040ustar00rootroot00000000000000{% extends "inherit/nullbase.html" %} {% load sekizai_tags %} {% render_block "js" %}django-sekizai-0.5.0/test_templates/inherit/spacechain.html000066400000000000000000000000471165054553500241100ustar00rootroot00000000000000 {% extends "inherit/extend.html" %}django-sekizai-0.5.0/test_templates/inherit/subvarchain.html000066400000000000000000000000451165054553500243150ustar00rootroot00000000000000{% extends "inherit/varchain.html" %}django-sekizai-0.5.0/test_templates/inherit/super_blocks.html000066400000000000000000000003701165054553500245040ustar00rootroot00000000000000{% extends "inherit/base.html" %} {% load sekizai_tags %} {% addtoblock "css" %} invisible css file {% endaddtoblock %} {% block "main" %} {{ block.super }} {% addtoblock "css" %} visible css file {% endaddtoblock %} more contents {% endblock %}django-sekizai-0.5.0/test_templates/inherit/varchain.html000066400000000000000000000000231165054553500235770ustar00rootroot00000000000000{% extends var %} django-sekizai-0.5.0/test_templates/named_end.html000066400000000000000000000001721165054553500222610ustar00rootroot00000000000000{% load sekizai_tags %} {% addtoblock "myblock" %} mycontent {% endaddtoblock "myblock" %} {% render_block "myblock" %} django-sekizai-0.5.0/test_templates/namespaces.html000066400000000000000000000003701165054553500224660ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "one" %} {% render_block "two" %} {% addtoblock "one" %} the same file {% endaddtoblock %} {% addtoblock "two" %} the same file {% endaddtoblock %} {% addtoblock "one" %} the same file {% endaddtoblock %}django-sekizai-0.5.0/test_templates/processors/000077500000000000000000000000001165054553500216635ustar00rootroot00000000000000django-sekizai-0.5.0/test_templates/processors/namespace.html000066400000000000000000000002511165054553500245030ustar00rootroot00000000000000{% load sekizai_tags %} header {% addtoblock "js" %} javascript {% endaddtoblock %} footer {% render_block "js" postprocessor "sekizai.tests.namespace_processor" %} django-sekizai-0.5.0/test_templates/processors/null.html000066400000000000000000000002441165054553500235230ustar00rootroot00000000000000{% load sekizai_tags %} header {% addtoblock "js" %} javascript {% endaddtoblock %} footer {% render_block "js" postprocessor "sekizai.tests.null_processor" %} django-sekizai-0.5.0/test_templates/spaceless.html000066400000000000000000000003021165054553500223240ustar00rootroot00000000000000{% load sekizai_tags %} {% render_block "spaceless" %} {% addtoblock "spaceless" %} Strong {% endaddtoblock %} {% addtoblock "spaceless" %} oblique {% endaddtoblock %}django-sekizai-0.5.0/test_templates/variables.html000066400000000000000000000003731165054553500223220ustar00rootroot00000000000000{% load sekizai_tags %} {% addtoblock blockname %} file two {% endaddtoblock %} {% addtoblock "one" %} file two {% endaddtoblock %} {% addtoblock blockname|upper %} file one {% endaddtoblock %} {% render_block "ONE" %} {% render_block blockname %}django-sekizai-0.5.0/test_templates/with_data.html000066400000000000000000000002511165054553500223110ustar00rootroot00000000000000{% load sekizai_tags %} {% with_data "key" as mylist %} {% for obj in mylist %} {{ obj }} {% endfor %} {% end_with_data %} {% add_data "key" 1 %} {% add_data "key" 2 %}