Django - use custom template loader on a per-request basis? - django

Is there a lower-level way to provide the list of loaders when rendering a template, as opposed to always having Django use the setting?
I'd like to use a custom template loader instance for only a few views (I have my reasons).

It looks like you'll have to write some code of your own to do it. Let's take a look at the normal code path for loading templates, if you use, say, render_to_response, where the relevant part of the source is:
return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
That's a call to django.template.loader.render_to_string, which passes through some other functions and eventually ends up calling find_template.
The first time find_template is called, in initializes the global template_source_loaders cache based on settings.TEMPLATE_LOADERS. So it looks like there's no just extra argument you can pass in or anything like that.
One possibility might be to add some loaders to django.template.loader.template_source_loaders just for the duration of that view. I don't know if that will cause other problems; it feels dirty, but if it works, it'll be pretty easy. (Just make a view decorator that does it.)
If you don't want to do that, it looks like you'll have to replicate the work of render_to_string with your own code (if you're really sure you want to use per-view template loaders, which I'm accepting as a premise for the sake of this question but I bet isn't actually necessary). There's not all that much code there, and if you know in advance a specific loader and a single template name that you want to use, it's actually pretty easy. (This is untested but will probably pretty much work.)
def render_to_response_with_loader(loader, name,
dictionary=None, context_instance=None, mimetype=None, dirs=None):
# from find_template
t, display_name = loader(name, dirs)
# from get_template
if not hasattr(t, 'render'):
# template needs to be compiled
t = django.template.loader.get_template_from_string(t, origin, template_name)
# from render_to_string
if not context_instance:
rendered = t.render(Context(dictionary))
else:
# Add the dictionary to the context stack, ensuring it gets removed again
# to keep the context_instance in the same state it started in.
context_instance.update(dictionary)
try:
rendered = t.render(context_instance)
finally:
context_instance.pop()
# from render_to_response
return HttpResponse(rendered, mimetype=mimetype)
If you want to support multiple possible loaders or a list of possible filenames, just copy the relevant code from django.template.loader.

I ended up doing this by modifying template_source_loaders as Dougal suggested. Like he said, I'm not sure this is safe (could it create a race condition?), but it works for my particular case at the moment. The benefit of doing it this way over the other way Dougal suggested is that it makes sure that {% extends %} and {% include %} also use the modified loaders. Here's my render_to_string with custom loaders:
def render_to_string_with_loader(*args, **kwargs):
""" Call render_to_string using ReportTemplateLoader to find templates. """
import django.template.loader as loader
old_loaders = settings.TEMPLATE_LOADERS
settings.TEMPLATE_LOADERS = ('main.loaders.ReportTemplateLoader',)
loader.template_source_loaders = None # force refresh from settings
try:
out = render_to_string(*args, **kwargs)
finally:
# use finally make sure template errors can't mess up later requests
settings.TEMPLATE_LOADERS = old_loaders
loader.template_source_loaders = None

This is an old question but as of 2021 (Django 3.2), here's what I did.
What I needed:
Serve different templates depending on the request (in my case the domain used)
Use only one instance of Django (= 1 IP/port)
(some multi-domain solutions were about creating a new instance with different DIRS)
For the multi-site / multi-domain:
Add 'django.contrib.sites' in INSTALLED_APPS in settings.py
This enables the Django sites framework
Add the 'django.contrib.sites.middleware.CurrentSiteMiddleware' to MIDDLEWARES in your settings.py
This adds a site property for every request handled by Django
For using per-request templates
Create extra engines
In TEMPLATES in settings.py
For example, I use Django engine. I created the same engine again with added DIRS where it could get templates.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
},
{
'NAME': 'a.local',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates/a.local'),
os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
},
{
'NAME': 'b.local',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates/b.local'),
os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
}
]
Use the using kwarg in render()
Set the rendering engine to use in render()
Before:
render(request, 'template.html', context=context_data)
After: render(request, 'template.html', context=context_data, using=request.site.domain)
(in my case, request.site.domain is either a.local or b.local, which is precisely the name of an engine I just created)
Override render (optional, hacky)
Avoid changing all your render everywhere.
This is nasty, you shouldn't do that. Move to classed-based views. Here it is.
def render(request, *args, **kwargs):
return shortcut_render(request, *args, **kwargs, using=request.site.domain)

