evilchili
evilchili

Reputation: 73

django - render any url with a different base template

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:

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 %}

Upvotes: 1

Views: 1622

Answers (1)

Chris Wesseling
Chris Wesseling

Reputation: 6368

You can implement your own TemplateLoader and set it in your settings.TEMPLATE_LOADERS. You can have a look at this similar question for an approach of what you're trying to do.

Basically what you're looking for is a way to load base.html from a different location.

Upvotes: 1

Related Questions