Django newbie here, need help on basic middleware to redirect to another view if a certain model field is empty.
I am creating a terms of agreement page that users must get redirected to right after they signup to the platform if their filed_terms field on their Profile model is empty.
I am using middleware for this. However I am unable to get this to work. This is my middleware class:
class TermsMiddleware(object):
def process_request(self, request):
if request.user.profile.filled_terms is None:
return redirect(reverse(terms))
This gives me the following error:
global name 'terms' is not defined
I also have the url matcher that works perfectly when I navigate to it manually:
url(r'^terms/', 'my_app.views.terms')
I have a terms.html template and a terms view in my views.py file that is working perfectly in all other respects. I have also added it to the settings middleware requirements to make sure it loads.
Do I have to import something from views or url dispatcher into my middleware file? If so what would that be? I have been at this for a while an cannot find anything helpful.
reverse function takes url name instead on the regex. So you need to add name on your url configuration. Here is the example.
url(r'^terms/', 'my_app.views.terms', name='terms')
Add this in your views.py
from django.core.urlresolvers import reverse
And you need to fix your reverse function into.
return redirect(reverse('terms'))
Python interpret your terms as a variable and you have no variable named terms while you need to put string on reverse.
<p>Hello, my name is {{ name }} ! </p>
Where/how do I set the variable: name = 'mike'? I've tried putting this in the settings.py and models.py file and it does not work.
Any info of proper code to put in a .py file would be great! I've searched through the docs page but didn't see how to set a variable for retrieval.
You need to set your variable value in the view function which normally put in view.py
e.g.
def hello(request):
return render(request, "hello.html", {"name": "mike"})
And you may would like to check https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#render to find more about how to render templates with passed variables.
You need also learn more about how does Django's URL mapping works https://docs.djangoproject.com/en/dev/ref/urls/
Use context processors in django. Django handles this dilemma of making some information available to
all pages with something called context processors.
Some Code is like this,
Create a new file called context_processors.py inside your utils app directory, and add the
following code to it:
from project import settings
def context_data(request):
return {
'name': settings.name,
'request': request
}
I would like to know how to pass a variable to all my templates, without repeating the same code on every method in my views.py file?
In the example below I would like to make categories (an array of category objects) available to all templates in the web app.
Eg: I would like to avoid writing 'categories':categories on every method. Is it possible?
One view method
def front_page(request):
categories = Category.objects.all()
if is_logged_in(request) is False:
return render_to_response('users/signup.html', {'is_logged_in': is_logged_in(request), 'categories':categories}, context_instance=RequestContext(request))
else:
return render_to_response('users/front_page.html', {'is_logged_in': is_logged_in(request), 'categories':categories},context_instance=RequestContext(request))
Another view method
def another_view_method(request):
categories = Category.objects.all()
return render_to_response('eg/front_page.html', {'is_logged_in': is_logged_in(request), 'categories':categories},context_instance=RequestContext(request))
What you want is a context processor, and it's very easy to create one. Assuming you have an app named custom_app, follow the next steps:
Add custom_app to INSTALLED_APPS in settings.py (you've done it already, right?);
Create a context_processors.py into custom_app folder;
Add the following code to that new file:
def categories_processor(request):
categories = Category.objects.all()
return {'categories': categories}
Add context_processors.py to TEMPLATE_CONTEXT_PROCESSORS in settings.py
TEMPLATE_CONTEXT_PROCESSORS += ("custom_app.context_processors.categories_processor", )
And now you can use {{categories}} in all the templates :D
As of Django 1.8
To add a TEMPLATE_CONTEXT_PROCESSORS, in the settings you must add the next code:
TEMPLATES[0]['OPTIONS']['context_processors'].append("custom_app.context_processors.categories_processor")
Or include that string directly in the OPTIONS.context_processors key in your TEMPLATES setting.
My flask app layout is:
myapp/
run.py
admin/
__init__.py
views.py
pages/
index.html
main/
__init__.py
views.py
pages/
index.html
_init_.py files are empty. admin/views.py content is:
from flask import Blueprint, render_template
admin = Blueprint('admin', __name__, template_folder='pages')
#admin.route('/')
def index():
return render_template('index.html')
main/views.py is similar to admin/views.py:
from flask import Blueprint, render_template
main = Blueprint('main', __name__, template_folder='pages')
#main.route('/')
def index():
return render_template('index.html')
run.py is:
from flask import Flask
from admin.views import admin
from main.views import main
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(main, url_prefix='/main')
print app.url_map
app.run()
Now, if I access http://127.0.0.1:5000/admin/, it correctly displays admin/index.html.
However, http://127.0.0.1:5000/main/ shows still admin/index.html instead of main/index.html. I checked app.url_map:
<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index,
<Rule 'main' (HEAD, OPTIONS, GET) -> main.index,
Also, I verified that index function in main/views.py is called as expected.
If I rename main/index.html to something different then it works. So, without
renaming, how can achieve that 1http://127.0.0.1:5000/main/1 shows main/index.html?
As of Flask 0.8, blueprints add the specified template_folder to the app's searchpath, rather than treating each of the directories as separate entities. This means that if you have two templates with the same filename, the first one found in the searchpath is the one used. This is admittedly confusing, and is poorly documented at this time (see this bug). It seems that you weren't the only one that was confused by this behavior.
The design reason for this behavior is so that blueprint templates can be easily overriden from the main app's templates, which are first-in-line in Flask's template searchpath.
Two options come to mind.
Rename each of the index.html files to be unique (e.g. admin.html
and main.html).
In each of the template folders, put each of the
templates in a subdirectory of the blueprint folder and then call
the template using that subdirectory. Your admin template, for example, would be yourapp/admin/pages/admin/index.html, and then called from within
the blueprint as render_template('admin/index.html').
In addition to linqq's good suggestions above, you can also override the default functionality if needed. There are a couple ways:
One can override create_global_jinja_loader in a subclassed Flask application (which returns a DispatchingJinjaLoader defined in flask/templating.py). This is not recommended, but would work. The reason that this is discouraged is that the DispatchingJinjaLoader has enough flexiblity to support the injection of custom loaders. And if you screw your own loader up, it'll be able to lean on default, sane functionality.
So, what is recommended is that one "override the jinja_loader function" instead. This is where lack of documentation comes in. Patching Flask's loading strategy requires some knowledge that doesn't seem to be documented, as well as a good understanding of Jinja2.
There are two components you need to understand:
The Jinja2 environment
The Jinja2 template loader
These are created by Flask, with sensible defaults, automatically. (You can specify your own Jinja2 options, by the way, by overriding app.jinja_options -- but bear in mind that you'll lose two extensions which Flask includes by default -- autoescape and with -- unless you specify them yourself. Take a look at flask/app.py to see how they reference those.)
The environment contains all of those context processors (e.g., so you can do var|tojson in a template), helper functions (url_for, etc) and variables (g, session, app). It also contains a reference to a template loader, in this case the aforementioned and auto-instantiated DispatchingJinjaLoader. So when you call render_template in your app, it finds or creates the Jinja2 environment, sets up all those goodies, and calls get_template on it, which in turn calls get_source inside of the DispatchingJinjaLoader, which tries a few strategies described later.
If all goes according to plan, that chain will resolve in finding a file and will return its contents (and some other data). Also, note that this is the same execution path that {% extend 'foo.htm' %} takes.
DispatchingJinjaLoader does two things: First it checks if the app's global loader, which is app.jinja_loader can locate the file. Failing that, it checks all application blueprints (in order of registration, AFAIK) for blueprint.jinja_loader in an attempt to locate the file. Tracing that chain to the very end, here is definition of jinja_loader (in flask/helpers.py, _PackageBoundObject, the base class of both the Flask application and Blueprints):
def jinja_loader(self):
"""The Jinja loader for this package bound object.
.. versionadded:: 0.5
"""
if self.template_folder is not None:
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
Ah! So now we see. Obviously, the namespaces of both will conflict over the same directory names. Since the global loader is called first, it will always win. (FileSystemLoader is one of several standard Jinja2 loaders.) However, what this means is that there's no truly simple way to reorder the Blueprint and the application-wide template loader.
So, we need to modify the behavior of DispatchingJinjaLoader. For a while, I thought there was no good non-discouraged and efficient way of going about this. However, apparently if you override app.jinja_options['loader'] itself, we can get the behavior we want. So, if we subclass DispatchingJinjaLoader, and modify one small function (I suppose it might be better to reimplement it entirely, but this works for now), we have the behavior we want. In total, a reasonable strategy would be the following (untested, but should work with modern Flask applications):
from flask.templating import DispatchingJinjaLoader
from flask.globals import _request_ctx_stack
class ModifiedLoader(DispatchingJinjaLoader):
def _iter_loaders(self, template):
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.app.blueprints:
loader = self.app.blueprints[bp].jinja_loader
if loader is not None:
yield loader, template
loader = self.app.jinja_loader
if loader is not None:
yield loader, template
This modifies the strategy of the original loader in two ways: Attempt to load from the blueprint (and ONLY the currently executing blueprint, not all blueprints) first, and if that fails, only then load from the application. If you like the all-blueprint behavior, you can do some copy-pasta from flask/templating.py.
To tie it all together, you have to set jinja_options on the Flask object:
app = Flask(__name__)
# jinja_options is an ImmutableDict, so we have to do this song and dance
app.jinja_options = Flask.jinja_options.copy()
app.jinja_options['loader'] = ModifiedLoader(app)
The first time a template environment is needed (and thus instantiated), meaning the first time render_template is called, your loader should be used.
twooster's answer is interesting, but another problem is that Jinja by default caches a template based on its name. Because both templates are named "index.html", the loader won't run for subsequent blueprints.
Besides linqq's two suggestions, a third option is to ignore the blueprint's templates_folder option all together and place the templates in respective folders in the application's templates directory.
ie:
myapp/templates/admin/index.html
myapp/templates/main/index.html
Tks #linqq, your method really works well here, besides I made a better solution by the decorator.
Attention here, don't import the render_template function like this:
from flask import render_template
You should import the flask module like this:
import flask
Then, make this block of code at the top of your router file:
def render_decorate(path_prefix):
def decorate(func):
def dec_func(*args, **kw):
arg_list = list(args)
arg_list[0] = path_prefix + str(arg_list[0])
arg_tuple = tuple(arg_list)
return func(*arg_tuple, **kw)
return dec_func
return decorate
#render_decorate("%YOUR_DIRECTORY_NAME%/")
def render_template(template_name_or_list, **context):
return flask.render_template(template_name_or_list, **context)
Replace the %YOUR_DIRECTORY_NAME% with your actual path, and ensure your templates folder is like this:
Folder Structure
And all done! Just use the render_template function as usual.
I'm using something like this on fypress and fybb because I have a theme system.
# utils.templates
from jinja2 import Environment, PackageLoader
from flask.templating import _default_template_ctx_processor
from flask import current_app, url_for, get_flashed_messages
admin_env = Environment(
loader=PackageLoader('fypress', '/templates/admin/'),
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
autoescape=True
)
def render_template(template, **kwargs):
kwargs.update(_default_template_ctx_processor())
kwargs.update({
'url_for': url_for,
'get_flashed_messages': get_flashed_messages # etc...
})
kwargs.update(dict(debug=current_app.config.get('DEBUG'), flask_config=current_app.config))
template = admin_env.get_template(template)
return template.render(**kwargs)
And then
# routes.admin.
from flask import Blueprint
from utils.templates import render_template
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
#admin_bp.route('/')
def root():
return render_template('index.html', title='Admin')
Currently this is what works for me. First search the template in the blueprint templates folder, if not found search in the app templates folder (for layout, etc.).
from jinja2 import BaseLoader, TemplateNotFound
from flask import Flask, current_app, request
class BlueprintLoader(BaseLoader):
def get_source(self, environment, template):
for loader in (current_app.blueprints[request.blueprint].jinja_loader, current_app.jinja_loader):
try:
if loader:
return loader.get_source(environment, template)
except TemplateNotFound:
pass
raise TemplateNotFound(template)
app = Flask(__name__)
app.jinja_env.loader = BlueprintLoader()
An easy fix would be to specify the template_folder = "templates" and then on rendering the template you would then specify the blueprint name as the parent directory as shown below
#users.route("/")
def users_index():
return render_template('users/index.html')
Note that the above solution works only if you have created a sub-folder with the name of the blueprint first inside the templates folder which will be under your blueprint