Related

How can I access environment variables directly in a Django template?

I'm looking to do something like this non-working code in my Django template:
{% if os.environ.DJANGO_SETTINGS_MODULE == "settings.staging" %}
Is something like this possible? My workaround is creating a context processor to make the variable available across all templates, but wanted to know if there is a more direct way to achieve the same result.
Use context_processor, do as below and you should be able to access os.environ['DJANGO_SETTINGS_MODULE'] as SETTING_TYPE in templates. Example you can use {% if SETTING_TYPE == "settings.staging" %}
project/context_processors.py
import os
def export_vars(request):
data = {}
data['SETTING_TYPE'] = os.environ['DJANGO_SETTINGS_MODULE']
return data
project/settings.py (your actual settings file)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
'project.context_processors.export_vars',
...
]
}
}
]
Extending on #vinay kumar's comment, you can create a custom template filter in the following way:
application/template_tags_folder/template_tags_file.py
from django.template.defaulttags import register
import os
#register.filter
def env(key):
return os.environ.get(key, None)
And then in your template you can access it this way:
template.html
{% if 'DJANGO_SETTINGS_MODULE'|env == 'app_name.staging_settings' %}
Finally I leave a django docs reference and a stackoverflow reference for creating custom template tags and filters in django.

How do I reset template loader cache in Django 1.9?

I used to be able to import django.template.loader.template_source_loaders and call reset() on all loaders to reset all template loader cache, but this no longer works.
How do I reset template loader cache in Django 1.9?
My settings, just in case this is useful:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'accounts/templates')],
'APP_DIRS': True
}
]
This is how I load my template:
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
from django.template.response import TemplateResponse
from django.http import HttpResponse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import ensure_csrf_cookie
#never_cache
#ensure_csrf_cookie
def view(request, **kwargs):
try:
template = get_template('index.html')
except TemplateDoesNotExist:
return HttpResponse("Run `make template`")
return TemplateResponse(request, template)
I'm experiencing this issue on local dev using the built-in runserver, with DEBUG=True. The issue does not apply on production because the template will always exist.
Django does not cache the templates, but it does cache the list of app template directories used by the app directories loader. If you create a new directory e.g. polls/templates after starting the server, then Django will not pick up templates in this directory until the server has been restarted.
Really easy:
from django.template.utils import get_app_template_dirs
get_app_template_dirs.cache_clear()

Upload custom templates in django

