Permalink
Browse files

Version 0.2.0.

  • Loading branch information...
1 parent a227a10 commit 59eda0108b67e17ce4688d60c16d3f063a0dc47d @carljm carljm committed Jun 27, 2011
View
@@ -1,6 +1,14 @@
CHANGES
=======
+0.2.0 (2011.06.26)
+------------------
+
+* Made template-finding more flexible: ``ICANHAZ_DIR`` is now ``ICANHAZ_DIRS``
+ (a list); added ``ICANHAZ_FINDERS``, ``ICANHAZ_APP_DIRNAMES``, and finding of
+ templates in installed apps.
+
+
0.1.0 (2011.06.22)
------------------
View
@@ -37,22 +37,44 @@ Usage
* Add ``"icanhaz"`` to your ``INSTALLED_APPS`` setting.
-* Set the ``ICANHAZ_DIR`` setting to the full (absolute) path to a directory
- where you will store your ICanHaz templates.
+* Set the ``ICANHAZ_DIRS`` setting to a list of full (absolute) path to
+ directories where you will store your ICanHaz templates.
* ``{% load icanhaz %}`` and use ``{% icanhaz "templatename" %}`` in your
Django templates to safely embed the ICanHaz.js template at
- ``ICANHAZ_DIR/templatename.html`` into your Django template, automatically
- wrapped in ``<script id="templatename" type="text/html">``, ready for
- ``ich.templatename({...})`` in your JavaScript.
+ ``<ICANHAZ_DIRS-entry>/templatename.html`` into your Django template,
+ automatically wrapped in ``<script id="templatename" type="text/html">``,
+ ready for ``ich.templatename({...})`` in your JavaScript.
``django-icanhaz`` does not bundle `ICanHaz.js`_ or provide any JavaScript
utilities; it just helps you easily embed the templates in your HTML. Include
`ICanHaz.js`_ in your project's static assets and use it in your JS as usual.
-Philosophy
-----------
+Advanced usage
+--------------
+
+You can also bundle ICanHaz templates with Django reusable apps; by default
+``django-icanhaz`` will look for templates in a ``jstemplates`` subdirectory of
+each app in ``INSTALLED_APPS``. The app subdirectory name(s) to check can be
+configured via the ``ICANHAZ_APP_DIRNAMES`` setting, which defaults to
+``["jstemplates"]``.
+
+The finding of templates can be fully controlled via the ``ICANHAZ_FINDERS``
+setting, which is a list of dotted paths to finder classes. A finder class
+should be instantiable with no arguments, and have a ``find(name)`` method
+which returns the full absolute path to a template file, given a base-name.
+
+By default, ``ICANHAZ_FINDERS`` contains ``"icanhaz.finders.FilesystemFinder"``
+(which searches directories listed in ``ICANHAZ_DIRS``) and
+``"icanhaz.finders.AppFinder"`` (which searches subdirectories named in
+``ICANHAZ_APP_DIRNAMES`` of each app in ``INSTALLED_APPS``), in that order --
+thus templates found in ``ICANHAZ_DIRS`` take precedence over templates in
+apps.
+
+
+Rationale
+---------
The collision between Django templates' use of ``{{`` and ``}}`` as template
variable markers and `ICanHaz.js`_' use of same has spawned a variety of
View
@@ -1 +1 @@
-__version__ = "0.1.0"
+__version__ = "0.2.0"
View
@@ -17,4 +17,11 @@ def __getattr__(self, k):
raise ImproperlyConfigured("%s setting is required." % k)
-conf = Configuration()
+conf = Configuration(
+ ICANHAZ_FINDERS=[
+ "icanhaz.finders.FilesystemFinder",
+ "icanhaz.finders.AppFinder",
+ ],
+ ICANHAZ_DIRS=[],
+ ICANHAZ_APP_DIRNAMES=["jstemplates"],
+ )
View
@@ -0,0 +1,60 @@
+import os, sys
+
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+from .conf import conf
+
+
+
+class BaseFinder(object):
+ def find(self, name):
+ raise NotImplementedError()
+
+
+
+class FilesystemFinder(BaseFinder):
+ @property
+ def directories(self):
+ return conf.ICANHAZ_DIRS
+
+
+ def find(self, name):
+ for directory in self.directories:
+ filepath = os.path.abspath(os.path.join(
+ directory,
+ name + ".html"))
+
+ if filepath.startswith(directory) and os.path.exists(filepath):
+ return filepath
+
+ return None
+
+
+
+def _get_app_template_dirs():
+ fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
+ ret = []
+ for app in conf.INSTALLED_APPS:
+ try:
+ mod = import_module(app)
+ except ImportError, e:
+ raise ImproperlyConfigured("ImportError %s: %s" % (app, e.args[0]))
+ app_dir = os.path.dirname(mod.__file__)
+ for dirname in conf.ICANHAZ_APP_DIRNAMES:
+ template_dir = os.path.join(app_dir, dirname)
+ if os.path.isdir(template_dir):
+ ret.append(template_dir.decode(fs_encoding))
+ return ret
+
+
+
+# At import time, cache the app directories to search.
+app_template_dirs = _get_app_template_dirs()
+
+
+
+class AppFinder(FilesystemFinder):
+ @property
+ def directories(self):
+ return app_template_dirs
View
@@ -0,0 +1,46 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+from .conf import conf
+
+
+
+def find(name):
+ for finder in finders:
+ filepath = finder.find(name)
+ if filepath is not None:
+ return filepath
+
+ raise ICanHazTemplateNotFound(name)
+
+
+
+def _get_finders():
+ ret = []
+ for finder_path in conf.ICANHAZ_FINDERS:
+ modpath, cls_name = finder_path.rsplit(".", 1)
+ try:
+ mod = import_module(modpath)
+ except ImportError, e:
+ raise ImproperlyConfigured(
+ "ImportError %s: %s" % (modpath, e.args[0]))
+
+ try:
+ cls = getattr(mod, cls_name)
+ except AttributeError, e:
+ raise ImproperlyConfigured(
+ "AttributeError %s: %s" % (cls_name, e.args[0]))
+
+ ret.append(cls())
+
+ return ret
+
+
+
+# Instantiate finders
+finders = _get_finders()
+
+
+
+class ICanHazTemplateNotFound(Exception):
+ pass
@@ -1,9 +1,7 @@
-import os.path
-
from django import template
-from django.core.exceptions import SuspiciousOperation
from ..conf import conf
+from ..loading import find, ICanHazTemplateNotFound
@@ -16,30 +14,17 @@ def __init__(self, name):
self.name = template.Variable(name)
-class ICanHazNode(template.Node):
- def __init__(self, name):
- self.name = template.Variable(name)
-
-
def render(self, context):
name = self.name.resolve(context)
- filepath = os.path.abspath(os.path.join(
- conf.ICANHAZ_DIR,
- name + ".html"))
-
- if not filepath.startswith(conf.ICANHAZ_DIR):
- raise SuspiciousOperation(
- "icanhaz tag attempting to open file at %r, outside of %r"
- % (filepath, conf.ICANHAZ_DIR))
-
try:
+ filepath = find(name)
fp = open(filepath, "r")
output = fp.read()
fp.close()
output = ('<script id="%s" type="text/html">\n'
% name) + output + "\n</script>\n"
- except IOError:
+ except (IOError, ICanHazTemplateNotFound):
output = ""
if conf.DEBUG:
raise
@@ -51,8 +36,8 @@ def render(self, context):
@register.tag
def icanhaz(parser, token):
"""
- Outputs the contents of a given file, path relative to ICANHAZ_DIR
- setting, into the page.
+ Finds the ICanHaz template for the given name and renders it surrounded by
+ the requisite ICanHaz <script> tags.
"""
bits = token.contents.split()
@@ -1 +1,3 @@
-from .tests import *
+from .test_finders import *
+from .test_loading import *
+from .test_ttag import *
@@ -0,0 +1,7 @@
+class MockFinder(object):
+ def __init__(self, retval=None):
+ self.retval = retval
+
+
+ def find(self, name):
+ return self.retval
No changes.
@@ -0,0 +1,91 @@
+import os
+
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+
+from mock import patch
+
+from .utils import override_settings
+
+
+
+__all__ = ["BaseFinderTest", "FilesystemFinderTest", "AppFinderTest"]
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+
+class BaseFinderTest(TestCase):
+ @property
+ def finder(self):
+ from icanhaz.finders import BaseFinder
+ return BaseFinder()
+
+
+ def test_find_not_implemented(self):
+ with self.assertRaises(NotImplementedError):
+ self.finder.find("name")
+
+
+
+class FilesystemFinderTest(TestCase):
+ @property
+ def finder(self):
+ from icanhaz.finders import FilesystemFinder
+ return FilesystemFinder()
+
+
+ @override_settings(ICANHAZ_DIRS=["/one/path", "/another/path"])
+ def test_directories(self):
+ self.assertEqual(
+ self.finder.directories,
+ ["/one/path", "/another/path"])
+
+
+ @override_settings(ICANHAZ_DIRS=[os.path.join(here, "templates")])
+ def test_find(self):
+ self.assertEqual(
+ self.finder.find("testtemplate"),
+ os.path.join(here, "templates", "testtemplate.html"))
+
+
+ @override_settings(ICANHAZ_DIRS=[os.path.join(here, "templates")])
+ def test_find_non_existing(self):
+ self.assertEqual(self.finder.find("doesntexist"), None)
+
+
+
+class AppFinderTest(TestCase):
+ @property
+ def finder(self):
+ from icanhaz.finders import AppFinder
+ return AppFinder()
+
+
+ def test_directories(self):
+ with patch(
+ "icanhaz.finders.app_template_dirs",
+ [os.path.join(here, "templates")]):
+ dirs = self.finder.directories
+
+ self.assertEqual(dirs, [os.path.join(here, "templates")])
+
+
+ @property
+ def func(self):
+ from icanhaz.finders import _get_app_template_dirs
+ return _get_app_template_dirs
+
+
+ @override_settings(
+ INSTALLED_APPS=["icanhaz.tests"],
+ ICANHAZ_APP_DIRNAMES=["templates", "jstemplates"])
+ def test_get_app_template_dirs(self):
+ self.assertEqual(self.func(), [os.path.join(here, "templates")])
+
+
+ @override_settings(INSTALLED_APPS=["icanhaz.nonexistent"])
+ def test_bad_app(self):
+ with self.assertRaises(ImproperlyConfigured):
+ self.func()
Oops, something went wrong.

0 comments on commit 59eda01

Please sign in to comment.