I'd like to know the reasoning behind using url_for to generate links in templates and the application code.
What do I gain by doing this:
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
and this:
<ul>
<li>Home</li>
<li>About Us</li>
<li>Contact</li>
</ul>
Instead of hard coding the paths?
From Flask's documentation,
flask.url_for(endpoint, **values)
Generates a URL to the given endpoint with the method provided.
Variable arguments that are unknown to the target endpoint are
appended to the generated URL as query arguments. If the value of a
query argument is None, the whole pair is skipped. In case blueprints
are active you can shortcut references to the same blueprint by
prefixing the local endpoint with a dot (.).
Now, instead of specifying static urls to reach an endpoint, you can use url_for which does a reverse match for the endpoint. It is particularly useful when you have arguments which you might want to specify at runtime.
{{ url_for('events', user_id=user.id, year=2013) }}
/events/1388224/2013
The paths generated are always absolute (start with a "/") so that they work regardless of the current URL. They also take into account if the application is mounted at a prefix rather than the root (like "/myapp/events/1/2013").
The _external=True argument can be used to generate URLs with the server name so that they can be used externally, such as in email messages.
View your events: {{ url_for("events", user_id=user.id, year=2013, _external=True) }}
Related
I have a base.html template which I would like to use for all pages. This base.html contains a navigation
<nav>
<li>Home</li>
<li>bar</li>
</nav>
This is no problem when I'm on the same level (e.g. localhost:5000/whatever), but when I'm in a subfolder (e.g. localhost:5000/whatever/insert) the links break.
This can be fixed by making the relative links absolute, e.g.
<nav>
<li>Home</li>
<li>bar</li>
</nav>
However, I don't know how to get the base_url. If possible, I would like to avoid adding base_url to each render_template call. And, if possible, I would also like to avoid to set base_url manually.
How is this problem solved with Flask / Jinja2?
Don't worry about a base url; if home and foo are routes in your Flask app, use the url_for() function to build your URLs instead:
<nav>
<li>Home</li>
<li>bar</li>
</nav>
Also see the URL Building section of the Flask Quickstart documentation:
Why would you want to build URLs using the URL reversing function url_for() instead of hard-coding them into your templates?
Reversing is often more descriptive than hard-coding the URLs.
You can change your URLs in one go instead of needing to remember to
manually change hard-coded URLs.
You can change your URLs in one go instead of needing to remember to manually change hard-coded URLs
URL building handles escaping of special characters and Unicode data
transparently.
The generated paths are always absolute, avoiding unexpected behavior of relative paths in browsers.
If your application is placed outside the URL root, for example, in
/myapplication instead of /, url_for() properly handles that for you.
If you have elements like,meta, a navbar, etc in your base.html that you would like to display across all pages in your site. You can type this at the top of each new page.
{% extends "base.html" %}
{% block content %}
<!-- write page specific html between here -->
{% endblock %}
its important to place
{% block content %}
{% endblock %}
within you base.html file.
I have a number jinja templates; each share a number of common stylesheets and js resources. Within Flask, I use the url_for method to identify the URL for each.
Eg.
icomoonstyle = url_for('static', filename='css/icons/icomoon/styles.css')
bootstrapstyle = url_for('static', filename='css/bootstrap.min.css')
corestyle = url_for('static',filename='css/core.min.css')
My question is; how do I share these variables within different routes without having to re-specify the above code under every decorator function?
Would I be right in saying that, anything global like this, should be stored within some kind of database or memcache (redis, mongo, etc.)? OR is there a best-practice way to safely store global variables like this within code elsewhere?
No, these are static values, they don't belong in a database or cache; they should be defined in code.
You can make items available for all Jinja2 templates by putting them into Environment.globals, see the docs.
You can make them available directly to all of your templates by using app.context_processor to add values to the Jinja2 environment:
#app.context_processor
def provide_links():
with app.app_context():
return {
"icomoonstyle": url_for('static', filename='css/icons/icomoon/styles.css'),
"bootstrapstyle": url_for('static', filename='css/bootstrap.min.css'),
"corestyle": url_for('static',filename='css/core.min.css')
}
Then all your Jinja templates will be able to use the variables defined in the returned dictionary:
<link rel="stylesheet" href="{{ icomoonstyle }}">
Even better, you can put all of your styles under a single list:
return {"STYLES": [
url_for('static', filename='css/icons/icomoon/styles.css'),
url_for('static', filename='css/bootstrap.min.css'),
url_for('static',filename='css/core.min.css')
]}
and then loop over them (assuming you are only going to be using them in one place):
{% for style in STYLES %}
<link rel="stylesheet" href="{{ style }}">
{% endfor %}
In Django, when I use:
{{ request.build_absolute_uri }}{% static "img/myimage.jpg" %}
It produces: 'http://myurl.com//static/img/myimage.jpg'. This produces an error.
How can I remove the double slashes?
The STATIC URL is:
STATIC_URL = '/static/'
But I don't think removing the first '/' would be a good idea.
The request object is available within your templates and you can easily access attributes such as request.scheme or request.META.HTTP_HOST to construct your base URL that you can prepend ahead of your static URL to get the full URL.
Final example would look something like this:
<img src="{{request.scheme}}://{{request.META.HTTP_HOST}}{% static 'img/myimage.jpg' %}">
The build_absolute_uri method builds an absolute uri for the current page. That means that if you're on e.g. 'http://myurl.com/login/', the resulted full url would be 'http://myurl.com/login//static/img/myimage.jpg'.
Instead, use request.get_host() (optionally together with request.scheme for the url scheme), or preferably, use the sites framework to set a template variable to the current site domain. The get_host() method has some issues regarding proxies.
The get_host() method will return the current domain without a path appended.
I just made a quick template tag for doing this. Create files /myapp/templatetags/__init__.py and /myapp/templatetags/my_tag_library.py, if you don't have them already, and add the following to my_tag_library.py:
from django import template
from django.templatetags import static
register = template.Library()
class FullStaticNode(static.StaticNode):
def url(self, context):
request = context['request']
return request.build_absolute_uri(super().url(context))
#register.tag('fullstatic')
def do_static(parser, token):
return FullStaticNode.handle_token(parser, token)
Then in your templates, just {% load my_tag_library %} and use e.g. {% fullstatic my_image.jpg %}.
In response to earlier comments wondering why someone would need to do this, my particular use case was that I wanted to put a link to a static file inside of an open graph protocol meta tag, and those links need to be absolute. In development the static files get served locally, but in production they get served remotely, so I couldn't just prepend the host to get the full url.
Is this worth an update (Django 2+)?
This helped me specifically because I was trying to put a query in the link, i.e. the myimage.jpg was actually pulling from the DB. I needed a way to put it in the src, which was to replace 'myimage.jpg' with {{ img_link_field_in_model }}.
<img src="{% get_static_prefix %}img/myimage.jpg">
will produce:
<img src="/static/img/myimage.jpg">
The example of the query is:
<img src="{% get_static_prefix %}img/{{img_link_from_model}}">
Use this for apps:
{{ request.build_absolute_uri|slice:":-1" }}{% static "img/myimage.jpg" %}
Use this generally:
{{ request.scheme }}://{{ request.META.HTTP_HOST }}{% static "img/myimage.jpg" %}
Not entirely sure what you're asking, but since the {% static .. %} is only adding /static/ to the front of your path you specify, you could just do that yourself:
{{ request.build_absolute_uri }}static/img/myimage.jpg
Not very modular, but then again most times you don't need direct access to the full url since it will just append it onto whatever url you're at if you use it as a src for some html object.
build_absolute_uri takes the location as an argument which handles the double slash problem.
Unfortunately you cannot pass arguments via the django template language.
You will need to build a custom template tag or filter which accepts an argument to the build_absolute_uri function.
One of the many reasons I prefer Jinja as I can just do this:
{{ request.build_absolute_uri(static('img/foo.png')) }}
I'd like everything to function correctly, except when it's mobile, the entire site will used a set of specific templates.
Also, I'd like to autodetect if it's mobile. If so, then use that set of templates throughout the entire site.
Have two sets of templates, one for mobile, one for desktop. Store the filenames in a pair of dictionaries, and use the User-agent header to detect which set should be used. Also allow manual selection of which site to use via a session entry.
If you place a class on your body (Django uses something similar to specify what column style to use), you could use the same templates but simply use different stylesheets. I'm not sure what main differences you are using separate templates for, but this might allow you to cut down on re-coding the templates multiple times.
best practice: use minidetector to add the extra info to the request, then use django's built in request context to pass it to your templates like so.
from django.shortcuts import render_to_response
from django.template import RequestContext
def my_view_on_mobile_and_desktop(request)
.....
render_to_response('regular_template.html',
{'my vars to template':vars},
context_instance=RequestContext(request))
then in your template you are able to introduce stuff like:
<html>
<head>
{% block head %}
<title>blah</title>
{% if request.mobile %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-mobile.css">
{% else %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-desktop.css">
{% endif %}
</head>
<body>
<div id="navigation">
{% include "_navigation.html" %}
</div>
{% if not request.mobile %}
<div id="sidebar">
<p> sidebar content not fit for mobile </p>
</div>
{% endif %>
<div id="content">
<article>
{% if not request.mobile %}
<aside>
<p> aside content </p>
</aside>
{% endif %}
<p> article content </p>
</aricle>
</div>
</body>
</html>
There are different strategies.
If you've a lot of views that renders to template files for the web version, and don't want to rewrite all views checking if the request is coming from a mobile user-agent, you'd be better writing a Middleware.
A workflow could be like this:
def process request:
if from_mobile:
settings.TEMPLATE_DIRS=settings.TEMPLATE_MOBILE_DIRS
else:
settings.TEMPLATE_DIRS=settings.TEMPLATE_WEB_DIRS
There is only a little problem here: As Django Documentation reports, it's not correct to change settings at runtime: http://docs.djangoproject.com/en/dev/topics/settings/#altering-settings-at-runtime
So you may want to call
django.conf.settings.configure(default_settings, **settings)
The answer depends heavily on the type of your target audience. If you target for modern mobile browsers equivalents to their desktop counterparts (such as WebKit-based), all you need is specific stylesheet with appropriate media query (you are basically designing for low-res rather than mobile).
Totally different strategy is needed if your site (e.g. airline schedules) must to be accessible widest possible range of mobile devices, some of running very old / limited browsers. Then custom (html) templates may be easiest way to go.
You might want to check out mobilesniffer and django-bloom to see if they fit your purposes.
I've made a Django site, but I've drank the Koolaid and I want to make an IPhone version. After putting much thought into I've come up with two options:
Make a whole other site, like i.xxxx.com. Tie it into the same database using Django's sites framework.
Find some time of middleware that reads the user-agent, and changes the template directories dynamically.
I'd really prefer option #2, however; I have some reservations, mainly because the Django documentation discourages changing settings on the fly. I found a snippet that would do the what I'd like. My main issue is having it as seamless as possible, I'd like it to be automagic and transparent to the user.
Has anyone else come across the same issue? Would anyone care to share about how they've tackled making IPhone versions of Django sites?
Update
I went with a combination of middleware and tweaking the template call.
For the middleware, I used minidetector. I like it because it detects a plethora of mobile user-agents. All I have to do is check request.mobile in my views.
For the template call tweak:
def check_mobile(request, template_name):
if request.mobile:
return 'mobile-%s'%template_name
return template_name
I use this for any view that I know I have both versions.
TODO:
Figure out how to access request.mobile in an extended version of render_to_response so I don't have to use check_mobile('template_name.html')
Using the previous automagically fallback to the regular template if no mobile version exists.
Rather than changing the template directories dynamically you could modify the request and add a value that lets your view know if the user is on an iphone or not. Then wrap render_to_response (or whatever you are using for creating HttpResponse objects) to grab the iphone version of the template instead of the standard html version if they are using an iphone.
Detect the user agent in middleware, switch the url bindings, profit!
How? Django request objects have a .urlconf attribute, which can be set by middleware.
From django docs:
Django determines the root URLconf
module to use. Ordinarily, this is the
value of the ROOT_URLCONF setting, but
if the incoming HttpRequest object has
an attribute called urlconf (set by
middleware request processing), its
value will be used in place of the
ROOT_URLCONF setting.
In yourproj/middlware.py, write a class that checks the http_user_agent string:
import re
MOBILE_AGENT_RE=re.compile(r".*(iphone|mobile|androidtouch)",re.IGNORECASE)
class MobileMiddleware(object):
def process_request(self,request):
if MOBILE_AGENT_RE.match(request.META['HTTP_USER_AGENT']):
request.urlconf="yourproj.mobile_urls"
Don't forget to add this to MIDDLEWARE_CLASSES in settings.py:
MIDDLEWARE_CLASSES= [...
'yourproj.middleware.MobileMiddleware',
...]
Create a mobile urlconf, yourproj/mobile_urls.py:
urlpatterns=patterns('',('r'/?$', 'mobile.index'), ...)
I'm developing djangobile, a django mobile extension: http://code.google.com/p/djangobile/
You should take a look at the django-mobileadmin source code, which solved exactly this problem.
Other way would be creating your own template loader that loads templates specific to user agent. This is pretty generic technique and can be use to dynamically determine what template has to be loaded depending on other factors too, like requested language (good companion to existing Django i18n machinery).
Django Book has a section on this subject.
There is a nice article which explains how to render the same data by different templates
http://www.postneo.com/2006/07/26/acknowledging-the-mobile-web-with-django
You still need to automatically redirect the user to mobile site however and this can be done using several methods (your check_mobile trick will work too)
How about redirecting user to i.xxx.com after parsing his UA in some middleware? I highly doubt that mobile users care how url look like, still they can access your site using main url.
best possible scenario: use minidetector to add the extra info to the request, then use django's built in request context to pass it to your templates like so
from django.shortcuts import render_to_response
from django.template import RequestContext
def my_view_on_mobile_and_desktop(request)
.....
render_to_response('regular_template.html',
{'my vars to template':vars},
context_instance=RequestContext(request))
then in your template you are able to introduce stuff like:
<html>
<head>
{% block head %}
<title>blah</title>
{% if request.mobile %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-mobile.css">
{% else %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-desktop.css">
{% endif %}
</head>
<body>
<div id="navigation">
{% include "_navigation.html" %}
</div>
{% if not request.mobile %}
<div id="sidebar">
<p> sidebar content not fit for mobile </p>
</div>
{% endif %>
<div id="content">
<article>
{% if not request.mobile %}
<aside>
<p> aside content </p>
</aside>
{% endif %}
<p> article content </p>
</aricle>
</div>
</body>
</html>
A simple solution is to create a wrapper around django.shortcuts.render. I put mine in a utils library in the root of my application. The wrapper works by automatically rendering templates in either a "mobile" or "desktop" folder.
In utils.shortcuts:
from django.shortcuts import render
from user_agents import parse
def my_render(request, *args, **kwargs):
"""
An extension of django.shortcuts.render.
Appends 'mobile/' or 'desktop/' to a given template location
to render the appropriate template for mobile or desktop
depends on user_agents python library
https://github.com/selwin/python-user-agents
"""
template_location = args[0]
args_list = list(args)
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
if user_agent.is_mobile:
args_list[0] = 'mobile/' + template_location
args = tuple(args_list)
return render(request, *args, **kwargs)
else:
args_list[0] = 'desktop/' + template_location
args = tuple(args_list)
return render(request, *args, **kwargs)
In view:
from utils.shortcuts import my_render
def home(request): return my_render(request, 'home.html')