I want users to be able, to upload their custom html templates including css styling and png and jpeg images. These html files shall be retrievable through django. That means that they can see the resulting html content in form of a normal webpage. In order to be able to do that, I (seemingly, don't know a better approach yet) have to upload the html files in the template directory.
This is my current model:
class StylesheetFile(models.Model):
HTML = 'HTML'
CSS = 'CSS'
JPEG = 'JPEG'
PNG = 'PNG'
# Currently supported mimetypes
MIMETYPE_CHOICES = (
(HTML, 'text/html' ),
(CSS , 'text/css' ),
(JPEG, 'image/jpeg'),
(PNG , 'image/png' ),
)
mimetype = models.CharField(max_length=64, choices = MIMETYPE_CHOICES)
company = models.ForeignKey(Company)
file = models.FileField(upload_to=get_upload_path)
And this is the current function to determine the upload_path:
def get_upload_path(instance, filename):
if instance.mimetype == 'HTML':
return os.path.join(
settings.TEMPLATES[0]['DIRS'][1],
instance.company.name,
filename)
if instance.mimetype == 'CSS':
return os.path.join(
"custom_css",
instance.company.name,
filename)
if instance.mimetype == 'JPEG' or instance.mimetype == 'PNG':
return os.path.join(
"custom_img",
instance.company.name,
filename)
Template settings
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/ubuntu/app/templates/',
'/home/ubuntu/app/custom_templates',
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
When I do this the css stylings, png and jpeg files are getting uploaded correctly, but the html files not. I receive following error:
The joined path (/home/ubuntu/app/custom_templates/FooCompany/factors.html) is located outside of the base path component (/home/ubuntu/app/static/media)
What can I do to prevent this error? Is there some best practice approach to my problem or do I have to go for some workaround like setting one of m y TEMPLATE_DIRS to /home/ubuntu/app/static/media/custom_templates. Maybe this is not even a workaround and legit practice.. I don't know really know.. Help is appreciated a lot!
You cannot use FileField to upload files outside of MEDIA_ROOT. That's an important security measure.
You could set TEMPLATE_DIRS to something inside MEDIA_ROOT and most likely this would work, but this makes my cringe really hard. That would essentially gave the users an ability to overwrite any template for any page on the site.
You don't have to save those templates as HTML files to use them as Django templates. You can save them in a database and render them directly from string:
from django.template import engines
from django.http import HttpResponse
# Get the template from database
template_from_db = YourCustomTemplateModel.objects.get(name='homepage')
template_string = template_from_db.content
# Create a django Template object
template = engines['django'].from_string(template_string)
# Render the template to a string
context = {'foo': 'bar'}
page_content = template.render(context=context)
# Send page to a the browser
return HttpResponse(page_content)
You should however think really hard about the security implications of doing this in general. Are you really comfortable with template creators being able to set arbitrary JavaScript on your domain (think cross-site scripting vulnerability)? What about calling arbitrary methods (or at least the ones not having any arguments) and accessing arbitrary arguments on objects you pass in the context dictionary?

Django: How to get language code in template?

Is there's some global variable for gettin' language code in django template or atleast passing it through view?
something like: {{ LANG }} should produce "en" for example..
I really not comfortable when people using request.LANGUAGE_CODE.
Detailed explanation would be appreciated =)
It's an old topic. But some might find it useful.
{% load i18n %}
...
{% get_current_language as LANGUAGE_CODE %}
Django reference and example.
If it didn't already exist, you would need to write a template context processor. Here's how you'd do that.
Put this somewhere:
def lang_context_processor(request):
return {'LANG': request.LANGUAGE_CODE}
And then, add a reference to it the TEMPLATE_CONTEXT_PROCESSORS setting. Something like this:
from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
'myproject.myapp.templatecontext.lang_context_processor',
)
(I recommend adding to the global setting because it means you don't break things accidentally when a new context processor is added to the defaults.)
However, it does exist, as the inbuilt template context processor django.template.context_processors.i18n. You can access it as LANGUAGE_CODE.
Purely for interest, here's the definition of that function:
def i18n(request):
from django.utils import translation
return {
'LANGUAGES': settings.LANGUAGES,
'LANGUAGE_CODE': translation.get_language(),
'LANGUAGE_BIDI': translation.get_language_bidi(),
}
Make sure that you're using a RequestContext for your template rendering, not a plain Context, or it won't work.
Tested with Django==1.11.2.
Enable I18N and employ i18n template context processor.
# setings.py
USE_I18N = True
# ...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
# ...
'django.template.context_processors.i18n',
# ...
],
},
},
]
And then it's simple in the template.
# template.html
{% load i18n %}
{{ LANGUAGE_CODE }}
But use render(), not render_to_response(), in your view function so the LANGUAGE_CODE variable is accessible in the template:
render_to_response()
This function preceded the introduction of render() and works
similarly except that it doesn’t make the request available in the
response. It’s not recommended and is likely to be deprecated in the
future.

