Merge branch 'master' of git://github.com/coffin/coffin
This commit is contained in:
commit
6c159eb609
10
CHANGES
10
CHANGES
@ -1,3 +1,11 @@
|
||||
0.3.7 (2012-09-29)
|
||||
- Support for Django 1.3 class-based views (Kirill Zaitsev,
|
||||
Dmitry Panteleev)
|
||||
- Loader for *.jinja files (Aarni Koskela)
|
||||
- makemessages command now supports whitespace stripping tags
|
||||
(Dmitry Panteleev)
|
||||
- Now supports Django's CachedLoader.
|
||||
|
||||
0.3.6 (2011-09-09)
|
||||
- Re-release of 0.3.5, containing the correct repository
|
||||
- Re-release of 0.3.5, containing the correct repository
|
||||
state this time.
|
||||
|
@ -14,7 +14,7 @@ reasonable.
|
||||
|
||||
|
||||
__all__ = ('__version__', '__build__', '__docformat__', 'get_revision')
|
||||
__version__ = (0, 3, '7', 'dev')
|
||||
__version__ = (0, 3, '8', 'dev')
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
@ -28,7 +28,7 @@ class CoffinEnvironment(Environment):
|
||||
# the proper priority), so we want to assign to these attributes.
|
||||
self.filters = all_ext['filters'].copy()
|
||||
self.filters.update(filters)
|
||||
self.globals = all_ext['globals'].copy()
|
||||
self.globals.update(all_ext['globals'])
|
||||
self.globals.update(globals)
|
||||
self.tests = all_ext['tests'].copy()
|
||||
self.tests.update(tests)
|
||||
@ -48,20 +48,30 @@ class CoffinEnvironment(Environment):
|
||||
loaders = []
|
||||
|
||||
from coffin.template.loaders import jinja_loader_from_django_loader
|
||||
from jinja2.loaders import BaseLoader as JinjaLoader
|
||||
|
||||
from django.conf import settings
|
||||
_loaders = getattr(settings, 'JINJA2_TEMPLATE_LOADERS', settings.TEMPLATE_LOADERS)
|
||||
for loader in _loaders:
|
||||
if isinstance(loader, basestring):
|
||||
loader_obj = jinja_loader_from_django_loader(loader)
|
||||
if loader_obj:
|
||||
loaders.append(loader_obj)
|
||||
else:
|
||||
warnings.warn('Cannot translate loader: %s' % loader)
|
||||
else: # It's assumed to be a Jinja2 loader instance.
|
||||
if isinstance(loader, JinjaLoader):
|
||||
loaders.append(loader)
|
||||
return loaders
|
||||
else:
|
||||
loader_name = args = None
|
||||
if isinstance(loader, basestring):
|
||||
loader_name = loader
|
||||
args = []
|
||||
elif isinstance(loader, (tuple, list)):
|
||||
loader_name = loader[0]
|
||||
args = loader[1]
|
||||
|
||||
if loader_name:
|
||||
loader_obj = jinja_loader_from_django_loader(loader_name, args)
|
||||
if loader_obj:
|
||||
loaders.append(loader_obj)
|
||||
continue
|
||||
|
||||
warnings.warn('Cannot translate loader: %s' % loader)
|
||||
return loaders
|
||||
|
||||
def _get_templatelibs(self):
|
||||
"""Return an iterable of template ``Library`` instances.
|
||||
@ -70,30 +80,35 @@ class CoffinEnvironment(Environment):
|
||||
register all libraries globally.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.template import get_library, InvalidTemplateLibrary
|
||||
from django.template import (
|
||||
get_library, import_library, InvalidTemplateLibrary)
|
||||
|
||||
libs = []
|
||||
for a in settings.INSTALLED_APPS:
|
||||
for app in settings.INSTALLED_APPS:
|
||||
ns = app + '.templatetags'
|
||||
try:
|
||||
path = __import__(a + '.templatetags', {}, {}, ['__file__']).__file__
|
||||
path = __import__(ns, {}, {}, ['__file__']).__file__
|
||||
path = os.path.dirname(path) # we now have the templatetags/ directory
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
for f in os.listdir(path):
|
||||
if f == '__init__.py' or f.startswith('.'):
|
||||
for filename in os.listdir(path):
|
||||
if filename == '__init__.py' or filename.startswith('.'):
|
||||
continue
|
||||
|
||||
if f.endswith('.py'):
|
||||
if filename.endswith('.py'):
|
||||
try:
|
||||
# TODO: will need updating when #6587 lands
|
||||
# libs.append(get_library(
|
||||
# "django.templatetags.%s" % os.path.splitext(f)[0]))
|
||||
l = get_library(os.path.splitext(f)[0])
|
||||
module = "%s.%s" % (ns, os.path.splitext(filename)[0])
|
||||
l = import_library(module)
|
||||
libs.append(l)
|
||||
|
||||
except InvalidTemplateLibrary:
|
||||
pass
|
||||
|
||||
# In addition to loading application libraries, support a custom list
|
||||
for libname in getattr(settings, 'JINJA2_DJANGO_TEMPLATETAG_LIBRARIES', ()):
|
||||
libs.append(get_library(libname))
|
||||
|
||||
return libs
|
||||
|
||||
def _get_all_extensions(self):
|
||||
@ -139,25 +154,28 @@ class CoffinEnvironment(Environment):
|
||||
|
||||
# Next, add the globally defined extensions
|
||||
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
|
||||
def from_setting(setting):
|
||||
def from_setting(setting, values_must_be_callable = False):
|
||||
retval = {}
|
||||
setting = getattr(settings, setting, {})
|
||||
if isinstance(setting, dict):
|
||||
for key, value in setting.iteritems():
|
||||
retval[key] = callable(value) and value or get_callable(value)
|
||||
if values_must_be_callable and not callable(value):
|
||||
value = get_callable(value)
|
||||
retval[key] = value
|
||||
else:
|
||||
for value in setting:
|
||||
value = callable(value) and value or get_callable(value)
|
||||
if values_must_be_callable and not callable(value):
|
||||
value = get_callable(value)
|
||||
retval[value.__name__] = value
|
||||
return retval
|
||||
filters.update(from_setting('JINJA2_FILTERS'))
|
||||
|
||||
tests.update(from_setting('JINJA2_TESTS', True))
|
||||
filters.update(from_setting('JINJA2_FILTERS', True))
|
||||
globals.update(from_setting('JINJA2_GLOBALS'))
|
||||
tests.update(from_setting('JINJA2_TESTS'))
|
||||
|
||||
# Finally, add extensions defined in application's templatetag libraries
|
||||
for lib in self._get_templatelibs():
|
||||
_load_lib(lib)
|
||||
attrs.update(getattr(lib, 'jinja2_environment_attrs', {}))
|
||||
|
||||
return dict(
|
||||
extensions=extensions,
|
||||
|
47
coffin/contrib/loader.py
Normal file
47
coffin/contrib/loader.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
A Django template loader wrapper for Coffin that intercepts
|
||||
requests for "*.jinja" templates, rendering them with Coffin
|
||||
instead of Django templates.
|
||||
|
||||
Usage:
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'coffin.contrib.loader.AppLoader',
|
||||
'coffin.contrib.loader.FileSystemLoader',
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
from os.path import splitext
|
||||
from coffin.common import env
|
||||
from django.conf import settings
|
||||
from django.template.loaders import app_directories, filesystem
|
||||
|
||||
|
||||
JINJA2_DEFAULT_TEMPLATE_EXTENSION = getattr(settings,
|
||||
'JINJA2_DEFAULT_TEMPLATE_EXTENSION', ('.jinja',))
|
||||
|
||||
if isinstance(JINJA2_DEFAULT_TEMPLATE_EXTENSION, basestring):
|
||||
JINJA2_DEFAULT_TEMPLATE_EXTENSION = (JINJA2_DEFAULT_TEMPLATE_EXTENSION,)
|
||||
|
||||
|
||||
class LoaderMixin(object):
|
||||
is_usable = True
|
||||
|
||||
def load_template(self, template_name, template_dirs=None):
|
||||
extension = splitext(template_name)[1]
|
||||
|
||||
if not extension in JINJA2_DEFAULT_TEMPLATE_EXTENSION:
|
||||
return super(LoaderMixin, self).load_template(template_name,
|
||||
template_dirs)
|
||||
template = env.get_template(template_name)
|
||||
return template, template.filename
|
||||
|
||||
|
||||
class FileSystemLoader(LoaderMixin, filesystem.Loader):
|
||||
pass
|
||||
|
||||
|
||||
class AppLoader(LoaderMixin, app_directories.Loader):
|
||||
pass
|
0
coffin/contrib/staticfiles/__init__.py
Normal file
0
coffin/contrib/staticfiles/__init__.py
Normal file
0
coffin/contrib/staticfiles/templatetags/__init__.py
Normal file
0
coffin/contrib/staticfiles/templatetags/__init__.py
Normal file
38
coffin/contrib/staticfiles/templatetags/static.py
Normal file
38
coffin/contrib/staticfiles/templatetags/static.py
Normal file
@ -0,0 +1,38 @@
|
||||
from coffin import template
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from coffin.templatetags.static import StaticExtension
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class StaticExtension(StaticExtension):
|
||||
"""Implements the {% static %} tag as provided by the ``staticfiles``
|
||||
contrib module.
|
||||
|
||||
Rreturns the URL to a file using staticfiles' storage backend.
|
||||
|
||||
Usage::
|
||||
|
||||
{% static path [as varname] %}
|
||||
|
||||
Examples::
|
||||
|
||||
{% static "myapp/css/base.css" %}
|
||||
{% static variable_with_path %}
|
||||
{% static "myapp/css/base.css" as admin_base_css %}
|
||||
{% static variable_with_path as varname %}
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_statc_url(cls, path):
|
||||
return super(StaticExtension, cls).get_statc_url(
|
||||
staticfiles_storage.url(path))
|
||||
|
||||
|
||||
register.tag(StaticExtension)
|
||||
|
||||
|
||||
def static(path):
|
||||
return StaticExtension.get_static_url(path)
|
@ -27,24 +27,40 @@ http://stackoverflow.com/questions/2090717/getting-translation-strings-for-jinja
|
||||
import re
|
||||
from django.core.management.commands import makemessages
|
||||
from django.utils.translation import trans_real
|
||||
from django.template import BLOCK_TAG_START, BLOCK_TAG_END
|
||||
|
||||
strip_whitespace_right = re.compile(r"(%s-?\s*(trans|pluralize).*?-%s)\s+" % (BLOCK_TAG_START, BLOCK_TAG_END), re.U)
|
||||
strip_whitespace_left = re.compile(r"\s+(%s-\s*(endtrans|pluralize).*?-?%s)" % (BLOCK_TAG_START, BLOCK_TAG_END), re.U)
|
||||
|
||||
def strip_whitespaces(src):
|
||||
src = strip_whitespace_left.sub(r'\1', src)
|
||||
src = strip_whitespace_right.sub(r'\1', src)
|
||||
return src
|
||||
|
||||
class Command(makemessages.Command):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
old_endblock_re = trans_real.endblock_re
|
||||
old_block_re = trans_real.block_re
|
||||
old_templatize = trans_real.templatize
|
||||
# Extend the regular expressions that are used to detect
|
||||
# translation blocks with an "OR jinja-syntax" clause.
|
||||
trans_real.endblock_re = re.compile(
|
||||
trans_real.endblock_re.pattern + '|' + r"""^\s*endtrans$""")
|
||||
trans_real.endblock_re.pattern + '|' + r"""^-?\s*endtrans\s*-?$""")
|
||||
trans_real.block_re = re.compile(
|
||||
trans_real.block_re.pattern + '|' + r"""^\s*trans(?:\s+(?!'|")(?=.*?=.*?)|$)""")
|
||||
trans_real.block_re.pattern + '|' + r"""^-?\s*trans(?:\s+(?!'|")(?=.*?=.*?)|-?$)""")
|
||||
trans_real.plural_re = re.compile(
|
||||
trans_real.plural_re.pattern + '|' + r"""^\s*pluralize(?:\s+.+|$)""")
|
||||
trans_real.plural_re.pattern + '|' + r"""^-?\s*pluralize(?:\s+.+|-?$)""")
|
||||
|
||||
def my_templatize(src, origin=None):
|
||||
new_src = strip_whitespaces(src)
|
||||
return old_templatize(new_src, origin)
|
||||
|
||||
trans_real.templatize = my_templatize
|
||||
|
||||
try:
|
||||
super(Command, self).handle(*args, **options)
|
||||
finally:
|
||||
trans_real.endblock_re = old_endblock_re
|
||||
trans_real.block_re = old_block_re
|
||||
trans_real.templatize = old_templatize
|
||||
|
@ -5,7 +5,7 @@ from django.http import HttpResponse
|
||||
from django.shortcuts import *
|
||||
|
||||
|
||||
__all__ = ('render_to_string', 'render_to_response',)
|
||||
__all__ = ('render_to_string', 'render_to_response', 'render')
|
||||
|
||||
|
||||
# Is within ``template.loader`` as per Django specification -
|
||||
@ -23,3 +23,29 @@ def render_to_response(template_name, dictionary=None, context_instance=None,
|
||||
"""
|
||||
rendered = render_to_string(template_name, dictionary, context_instance)
|
||||
return HttpResponse(rendered, mimetype=mimetype)
|
||||
|
||||
|
||||
def render(request, *args, **kwargs):
|
||||
"""
|
||||
Returns a HttpResponse whose content is filled with the result of calling
|
||||
coffin.template.loader.render_to_string() with the passed arguments.
|
||||
Uses a RequestContext by default.
|
||||
"""
|
||||
httpresponse_kwargs = {
|
||||
'content_type': kwargs.pop('content_type', None),
|
||||
'status': kwargs.pop('status', None),
|
||||
}
|
||||
|
||||
if 'context_instance' in kwargs:
|
||||
context_instance = kwargs.pop('context_instance')
|
||||
if kwargs.get('current_app', None):
|
||||
raise ValueError('If you provide a context_instance you must '
|
||||
'set its current_app before calling render()')
|
||||
else:
|
||||
current_app = kwargs.pop('current_app', None)
|
||||
context_instance = RequestContext(request, current_app=current_app)
|
||||
|
||||
kwargs['context_instance'] = context_instance
|
||||
|
||||
return HttpResponse(render_to_string(*args, **kwargs),
|
||||
**httpresponse_kwargs)
|
||||
|
@ -88,6 +88,10 @@ def add_to_builtins(module_name):
|
||||
You can still use Django's own ``add_to_builtins`` to register
|
||||
directly with Django and bypass Coffin.
|
||||
|
||||
Once thing that is special about Coffin is that because {% load %}
|
||||
is not supported in Coffin, *everything* it provides must be
|
||||
registered through the builtins.
|
||||
|
||||
TODO: Allow passing path to (or reference of) extensions and
|
||||
filters directly. This would make it easier to use this function
|
||||
with 3rd party Jinja extensions that do not know about Coffin and
|
||||
@ -103,4 +107,5 @@ def add_to_builtins(module_name):
|
||||
|
||||
add_to_builtins('coffin.template.defaulttags')
|
||||
add_to_builtins('coffin.template.defaultfilters')
|
||||
add_to_builtins('coffin.templatetags.static')
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import re
|
||||
from jinja2 import loaders
|
||||
|
||||
match_loader = re.compile(r'^(django|coffin)\.')
|
||||
|
||||
def jinja_loader_from_django_loader(django_loader):
|
||||
|
||||
def jinja_loader_from_django_loader(django_loader, args=None):
|
||||
"""Attempts to make a conversion from the given Django loader to an
|
||||
similarly-behaving Jinja loader.
|
||||
|
||||
@ -9,9 +12,11 @@ def jinja_loader_from_django_loader(django_loader):
|
||||
:return: The similarly-behaving Jinja loader, or None if a similar loader
|
||||
could not be found.
|
||||
"""
|
||||
if not match_loader.match(django_loader):
|
||||
return None
|
||||
for substr, func in _JINJA_LOADER_BY_DJANGO_SUBSTR.iteritems():
|
||||
if substr in django_loader:
|
||||
return func()
|
||||
return func(*(args or []))
|
||||
return None
|
||||
|
||||
|
||||
@ -31,8 +36,56 @@ def _make_jinja_filesystem_loader():
|
||||
return loaders.FileSystemLoader(settings.TEMPLATE_DIRS)
|
||||
|
||||
|
||||
def _make_jinja_cached_loader(*loaders):
|
||||
"""Makes a loader for Jinja which acts like
|
||||
:mod:`django.template.loaders.cached`.
|
||||
"""
|
||||
return JinjaCachedLoader(
|
||||
[jinja_loader_from_django_loader(l) for l in loaders])
|
||||
|
||||
|
||||
# Determine loaders from Django's conf.
|
||||
_JINJA_LOADER_BY_DJANGO_SUBSTR = { # {substr: callable, ...}
|
||||
'app_directories': _make_jinja_app_loader,
|
||||
'filesystem': _make_jinja_filesystem_loader,
|
||||
'cached': _make_jinja_cached_loader,
|
||||
'AppLoader': _make_jinja_app_loader,
|
||||
'FileSystemLoader': _make_jinja_filesystem_loader,
|
||||
}
|
||||
|
||||
|
||||
class JinjaCachedLoader(loaders.BaseLoader):
|
||||
"""A "sort of" port of of Django's "cached" template loader
|
||||
to Jinja 2. It exists primarily to support Django's full
|
||||
TEMPLATE_LOADERS syntax.
|
||||
|
||||
However, note that it does not behave exactly like Django's cached
|
||||
loader: Rather than caching the compiled template, it only caches
|
||||
the template source, and recompiles the template every time. This is
|
||||
due to the way the Jinja2/Coffin loader setup works: The ChoiceLoader,
|
||||
which Coffin uses at the root to select from any of the configured
|
||||
loaders, calls the ``get_source`` method of each loader directly,
|
||||
bypassing ``load``. Our loader can therefore only hook into the process
|
||||
BEFORE template compilation.
|
||||
Caching the compiled templates by implementing ``load`` would only
|
||||
work if this loader instance were the root loader. See also the comments
|
||||
in Jinja2's BaseLoader class.
|
||||
|
||||
Note that Jinja2 has an environment-wide bytecode cache (i.e. it caches
|
||||
compiled templates), that can function alongside with this class.
|
||||
|
||||
Note further that Jinja2 has an environment-wide template cache (via the
|
||||
``auto_reload`` environment option), which duplicate the functionality
|
||||
of this class entirely, and should be preferred when possible.
|
||||
"""
|
||||
|
||||
def __init__(self, subloaders):
|
||||
self.loader = loaders.ChoiceLoader(subloaders)
|
||||
self.template_cache = {}
|
||||
|
||||
def get_source(self, environment, template):
|
||||
key = (environment, template)
|
||||
if key not in self.template_cache:
|
||||
result = self.loader.get_source(environment, template)
|
||||
self.template_cache[key] = result
|
||||
return self.template_cache[key]
|
||||
|
0
coffin/templatetags/__init__.py
Normal file
0
coffin/templatetags/__init__.py
Normal file
130
coffin/templatetags/static.py
Normal file
130
coffin/templatetags/static.py
Normal file
@ -0,0 +1,130 @@
|
||||
try:
|
||||
from urllib.parse import urljoin
|
||||
except ImportError: # Python 2
|
||||
from urlparse import urljoin
|
||||
|
||||
from coffin.template import Library
|
||||
from jinja2.ext import Extension
|
||||
from jinja2 import nodes
|
||||
from django.utils.encoding import iri_to_uri
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
class PrefixExtension(Extension):
|
||||
|
||||
def parse(self, parser):
|
||||
stream = parser.stream
|
||||
lineno = stream.next().lineno
|
||||
|
||||
call_node = self.call_method('render')
|
||||
|
||||
if stream.next_if('name:as'):
|
||||
var = nodes.Name(stream.expect('name').value, 'store')
|
||||
return nodes.Assign(var, call_node).set_lineno(lineno)
|
||||
else:
|
||||
return nodes.Output([call_node]).set_lineno(lineno)
|
||||
|
||||
def render(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def get_uri_setting(cls, name):
|
||||
try:
|
||||
from django.conf import settings
|
||||
except ImportError:
|
||||
prefix = ''
|
||||
else:
|
||||
prefix = iri_to_uri(getattr(settings, name, ''))
|
||||
return prefix
|
||||
|
||||
|
||||
class GetStaticPrefixExtension(PrefixExtension):
|
||||
"""
|
||||
Populates a template variable with the static prefix,
|
||||
``settings.STATIC_URL``.
|
||||
|
||||
Usage::
|
||||
|
||||
{% get_static_prefix [as varname] %}
|
||||
|
||||
Examples::
|
||||
|
||||
{% get_static_prefix %}
|
||||
{% get_static_prefix as static_prefix %}
|
||||
|
||||
"""
|
||||
|
||||
tags = set(['get_static_prefix'])
|
||||
|
||||
def render(self):
|
||||
return self.get_uri_setting('STATIC_URL')
|
||||
|
||||
|
||||
class GetMediaPrefixExtension(PrefixExtension):
|
||||
"""
|
||||
Populates a template variable with the media prefix,
|
||||
``settings.MEDIA_URL``.
|
||||
|
||||
Usage::
|
||||
|
||||
{% get_media_prefix [as varname] %}
|
||||
|
||||
Examples::
|
||||
|
||||
{% get_media_prefix %}
|
||||
{% get_media_prefix as media_prefix %}
|
||||
|
||||
"""
|
||||
|
||||
tags = set(['get_media_prefix'])
|
||||
|
||||
def render(self):
|
||||
return self.get_uri_setting('STATIC_URL')
|
||||
|
||||
|
||||
class StaticExtension(PrefixExtension):
|
||||
"""
|
||||
Joins the given path with the STATIC_URL setting.
|
||||
|
||||
Usage::
|
||||
|
||||
{% static path [as varname] %}
|
||||
|
||||
Examples::
|
||||
|
||||
{% static "myapp/css/base.css" %}
|
||||
{% static variable_with_path %}
|
||||
{% static "myapp/css/base.css" as admin_base_css %}
|
||||
{% static variable_with_path as varname %}
|
||||
|
||||
"""
|
||||
|
||||
tags = set(['static'])
|
||||
|
||||
def parse(self, parser):
|
||||
stream = parser.stream
|
||||
lineno = stream.next().lineno
|
||||
|
||||
path = parser.parse_expression()
|
||||
call_node = self.call_method('get_statc_url', args=[path])
|
||||
|
||||
if stream.next_if('name:as'):
|
||||
var = nodes.Name(stream.expect('name').value, 'store')
|
||||
return nodes.Assign(var, call_node).set_lineno(lineno)
|
||||
else:
|
||||
return nodes.Output([call_node]).set_lineno(lineno)
|
||||
|
||||
@classmethod
|
||||
def get_statc_url(cls, path):
|
||||
return urljoin(PrefixExtension.get_uri_setting("STATIC_URL"), path)
|
||||
|
||||
|
||||
register.tag(GetStaticPrefixExtension)
|
||||
register.tag(GetMediaPrefixExtension)
|
||||
register.tag(StaticExtension)
|
||||
|
||||
|
||||
def static(path):
|
||||
return StaticExtension.get_static_url(path)
|
9
coffin/views/decorators.py
Normal file
9
coffin/views/decorators.py
Normal file
@ -0,0 +1,9 @@
|
||||
from coffin.template.response import TemplateResponse
|
||||
|
||||
def template_response(cls):
|
||||
"""
|
||||
A decorator to enforce class_based generic views
|
||||
to use coffin TemplateResponse
|
||||
"""
|
||||
cls.response_class = TemplateResponse
|
||||
return cls
|
13
coffin/views/generic/base.py
Normal file
13
coffin/views/generic/base.py
Normal file
@ -0,0 +1,13 @@
|
||||
import django.views.generic.base as _generic_base
|
||||
from coffin.template.response import TemplateResponse as JinjaTemplateResponse
|
||||
|
||||
class TemplateResponseMixin(_generic_base.TemplateResponseMixin):
|
||||
"""
|
||||
A mixin that can be used to render a template using Jinja.
|
||||
"""
|
||||
response_class = JinjaTemplateResponse
|
||||
|
||||
class TemplateView(TemplateResponseMixin, _generic_base.TemplateView):
|
||||
"""
|
||||
A view that renders a template using Jinja.
|
||||
"""
|
50
coffin/views/generic/dates.py
Normal file
50
coffin/views/generic/dates.py
Normal file
@ -0,0 +1,50 @@
|
||||
from coffin.views.generic.detail import SingleObjectTemplateResponseMixin
|
||||
from coffin.views.generic.list import MultipleObjectTemplateResponseMixin
|
||||
import django.views.generic.dates as _generic_dates
|
||||
|
||||
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseArchiveIndexView):
|
||||
"""
|
||||
Equivalent of django generic view ArchiveIndexView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive'
|
||||
|
||||
|
||||
class YearArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseYearArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view YearArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_year'
|
||||
|
||||
|
||||
class MonthArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseMonthArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view MonthArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_month'
|
||||
|
||||
|
||||
class WeekArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseWeekArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view WeekArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_week'
|
||||
|
||||
|
||||
class DayArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseDayArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view DayArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
class TodayArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseTodayArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view TodayArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
|
||||
class DateDetailView(SingleObjectTemplateResponseMixin, _generic_dates.BaseDateDetailView):
|
||||
"""
|
||||
Equivalent of django generic view DateDetailView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_detail'
|
12
coffin/views/generic/detail.py
Normal file
12
coffin/views/generic/detail.py
Normal file
@ -0,0 +1,12 @@
|
||||
import django.views.generic.detail as _generic_detail
|
||||
from coffin.views.generic.base import TemplateResponseMixin as JinjaTemplateResponseMixin
|
||||
|
||||
class SingleObjectTemplateResponseMixin(JinjaTemplateResponseMixin, _generic_detail.SingleObjectTemplateResponseMixin):
|
||||
"""
|
||||
Equivalent of django mixin SingleObjectTemplateResponseMixin, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
class DetailView(SingleObjectTemplateResponseMixin, _generic_detail.BaseDetailView):
|
||||
"""
|
||||
Equivalent of django generic view DetailView, but uses Jinja template renderer.
|
||||
"""
|
30
coffin/views/generic/edit.py
Normal file
30
coffin/views/generic/edit.py
Normal file
@ -0,0 +1,30 @@
|
||||
from coffin.views.generic.base import TemplateResponseMixin
|
||||
from coffin.views.generic.detail import SingleObjectTemplateResponseMixin
|
||||
import django.views.generic.edit as _generic_edit
|
||||
|
||||
|
||||
class FormView(TemplateResponseMixin, _generic_edit.BaseFormView):
|
||||
"""
|
||||
Equivalent of django generic view FormView, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
|
||||
class CreateView(SingleObjectTemplateResponseMixin, _generic_edit.BaseCreateView):
|
||||
"""
|
||||
Equivalent of django generic view CreateView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_form'
|
||||
|
||||
|
||||
class UpdateView(SingleObjectTemplateResponseMixin, _generic_edit.BaseUpdateView):
|
||||
"""
|
||||
Equivalent of django generic view UpdateView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_form'
|
||||
|
||||
|
||||
class DeleteView(SingleObjectTemplateResponseMixin, _generic_edit.BaseDeleteView):
|
||||
"""
|
||||
Equivalent of django generic view DeleteView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_confirm_delete'
|
12
coffin/views/generic/list.py
Normal file
12
coffin/views/generic/list.py
Normal file
@ -0,0 +1,12 @@
|
||||
import django.views.generic.list as _generic_list
|
||||
from coffin.views.generic.base import TemplateResponseMixin as JinjaTemplateResponseMixin
|
||||
|
||||
class MultipleObjectTemplateResponseMixin(JinjaTemplateResponseMixin, _generic_list.MultipleObjectTemplateResponseMixin):
|
||||
"""
|
||||
Equivalent of django mixin MultipleObjectTemplateResponseMixin, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
class ListView(MultipleObjectTemplateResponseMixin, _generic_list.BaseListView):
|
||||
"""
|
||||
Equivalent of django generic view ListView, but uses Jinja template renderer.
|
||||
"""
|
@ -1,9 +1,10 @@
|
||||
from os import path
|
||||
import sys
|
||||
|
||||
def setup_package():
|
||||
# setup Django with our test demo project
|
||||
sys.path.insert(0, path.join(path.dirname(__file__), 'res', 'apps'))
|
||||
from django.core.management import setup_environ
|
||||
import settings
|
||||
setup_environ(settings)
|
||||
# Setup Django with our test demo project. We need to do this in global
|
||||
# module code rather than setup_package(), because we want it to run
|
||||
# before any module-wide imports in any of the test modules.
|
||||
sys.path.insert(0, path.join(path.dirname(__file__), 'res', 'apps'))
|
||||
from django.core.management import setup_environ
|
||||
import settings
|
||||
setup_environ(settings)
|
||||
|
46
tests/test_env.py
Normal file
46
tests/test_env.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""Test construction of the implicitly provided JinjaEnvironment,
|
||||
in the common.py module.
|
||||
"""
|
||||
|
||||
from coffin.common import get_env
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
def test_i18n():
|
||||
with override_settings(USE_I18N=True):
|
||||
assert get_env().from_string('{{ _("test") }}').render() == 'test'
|
||||
|
||||
|
||||
class TestLoaders:
|
||||
|
||||
def test_django_loader_replace(self):
|
||||
from coffin.template.loaders import jinja_loader_from_django_loader
|
||||
from jinja2 import loaders
|
||||
|
||||
# Test replacement of filesystem loader
|
||||
l = jinja_loader_from_django_loader('django.template.loaders.filesystem.Loader')
|
||||
assert isinstance(l, loaders.FileSystemLoader)
|
||||
|
||||
# Since we don't do exact matches for the loader string, make sure we
|
||||
# are not replacing loaders that are outside the Django namespace.
|
||||
l = jinja_loader_from_django_loader('djangoaddon.template.loaders.filesystem.Loader')
|
||||
assert not isinstance(l, loaders.FileSystemLoader)
|
||||
|
||||
def test_cached_loader(self):
|
||||
from jinja2 import loaders
|
||||
|
||||
with override_settings(TEMPLATE_LOADERS=[
|
||||
('django.template.loaders.cached.Loader', (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)),]):
|
||||
env = get_env()
|
||||
assert len(env.loader.loaders) == 1
|
||||
cached_loader = get_env().loader.loaders[0]
|
||||
assert hasattr(cached_loader, 'template_cache')
|
||||
assert len(cached_loader.loader.loaders) == 2
|
||||
assert isinstance(cached_loader.loader.loaders[0], loaders.FileSystemLoader)
|
||||
|
||||
# the cached loader can find a template too.
|
||||
assert env.loader.load(env, 'render-x.html').render({'x': 'foo'}) == 'foo'
|
||||
|
5
tests/test_shortcuts.py
Normal file
5
tests/test_shortcuts.py
Normal file
@ -0,0 +1,5 @@
|
||||
def test_render():
|
||||
"""Test the render shortcut."""
|
||||
from coffin.shortcuts import render
|
||||
response = render(None, 'render-x.html', {'x': 'foo'})
|
||||
assert response.content == 'foo'
|
Loading…
Reference in New Issue
Block a user