Commits

Kevin Veroneau committed a30b573

Initial project commit.

Comments (0)

Files changed (15)

+## Django-Chameleon-templates
+
+This Django app will provide your Django project with a nice drop-in replacement
+for the Django template engine shipped with Django.
+
+You will need to download Chameleon to use these template Loaders, you can either
+use PIP or download it directly from the Chameleon website:
+<http://chameleon.repoze.org/>
+
+One you have it installed, there are a few ways you can use the template engine in
+your Django project.  Here is a list of all the included template Loaders and
+what they do:
+
+**djchameleon.loaders.filesystem.Loader**:
+
+>  This is a drop-in replacement for the default Django filesystem.Loader
+>  It will use your existing TEMPLATE_DIRS to load templates.
+
+**djchameleon.loaders.app_directories.Loader**:
+
+>  This is a drop-in replacement for the default Django app_directories.Loader
+>  It will use the 'templates' directories in each of your app directories.
+
+**djchameleon.loaders.chameleon_engine.Loader**:
+
+>  This Loader allows you to specify a seperate CHAMELEON_TEMPLATES settings
+>  variable, which acts the exact same as your TEMPLATE_DIRS variable.
+
+There are also a couple settings variables you can set as well:
+
+**CHAMELEON_TEMPLATES**:
+
+>  Takes the exact same format as TEMPLATE_DIRS variable.  This setting is only
+>  valid when the chameleon_engine.Loader is used.  It allows you to have a
+>  specific directory for chameleon templates.
+
+**CHAMELEON_EXTENSION**:
+
+>  This forces a specific extension on all template files.  This uses the
+>  functionality shipped with Chameleon, and merely passes this setting directly
+>  over to Chameleon.
+
+### BASIC USAGE AND EXAMPLE TEMPLATES
+
+You will still render templates using the Django method, such as *render_to_response()* or *render()*.
+You will even have access to all of your usual *context variables*, including **context_processors**.
+
+Included in this package are some examples which you can take a look at.  
+There is an example **views.py**, and **urls.py**.  To use try out these examples add the following
+line to your project's **urls.py**:
+
+    url(r'^chameleon/', include('djchameleon.urls')),
+
+You can of course change the base url.  The reason why I suggest adding this is so that the included
+**url** tag can reverse the url in the example template.  Yes, this template drop-in has a Django
+compatible **url** method you can use, more on that in the next section.
+
+In order to use these examples you will also need to add *djchameleon* to your **INSTALLED_APPS** and
+use the *djchameleon.loaders.app_directories.Loader*.  In normal operation, you do not need to have
+this app in your **INSTALLED_APPS**, only the example template requires it.
+
+### Chameleon Django API
+
+Now on to the fun stuff, how to use various Django resources within your Chameleon templates, such as
+**CSRF_TOKEN** and **URL Reversal**.  At the moment, these are the only main functions taken from Django
+as they are a requirement when working with a Django project.  The example mentioned above shows both
+of these in action, but I will explain how to use each one of these APIs here within your template:
+
+**URL Reversal**:
+
+    <a tal:attributes="href url:hello_chameleon">A Link!</a>
+
+>  If you need to place a URL inside JavaScript, you can use the following notation:
+
+    ${url:hello_chameleon}
+
+**CSRF_TOKEN tag**
+
+    <p metal:use-macro="django.csrf_token" />
+
+>  This will do exactly what {% csrf_token %} does.  You can also render it manually as such:
+
+    <input type="hidden" name="csrfmiddlewaretoken" tal:attributes="value csrf_token" />
+
+**Adding METAL Macros**
+
+Unlike in Django where creating *template tags* requires Python programming skills, you
+can easily extend the Chameleon template engine using *METAL Macros*.  These can be
+either placed in your **base template** or in your app directories **macros/** directory.
+You can see an example macro in the **djchameleon/macros/example.pt** file.  Everything with a
+**pt** extension in your apps **macros/** directory is added into the global template namespace.
+
+For example, say your app name is **todo**, and you want to create a namespace called **todo** which
+assists in many aspects of your **todo** application.  This is where you would create the file:
+
+**django_project/todo/macros/todo.pt**
+
+Then inside that file, you can place various reusable HTML/Python code for your todo app templates to
+use.  Here's an example template, which is included in the **example.pt** file:
+
+    <p metal:define-macro="hello">
+      Hello, <strong metal:define-slot="name">World</strong>
+    </p>
+
+To use this macro inside any Chameleon template, use one of the following code:
+
+    <p metal:use-macro="todo.hello" />
+
+Or if you wish to fill in the *name* context, use this code:
+
+    <p metal:use-macro="todo.hello" >
+      <i metal:fill-slot="name">Kevin</i>
+    </p>
+
+Also note that you can also change the underlying tag dynamically, here it's changed from **strong** to **i**.

djchameleon/__init__.py

Empty file added.

djchameleon/loaders/__init__.py

Empty file added.

djchameleon/loaders/app_directories.py

+from chameleon import PageTemplateLoader
+from django.template.loaders import app_directories
+from .chameleon_tags import Template
+from django.template.base import TemplateDoesNotExist
+from django.conf import settings
+
+class Loader(PageTemplateLoader, app_directories.Loader):
+    is_usable = True
+    def __init__(self, *args, **kwargs):
+        template_dirs = app_directories.app_template_dirs
+        self.formats.update({'xml': Template})
+        default_extension = getattr(settings, 'CHAMELEON_EXTENSION', None)
+        super(PageTemplateLoader, self).__init__(template_dirs, default_extension=default_extension, debug=settings.TEMPLATE_DEBUG)
+    def load_template(self, template_name, template_dirs=None):
+        try:
+            template = self.load(template_name)
+            return template, None
+        except ValueError:
+            raise TemplateDoesNotExist('Template not found: %s' % template_name)

djchameleon/loaders/chameleon_engine.py

+from chameleon import PageTemplateLoader
+from django.template.loaders import app_directories
+from django.conf import settings
+from .chameleon_tags import Template
+from django.template.base import TemplateDoesNotExist
+
+class Loader(PageTemplateLoader, app_directories.Loader):
+    is_usable = True
+    def __init__(self, *args, **kwargs):
+        template_dirs = None
+        if getattr(settings, 'CHAMELEON_TEMPLATES', None):
+            template_dirs = settings.CHAMELEON_TEMPLATES
+        self.formats.update({'xml': Template})
+        default_extension = getattr(settings, 'CHAMELEON_EXTENSION', None)
+        super(PageTemplateLoader, self).__init__(template_dirs, default_extension=default_extension, debug=settings.TEMPLATE_DEBUG)
+    def load_template(self, template_name, template_dirs=None):
+        try:
+            template = self.load(template_name)
+            return template, None
+        except ValueError:
+            raise TemplateDoesNotExist('Template not found: %s' % template_name)

djchameleon/loaders/chameleon_tags.py

+from chameleon.zpt.template import PageTemplateFile
+from django.core.urlresolvers import reverse
+from django.utils.importlib import import_module
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+from django.utils import six
+import ast
+import os
+import sys
+
+if not six.PY3:
+    fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
+
+MY_ROOT = os.path.normpath(os.path.dirname(__file__))
+
+def url_expression(string):
+    def compiler(target, engine):
+        the_url = reverse(string)
+        value = ast.Str(the_url)
+        return [ast.Assign(targets=[target], value=value)]
+    return compiler
+
+djangotags = PageTemplateFile(os.path.join(MY_ROOT, 'defaultmacros', 'djangotags.pt'))
+
+app_macros_dirs = []
+for app in settings.INSTALLED_APPS:
+    try:
+        mod = import_module(app)
+    except ImportError as e:
+        raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0]))
+    macro_dir = os.path.join(os.path.dirname(mod.__file__), 'macros')
+    if os.path.isdir(macro_dir):
+        if not six.PY3:
+            macro_dir = macro_dir.decode(fs_encoding)
+        app_macros_dirs.append(macro_dir)
+
+# It won't change, so convert it to a tuple to save memory.
+app_macros_dirs = tuple(app_macros_dirs)
+
+class Template(PageTemplateFile):
+    expression_types = PageTemplateFile.expression_types.copy()
+    expression_types['url'] = url_expression
+    def gather_macros(self):
+        macro_pt = {}
+        for d in app_macros_dirs:
+            for pt in os.listdir(d):
+                fn = pt.split('.')
+                if len(fn) > 1 and fn[-1] == 'pt':
+                    macro_pt.update({fn[0]: PageTemplateFile(os.path.join(d, pt))})
+        return macro_pt
+    def render(self, context):
+        context_dict = {'django': djangotags}
+        context_dict.update(self.gather_macros())
+        for d in context.dicts:
+            context_dict.update(d)
+        return super(Template, self).render(**context_dict)

djchameleon/loaders/defaultmacros/djangotags.pt

+<p metal:define-macro="csrf_token">
+  <input type='hidden' name='csrfmiddlewaretoken' value='' tal:attributes="value csrf_token" />
+</p>

djchameleon/loaders/filesystem.py

+from chameleon import PageTemplateLoader
+from django.template.loaders import filesystem
+from django.conf import settings
+from .chameleon_tags import Template
+from django.template.base import TemplateDoesNotExist
+
+class Loader(PageTemplateLoader, filesystem.Loader):
+    is_usable = True
+    def __init__(self, *args, **kwargs):
+        template_dirs = settings.TEMPLATE_DIRS
+        self.formats.update({'xml': Template})
+        default_extension = getattr(settings, 'CHAMELEON_EXTENSION', None)
+        super(PageTemplateLoader, self).__init__(template_dirs, default_extension=default_extension, debug=settings.TEMPLATE_DEBUG)
+    def load_template(self, template_name, template_dirs=None):
+        try:
+            template = self.load(template_name)
+            return template, None
+        except ValueError:
+            raise TemplateDoesNotExist('Template not found: %s' % template_name)

djchameleon/macros/example.pt

+<p metal:define-macro="hello">
+  Hello, <strong metal:define-slot="name">World</strong>
+</p>

djchameleon/models.py

+from django.db import models
+
+# Create your models here.

djchameleon/templates/base.pt

+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>Example &mdash; Chameleon Page</title>
+  </head>
+  <body>
+    <h1>Django Chameleon test pages!</h1>
+    <div id="content">
+      <metal:content define-slot="content" />
+    </div>
+  </body>
+</html>

djchameleon/templates/hello.pt

+<metal:main use-macro="load: base.pt">
+  <div metal:fill-slot="content">
+    <div>Hello, ${name}.</div>
+    <div tal:content="url:hello_chameleon" />
+    <p metal:use-macro="django.csrf_token" />
+    <p metal:use-macro="example.hello" >
+      <i metal:fill-slot="name">Kevin</i>
+    </p>
+  </div>
+</metal:main>
+"""
+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)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('djchameleon.views',
+    url(r'^hello$', 'hello', name='hello_chameleon'),
+)
+from django.shortcuts import render
+
+def hello(req):
+    return render(req, 'hello.html', {'name':'Kevin'})