Get non-hashed urls when using Django's ManifestStaticFilesStorage - django

When using ManifestStaticFilesStorage the static template function always returns the 'hashed' file name (when DEBUG=False). Is there any way to get the non-hashed regular file name in the template? It seems odd that there's no obvious way since collectstatic always includes both hashed and non-hashed files, but the non-hashed ones would never be used.
I'm guessing what I need to do is create my own templatetag, but was wondering if I missed something.
EDIT to clarify what I want...
Right now {% static 'css/style.css' %} outputs something like /static/css/style.a163843f12bc.css while I'd prefer it to result in /static/css/style.css which should always be the latest version.
I suppose another solution is to use {{ STATIC_URL }}css/style.css by adding 'django.template.context_processors.static' to the list of context processors.

If you don't want to use STATIC_URL but continue to use the url template tag, you could override url() in your custom subclass of ManifestStaticFileStorage:
# add kwarg "clean"
def url(self, name, force=False, clean=False):
if clean:
return self._url(self.clean_name, name, force)
return self._url(self.stored_name, name, force)
While stored_name(name) returns the hashed version clean_name(name) (same method signature) just replaces backslashes with slashes and returns the plain name.
This is untested but from the code in django/contrib/staticfiles/storage.py it looks like this could work.
To activate your custom FileStorage class you need to point settings.STATICFILES_STORAGE to it.
In your templates, whereever you want to use the unhashed name, you'd write:
{% static 'css/style.css' clean=True %}
Some insights on how static works:
#classmethod
def handle_simple(cls, path):
if apps.is_installed('django.contrib.staticfiles'):
from django.contrib.staticfiles.storage import staticfiles_storage
return staticfiles_storage.url(path)
else:
return urljoin(PrefixNode.handle_simple("STATIC_URL"), quote(path))
So, this means that using {{ STATIC_URL }} is not far from what {% static %} without the staticfiles app does. The staticfiles storage class adds a layer of abstraction, checks for URL fragment and does URL quoting.

Essentially what I want is just concatenating the STATIC_URL with the relative file path. However, the static template tag adds a little more functionality like proper escaping spaces and allowing assigning to template variables so I derived the following from the Django source and added it in an app as 'templatetags/simple_static.py':
from django import template
from django.templatetags.static import PrefixNode
from django.utils.six.moves.urllib.parse import quote, urljoin
register = template.Library()
#register.simple_tag
def simple_static(path):
"""
Simple concatenation of path with STATIC_URL
Usage::
{% simple_static path [as varname] %}
Examples::
{% simple_static "myapp/css/base.css" %}
{% simple_static variable_with_path %}
{% simple_static "myapp/css/base.css" as admin_base_css %}
{% simple_static variable_with_path as varname %}
"""
return urljoin(PrefixNode.handle_simple("STATIC_URL"), quote(path))
Then in the template I can do:
{% load simple_static %}{% simple_static 'css/style.css' %}
And it will output '/static/css/style.css' if STATIC_URL is '/static/'

Related

Can't use django variable in include tag

I'm trying to include a .html using
{% include "paintings/experiments/points/{{substance.name}}.html" %}
This however leads to the error TemplateDoesNotExist.
If I hardcode the name of the .html file, it does work.
{% include "paintings/experiments/points/fabric.html" %}
And, in fact, I can use {{substance.name}} inside the included html, were it does indeed get substituted for fabric. Why can I not use a django variable when using an include tag?
I doit with the add templateTag.
{% include "paintings/experiments/points/"|add:substance.name %}
Notice that substance.name should have .html . I'm using this approach to use dynamic Templates. So in a context_processor I set the variable value and use it normally, like this:
{% include ""|add:paginationTemplatePath with page=page_obj %}
In this case, I change the paginationTemplatePath given certain conditions on the context_processor.
I'm exposing this example in order to enrich the answer for other cases, as use include with variable page.
include template tag was designed to accept either string or variable. If you try to use the above, it's just going to be string. But you can manipulate strings with template filters and tags.
You can create custom template tag that creates variable and then use that newly created variable in the include tag. If you check the documentation on Custom template tags and filter you'll see how they work and what are the requirements for them to work.
First you must create a directory inside your app named templatetags
myapp/
__init__.py
models.py
templatetags/
__init__.py
custom_tags.py
views.py
Below is a possible code to create variable for template path:
from django import template
register = template.Library()
#register.simple_tag
def build_template_path(base, name):
return base.format(name)
base in your case would be "paintings/experiments/points/{}.html" and name would be source.name.
Now in the template you first have to load these custom tags with:
{% load custom_tags %}
and then you use this template tag in the template:
{% for source in sources %}
{% build_template_path "paintings/experiments/points/{}.html" source.name as template_path %}
{% include template_path %}
{% endfor %}
With build_template_path you create custom variable template_path which you then use in the include tag.
Overall, template tags are very powerful. You can create or do pretty much anything with them, while filters are a bit more limited, but you could have done that with filters as well. Maybe something like this:
#register.filter
def replace_value(value, name):
return value.replace('**', name)
{% include "paintings/experiments/points/**.html"|replace_value:source.name %}

How to use django-compressor to compress form media offline

