I would like to be able to render some or all of the views in my project with a different base template. In other words, for url /some/view I would like to be able to have /inline/some/view and have it render the same content, but using a different base template.
Modifying each view to accept a different template is not an option, because I would like to apply this behaviour across all apps in the project, including things like django.contrib.auth.
So far, I have, in urls.py:
url("^inline/(?P<uri>.*)", format.inline, name='inline'),
And the view, format.py:
from django.core.urlresolvers import resolve
def inline(request, uri=''):
# get the view that would normally handle this request
view, args, kwargs = resolve('/' + uri)
# call the view
kwargs['request'] = request
template_response = view(*args, **kwargs)
# ...now what?
I'm not sure where to go from here. Can I modify the entire template chain before I call view(), so that template_response.render() does the right thing?
Perhaps I am entirely off-base with this approach and should be looking at a middleware solution, but I am attached to the idea of this behaviour keying off URLs, because it will be easy to explain to the content people later on.
UPDATE
I was able to achieve the effect I desired, but the implementation is severely lacking. Here's what I did:
- copied the templates for the views I wished to inline into
templates/inline/ - replaced
{% extends base.html %}with{% extends inline/base.html %} modified the view thus:
from django.core.urlresolvers import resolve def inline(request, uri=''): # get the view that would normally handle this request view, args, kwargs = resolve('/' + uri) # call the view kwargs['request'] = request template_response = view(*args, **kwargs) response.template_name = os.path.join('inline', response.template_name) return response
I don't like this solution because it will require those inline templates to be managed, being replaced/updated whenever apps in the project change, and so on. I would still dearly love a cleaner solution.
Update 2: Solution
chris-wesseling was 100% correct; a custom template loader was exactly what I needed. For posterity, here is my implementation.
app/loaders.py:
from django.conf import settings
from django.template.loader import BaseLoader
from django.template.base import TemplateDoesNotExist
import os
class BaseTemplateOverrideLoader(BaseLoader):
"""
Load templates from a specified subdirectory in the current app's directory.
"""
subdir = 'templates'
def load_template_source(self, template_name, template_dirs=None):
template_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
self.subdir
)
try:
t = os.path.join(template_dir, template_name)
with open(t, 'rb') as fp:
return (fp.read().decode(settings.FILE_CHARSET), template_dir)
except IOError:
pass
raise TemplateDoesNotExist(template_name)
class InlineTemplateLoader(BaseTemplateOverrideLoader):
"""
Override the location of base.html for inline views.
"""
is_usable = True
subdir = 'templates/inline'
# ... other custom override classes here ....
app/views/inline.py:
from django.conf import settings
from django.core.urlresolvers import resolve
from django.template import loader
def default(request, slug=None):
view, args, kwargs = resolve('/' + slug)
old_loaders = settings.TEMPLATE_LOADERS
# Temporarily insert the inline template loader into TEMPLATE_LOADERS;
# we must also force BaseLoader to reload all templates loaders since
# they are cached at compile time.
settings.TEMPLATE_LOADERS = ('app.loaders.InlineTemplateLoader', ) + \
settings.TEMPLATE_LOADERS
loader.template_source_loaders = None
# now call the original view that handles this request
kwargs['request'] = request
response = view(*args, **kwargs)
response_string = response.render()
# restore the template loaders to their original condition
settings.TEMPLATE_LOADERS = old_loaders
loader.template_source_loaders = None
return response_string
app/templates/inline/base.html:
{% comment %}
inline/base.html
-- render just the main content without any styles etc,
for loading as inline content via ajax or whatever.
{% endcomment %}
{% block main %}{% endblock %}
{% extends variable %} uses the value of variable. If the variable evaluates to a string, Django will use that string as the name of the parent template. If the variable evaluates to a Template object, Django will use that object as the parent template.