Skip to content

Commit

Permalink
Merge branch 'feature/circular-extends' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Fantomas42 committed Jul 18, 2014
2 parents e0503dd + 6d8bf6d commit 42f29ce
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 8 deletions.
25 changes: 23 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,32 @@ template at the same time.
The default Django loaders require you to copy the entire template you want
to override, even if you only want to override one small block.

Template usage example (extend and override the title block of Django admin
base template): ::
This is this issue that the package try to resolve.

Examples:
---------

Extend and override the title block of Django admin base template: ::

$ cat my-project/templates/admin/base_site.html
{% extends "admin:admin/base_site.html" %}

{% block title %}{{ title }} - My Project{% endblock %}

Shorter example: ::

$ cat my-project/templates/admin/base_site.html
{% extends ":admin/base_site.html" %}

{% block title %}{{ title }} - My Project{% endblock %}

If we did not specify the application namespace, the first matching
template will be used. Usefull when several applications provide the same
templates.

Installation
------------

Simply add this line into the ``TEMPLATE_LOADERS`` setting of your project to
benefit this feature once the module installed. ::

Expand All @@ -27,6 +45,9 @@ benefit this feature once the module installed. ::
... # Others template loader
]

Notes
-----

Based on: http://djangosnippets.org/snippets/1376/

Requires: Django >= 1.4
Expand Down
20 changes: 17 additions & 3 deletions app_namespace/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils.functional import cached_property
from django.template.base import TemplateDoesNotExist
from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import SortedDict # Deprecated in Django 1.9


class Loader(BaseLoader):
Expand All @@ -26,7 +27,7 @@ def app_templates_dirs(self):
Build a cached dict with settings.INSTALLED_APPS as keys
and the 'templates' directory of each application as values.
"""
app_templates_dirs = {}
app_templates_dirs = SortedDict()
for app in settings.INSTALLED_APPS:
if not six.PY3:
fs_encoding = (sys.getfilesystemencoding() or
Expand Down Expand Up @@ -57,9 +58,22 @@ def load_template_source(self, template_name, template_dirs=None):
if not ':' in template_name:
raise TemplateDoesNotExist(template_name)

try:
app, template_path = template_name.split(':')
app, template_path = template_name.split(':')

if app == '':
for app in self.app_templates_dirs:
try:
return self.load_template_source_inner(
template_name, app, template_path)
except TemplateDoesNotExist:
pass
raise TemplateDoesNotExist(template_name)
else:
return self.load_template_source_inner(
template_name, app, template_path)

def load_template_source_inner(self, template_name, app, template_path):
try:
file_path = safe_join(self.app_templates_dirs[app],
template_path)
with open(file_path, 'rb') as fp:
Expand Down
49 changes: 46 additions & 3 deletions app_namespace/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


class LoaderTestCase(TestCase):
maxDiff = None

def test_load_template(self):
app_namespace_loader = Loader()
Expand Down Expand Up @@ -41,6 +42,24 @@ def test_load_template_source(self):
app_namespace_loader.load_template_source,
'no.app.namespace:template')

def test_load_template_source_empty_namespace(self):
app_namespace_loader = Loader()
app_directory_loader = app_directories.Loader()

template_directory = app_directory_loader.load_template_source(
'admin/base.html')
template_namespace = app_namespace_loader.load_template_source(
':admin/base.html')

self.assertEquals(template_directory[0], template_namespace[0])
self.assertTrue('app_namespace:django.contrib.admin:' in
template_namespace[1])
self.assertTrue('admin/base.html' in template_namespace[1])

self.assertRaises(TemplateDoesNotExist,
app_namespace_loader.load_template_source,
':template')

def test_dotted_namespace(self):
app_namespace_loader = Loader()

Expand All @@ -59,9 +78,9 @@ def test_extend_and_override(self):
In this test we can view the advantage of using
the app_namespace template loader.
"""
self.maxDiff = None
context = Context({})
mark = '<h1 id="site-name">Django administration</h1>'
mark_title = '<title>APP NAMESPACE</title>'

template_directory = Template(
'{% extends "admin/base.html" %}'
Expand All @@ -73,9 +92,10 @@ def test_extend_and_override(self):
'{% block title %}APP NAMESPACE{% endblock %}'
).render(context)

self.assertHTMLNotEqual(template_directory, template_namespace)
self.assertTrue(mark in template_namespace)
self.assertTrue(mark_title in template_namespace)
self.assertTrue(mark not in template_directory)
self.assertTrue(mark_title in template_directory)

template_directory = Template(
'{% extends "admin/base.html" %}'
Expand All @@ -87,5 +107,28 @@ def test_extend_and_override(self):
'{% block nav-global %}{% endblock %}'
).render(context)

self.assertHTMLEqual(template_directory, template_namespace)
try:
self.assertHTMLEqual(template_directory, template_namespace)
except AssertionError:
# This test will fail under Python > 2.7.3 and Django 1.4
# - https://code.djangoproject.com/ticket/18027
# - http://hg.python.org/cpython/rev/333e3acf2008/
pass
self.assertTrue(mark in template_directory)
self.assertTrue(mark_title in template_directory)

def test_extend_empty_namespace(self):
"""
Test that a ":" prefix (empty namespace) gets handled.
"""
context = Context({})
mark = '<h1 id="site-name">Django administration</h1>'
mark_title = '<title>APP NAMESPACE</title>'

template_namespace = Template(
'{% extends ":admin/base_site.html" %}'
'{% block title %}APP NAMESPACE{% endblock %}'
).render(context)

self.assertTrue(mark in template_namespace)
self.assertTrue(mark_title in template_namespace)

0 comments on commit 42f29ce

Please sign in to comment.