A Django template (containing a form with media) that includes a snippet like the one below would throw an error when using django-compressor with COMPRESS_OFFLINE=True, because form is not available when offline compression is executed:
# Template snippet
{% compress js %}
{{ form.media.js }}
{% endcompress %}
Generally speaking, django-compressor provides the COMPRESS_OFFLINE_CONTEXT setting to handle similar situations. However, if a site contains many such forms or widgets with media, this solution isn't ideal.
For example, currently, I do something like this in settings.py (for each widget's media):
# settings.py
...
from my_app1.widgets import Widget1
from my_app1.widgets import Widget2
from my_app1.widgets import Widget3
...
widgets = {
'my_widget1': Widget1(),
'my_widget2': Widget2(),
'my_widget3': Widget3(),
...
}
for name, widget in widgets.items():
COMPRESS_OFFLINE_CONTEXT['{}_css'.format(name)] = widget.media['css']
COMPRESS_OFFLINE_CONTEXT['{}_js'.format(name)] = widget.media['js']
And then in templates, I do this:
{% compress js %}
{{ my_widget1_js }}
{% endcompress %}
Is there a way to handle this situation in a manner that more closely resembles Django's {{ form.media }} method, or perhaps without needing to enumerate the specific media to every widget or every form (containing media) in the site?
You can simplify your settings by using some custom class with an __getattr__ method. That way if you will pass that object into template and try to call
{{ obj.my_widget1_js }}
logic inside __getattr__ can search for specified widget in your project and return it's media.
Example of class that will auto-discover each widget:
class WidgetImporter(object):
def __getattr__(self, name):
path = name.split('__') # this will split path on double underscore, so you can define submodule here
path = [(node, "".join(part.capitalize() for part in node.split('_'))) for node in path] # this will convert underscore_names to CamelCase names.
# determining for each module if it's name is CamelCased or underscored
real_path = []
for underscored, cameled in path:
try:
__import__(".".join(real_path + [underscored])) # first trying to import umderscored module
except ImportError:
try:
__import__(".".join(real_path + [cameled])) # now try with cameled
except ImportError:
return "" # or raise some exception here if you like
else:
real_path.append(cameled)
else:
real_path.append(underscored)
last_node = real_path[len(real_path) - 1]
return getattr(__import__(".".join(real_path), fromlist=[last_node]), last_node)() # returning actual class instance
That way you can use it inside your settings.py file:
COMPRESS_OFFLINE_CONTEXT = {
'widgets': WidgetImporter(),
}
and use in your template:
{{ widgets.my_app1__widgets__widget1.media.css }}
Class will try to resolve my_app1__widgets__widget1 to actual path to your class and will try to import it. If it succeeds, it's media['css'] will be printed. It's not 100% optimal code and 100% safe (there can be some vulnerabilities if someone has access to your templates), but it will do the job.

Test the return value of a template tag

There's a boolean variable defined in settings.py that I'd like to make visible in my templates, so that I can control whether a part of the template is shown.
I've thought of creating a template tag to expose the value to the template:
#register.simple_tag
def show_something():
return settings.SHOW_SOMETHING
... which I would use in the template like this:
{% if show_something %}
Show it
{% endif %}
... but unfortunately it doesn't seem to work.
Also tried outputting the value, and this displays it as I expected:
{% show_something %}
Any idea if using a template tag is possible for what I need, or if there's a better way?
I think a template context processor might be better suited for this. Put a context_processors.py file in your project
context_processors.py
from django.conf import settings
def some_setting(request):
# Or some other logic here instead of always returning it
return {
'some_setting': settings.SOME_SETTING
}
settings.py
SOME_SETTING = False
TEMPLATE_CONTEXT_PROCESSORS = (
...,
'path.to.context_processors.some_setting'
)
and in your templates you can now access the variable with {{ some_setting }} or use it in an if statement like {% if some_setting %}Show this{% endif %}

django template: access project root path

I would like to access the root path variable in my template, how can I do that?
So far, I have:
{% if user.username == "" and request.path != "/login/" and request.path != "/" %}
<meta http-equiv="REFRESH" content="0;url=/login/">
{% else %}
I would like to write something like this:
{% if user.username == "" and request.path != projectRoot+"/login/" and request.path != projectRoot %}
<meta http-equiv="REFRESH" content="0;url=projectRoot+/login/">
{% else %}
Should I create and send this projectRoot variable from my view, or is there already a variable for that?
Many thanks in advance,
Romain
You can get the full path of the URl using request.full_path and write a template tag to check if it contains a string but this is a very bad way of trying to do what you want (redirect when unauthorized).
Instead, you should decorate your view using #login_required:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
Although I agree with Timmy that you shouldn't be trying to do this sort of redirection in the template, note that there is an easy way of getting the full URL to a view: and that is using the {% url %} tag. That tag (and the reverse function in views) is aware of the full path to your site, which it gets from the WSGI environment, so there is no need to pass an extra variable. Since you shouldn't be hard-coding URL paths anyway, using this is a win all round.

Django custom tag 'takes_context'

I`m new with django (came from Grails), especially with all those custom tags that you have to deal with, instead of writing your variables directly inside the templates.
Well, what I need to do was something really simple, but for some reason is taking me a long time to finish. What I wish to do was make a tag that checks for me if the given path is equals my current url, and then returns the class if true.
<li class="{% check_url '/login/' 'current_page_item' %}">
login
</li>
But, the problem came when I tried to register the tag with takes_context :
Caught TypeError while rendering: simple_tag() got an unexpected keyword argument 'takes_context'
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def check_url(context, path, attr):
if context['request'].environ.get('PATH_INFO') == path:
return attr
else:
return ''
How can I fix it? Also, is there a better way to do it?
That's because takes_context is only available since django 1.3.
Another approach to do it (and to avoid hardcoded urls):
{% url social_login as the_url %}
{% ifequal the_url request.path %}
....
{% endif %}
Or check out something like this!