Django view - load template from calling app's dir first

I try to keep a somewhat consistent naming scheme on my HTML templates. I.e. index.html for main, delete.html for delete page and so forth. But the app_directories loader always seems to load the template from the app that's first alphabetically.
Is there any way to always check for a match in the calling app's templates directory first?
Relevant settings in my settings.py:
PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
TEMPLATE_LOADERS = (
'django.template.loaders.app_directories.load_template_source',
'django.template.loaders.filesystem.load_template_source',
)
TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'templates'),
)
I've tried changing the order of TEMPLATE_LOADERS, without success.
Edit as requested by Ashok:
Dir structure of each app:
templates/
index.html
add.html
delete.html
create.html
models.py
test.py
admin.py
views.py
In each app's views.py:
def index(request):
# code...
return render_to_response('index.html', locals())
def add(request):
# code...
return render_to_response('add.html', locals())
def delete(request):
# code...
return render_to_response('delete.html', locals())
def update(request):
# code...
return render_to_response('update.html', locals())
The reason for this is that the app_directories loader is essentially the same as adding each app's template folder to the TEMPLATE_DIRS setting, e.g. like
TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'app1', 'templates'),
os.path.join(PROJECT_PATH, 'app2', 'template'),
...
os.path.join(PROJECT_PATH, 'templates'),
)
The problem with this is that as you mentioned, the index.html will always be found in app1/templates/index.html instead of any other app. There is no easy solution to magically fix this behavior without modifying the app_directories loader and using introspection or passing along app information, which gets a bit complicated. An easier solution:
Keep your settings.py as-is
Add a subdirectory in each app's templates folder with the name of the app
Use the templates in views like 'app1/index.html' or 'app2/index.html'
For a more concrete example:
project
app1
templates
app1
index.html
add.html
...
models.py
views.py
...
app2
...
Then in the views:
def index(request):
return render_to_response('app1/index.html', locals())
You could even write a wrapper to automate prepending the app name to all your views, and even that could be extended to use introspection, e.g.:
def render(template, data=None):
return render_to_response(__name__.split(".")[-2] + '/' + template, data)
def index(request):
return render('index.html', locals())
The _____name_____.split(".")[-2] assumes the file is within a package, so it will turn e.g. 'app1.views' into 'app1' to prepend to the template name. This also assumes a user will never rename your app without also renaming the folder in the templates directory, which may not be a safe assumption to make and in that case just hard-code the name of the folder in the templates directory.
I know this is an old thread, but I made something reusable, that allows for simpler namespacing. You could load the following as a Template Loader. It will find appname/index.html in appname/templates/index.html.
Gist available here: https://gist.github.com/871567
"""
Wrapper for loading templates from "templates" directories in INSTALLED_APPS
packages, prefixed by the appname for namespacing.
This loader finds `appname/templates/index.html` when looking for something
of the form `appname/index.html`.
"""
from django.template import TemplateDoesNotExist
from django.template.loaders.app_directories import app_template_dirs, Loader as BaseAppLoader
class Loader(BaseAppLoader):
'''
Modified AppDirecotry Template Loader that allows namespacing templates
with the name of their app, without requiring an extra subdirectory
in the form of `appname/templates/appname`.
'''
def load_template_source(self, template_name, template_dirs=None):
try:
app_name, template_path = template_name.split('/', 1)
except ValueError:
raise TemplateDoesNotExist(template_name)
if not template_dirs:
template_dirs = (d for d in app_template_dirs if
d.endswith('/%s/templates' % app_name))
return iter(super(Loader, self).load_template_source(template_path,
template_dirs))
The app_loader looks for templates within your applications in order that they are specified in your INSTALLED_APPS. (http://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types).
My suggestion is to preface the name of your template file with the app name to avoid these naming conflicts.
For example, the template dir for app1 would look like:
templates/
app1_index.html
app1_delete.html
app1_add.html
app1_create.html