django template: access project root path - django

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.

Related

Django - add link with custom admin page href

In my Django project, I have created a custom admin page for an app via the get_urls() method. I'd like to add a link to the app's main model index view that will take users to this custom page - however, I'm having some trouble creating this link element correctly and I don't seem to be able to piece together the right way to do it - I'm just left with a Reverse for 'export' not found. 'export' is not a valid view function or pattern name. error.
I've set up the admin for the app like so:
# my_project/observations/admin.py
from django.template.response import TemplateResponse
from django.urls import path
class ObservationAdmin(SimpleHistoryAdmin, SoftDeletionModelAdmin):
change_list_template = 'export_link.html'
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('export/', self.admin_site.admin_view(self.export_view), name='export')
]
return custom_urls + urls
def export_view(self, request):
context = dict(
self.admin_site.each_context(request),
)
return TemplateResponse(request, 'export.html', context)
and the two templates that are referenced:
# my_project/observations/templates/export.html
{% extends "admin/base_site.html" %}
{% block content %}
<div>
Some custom content
</div>
{% endblock %}
# my_project/observations/templates/export_link.html
{% extends 'admin/change_list.html' %}
{% block object-tools-items %}
<li>
Export
</li>
{{ block.super }}
{% endblock %}
Navigating directly to http://localhost:8000/admin/observations/observation/export/ works perfectly, I see the custom content page exactly as I want it... so the issue I'm striking is with the link template - I get the Reverse... error when I navigate to the model index page.
Perhaps the argument I'm passing to url is incorrect, or I need to register that URL elsewhere - but I don't quite know. The other examples of link elements like this that I've been able to find don't reference URLs created via the admin class' get_urls() method - so any guidance on this would be greatly appreciated.
Thanks very much, let me know if there's any other info that I can provide to help sort this out.
I think the problems is in missing namespace in your export_link.html template. Instead of:
Export
try:
Export

Get non-hashed urls when using Django's ManifestStaticFilesStorage

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/'

Compare urls in Django Template

In a django template I don't want to show some element in case if the url/path is a specific one. In pseudo:
{% if not url = account:detail %}
We can do this in two steps here:
first we resulve the url, and assign it to a variable (here url2); and
next compare the urls
So:
{% url account:detail as url2 %}
{% if url != url2 %}
<!-- ... (do something) ... -->
{% endif %}
Note however that if two urls are syntactically different (for example yourdomain.com/foo and /foo), that they per se point to something different.
If you want access to the current path, you can - like #RajaSimon says, usually use request.path (given you render the template with a RequestContext, render(..), or another way to pass the request object).
You can give your urls a name ( url_name ) in urls.py file and then you can compare directly using HttpRequest.resolver_match object available in template.
from django.urls import path
from . import views
urlpatterns = [
path('articles/<int:year>/', views.year_archive, name='news_year_archive'),
# ...
]
In template you compare it like,
{% if request.resolver_match.url_name == "news_year_archive" %}
...your stuff
{% endif %}

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!

Django: Figure out which item in a menu that has been selected

I'm sure I've seen this question on Stack Overflow before, but I couldn't find it by my life, so here goes nothing.
I have a normal Django menu which uses the {% url %} tag and static names for the menu items. Now I want to have a different style for the menu item which has been selected. But the menu is being rendered in the base template, so how do I figure out which menu item it is?
You could surely do this with some ugly template code, but a better more globally known way is to use a CSS selector. This lets CSS do all of the work automatically for you.
Here's how it works:
You simply put an id in your body depending on which page you are on.
Then in css you do something like this:
#section-aboutme #nav-aboutme,
#section-contact #nav-contact
/* ... put one of these per body/menu item ... */
{
font-color: red;
}
You put the nav-aboutme, and nav-contact ids on each of your menu items.
The style will automatically be selected by CSS depending on which body id they are inside of.
I normally do it the way Brian suggested, but to accommodate for a template which a designer gave me which used the more common class="selected" method, I wrote a {% nav %} template tag.
Your HTML navigation template will look something like:
{% block nav %}
<ul class="nav">
<li{% if nav.home %} class="selected"{% endif %}>Home</li>
<li{% if nav.about %} class="selected"{% endif %}>About</li>
</ul>
{% endblock %}
To set the navigation in a child template, do:
{% include "base.html" %}
{% load nav %}
{% block nav %}
{% nav "about" %}
{{ block.super }}
{% endblock %}
How about a custom tag which you use to generate your nav item?
The following takes the name of the url for which a nav item should be generated and the text it should display. It generates a li tag with a class of "selected" if the named url's path is the same as the current url (requires 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS). Within the li, it generates an a tag with the path of the url specified by the url_name. It has the contents specified by contents.
Obviously, this could be tweaked to generate different markup for the nav item, as required.
The rest can be done using CSS.
Advantages:
Easy to use
Little code required
DRY
Could be made to be more flexible
Disadvantages:
Requires 'django.core.context_processors.request'
Requires urls to be named e.g. urlpatterns = patterns('django.views.generic.simple',
...
(r'^$', 'direct_to_template', {'template': 'index.html'}, 'index'),
...
). This could potentially be done differently (e.g. pass in url).
Doesn't cope with pages not exactly equal to the specified and therefore will not apply the selected class to the li when on a page lower in the url heirarchy. For example, if I'm on /products/, it will highlight the nav item directing to /products/. If I'm on /products/myProduct/, it will not highlight the /products/ link. This could be coded around, but it would force people to use sensible urls. For example, change the additionalAttrs assignment to additionalAttrs = ' class=selected' if (context['request'].path.startswith(path) and path != '/') or (context['request'].path == path) else ''.
Code:
from django import template
from django.core.urlresolvers import reverse
register = template.Library()
class NavNode(template.Node):
def __init__(self, url_name, contents):
self.url_name = url_name
self.contents = contents
def render(self, context):
path = reverse(self.url_name)
additionalAttrs = ' class=selected' if path == context['request'].path else ''
return '<li'+additionalAttrs+'>'+self.contents+'</li>'
#register.tag
def nav_link(parser, token):
bits = token.split_contents()
if len(bits) == 3:
contents = bits.pop()
url_name = bits.pop()
else:
raise template.TemplateSyntaxError, "%r tag requires a single argument" % bits[0]
if contents[0] == contents[-1] and contents[0] in ('"', "'"):
contents = contents[1:-1]
return NavNode(url_name, contents)
You can pass request.path to your template
from django.shortcuts import render_to_response
from django.template import RequestContext
return render_to_response('templ.html', {'page':request.path}, context_instance=RequestContext(request))
then use an ugly if template tag to add a CSS class to your menu item
Let's say one has an app named "stackoverflow" and inside of that app folder one has a templates folder with a stackoverflow.html file that extends from the base template.
One way to achieve it is by defining a variable in the views.py of ones stackoverflow app, like
def stackoverflow(request):
return render(request,
'stackoverflow/stackoverflow.html',
{'section': 'stackoverflow'})
Then, in the base template
<li {% if section == "stackoverflow" %} class="selected" {% endif %}>
StackOverflow
</li>
Essentially having the variable section allows to figure the section. Note that one needs a space between section and ==... if one doesn't respect that, then one will get a Django Template Error saying
Could not parse the remainder.