![]() |
Flask uses the Django-inspired Jinja2 templating language by default but can be configured to use another language. A programmer in a hurry couldn't be blamed for mixing up Django and Jinja templates. In fact, both the Django examples above work in Jinja2. Instead of going over the same examples, let's look at the places that Jinja2 is more expressive than Django templating.
Both Jinja and Django templates provide a feature called filtering, where a list can be passed through a function before being displayed. A blog that features post categories might make use of filters to display a post's categories in a comma-separated list.
- <!-- Django -->
- <div class="categories">Categories: {{ post.categories|join:", " }}</div>
- <!-- now in Jinja -->
- <div class="categories">Categories: {{ post.categories|join(", ") }}</div>
Jinja and Django for loops are also similar. Let's see where they differ. In Jinja2, the for-else-endfor construct lets you iterate over a list, but also handle the case where there are no items.
- {% for item in inventory %}
- <div class="display-item">{{ item.render() }}</div>
- {% else %}
- <div class="display-warn">
- <h3>No items found</h3>
- <p>Try another search, maybe?</p>
- </div>
- {% endfor %}
- {% for item in inventory %}
- <div class="display-item">{{ item.render }}</div>
- {% empty %}
- <div class="display-warn">
- <h3>No items found</h3>
- <p>Try another search, maybe?</p>
- </div>
- {% endfor %}
5.3 Pyramid
Like Flask, Pyramid supports many templating languages (including Jinja2 and Mako) but ships with one by default. Pyramid uses Chameleon, an implementation of ZPT (the Zope Page Template) templating language. Let's look back at our first example, adding a user's name to the top bar of our site. The Python code looks much the same except that we don't need to explicitly call a render_template function.
- @view_config(renderer='templates/home.pt')
- def my_view(request):
- # do stuff...
- return {'user': user}
- <div class="top-bar row">
- <div class="col-md-10">
- <!-- more top bar things go here -->
- </div>
- <div tal:condition="user"
- tal:content="string:You are logged in as ${user.fullname}"
- class="col-md-2 whoami">
- </div>
- </div>
METAL (Macro Expansion Template Attribute Language) is the most powerful (and complex) part of Chameleon templating. Macros are extensible, and can be defined as having slots that are filled when the macro is invoked.
6 Frameworks in Action
For each framework let's take a look at making an app called wut4lunch, a social network to tell the whole internet what you ate for lunch. Free startup idea right there, totally a gamechanger. The application will be a simple interface that allows users to post what they had for lunch and to see a list of what other users ate. The home page will look like this when we're done.
![]() |
The shortest implementation clocks in at 34 lines of Python and a single 22 line Jinja template. First we have some housekeeping tasks to do, like initializing our app and pulling in our ORM.
from flask import Flask
- # For this example we'll use SQLAlchemy, a popular ORM that supports a
- # variety of backends including SQLite, MySQL, and PostgreSQL
- from flask.ext.sqlalchemy import SQLAlchemy
- app = Flask(__name__)
- # We'll just use SQLite here so we don't need an external database
- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
- db = SQLAlchemy(app)
- class Lunch(db.Model):
- """A single lunch"""
- id = db.Column(db.Integer, primary_key=True)
- submitter = db.Column(db.String(63))
- food = db.Column(db.String(255))
String
fields in the database. Using our models is also extremely simple, thanks to the SQLAlchemy query syntax we'll see later.Building our submission form is just as easy. After importing Flask-WTForms and the correct field types, you can see the form looks quite a bit like our model. The main difference is the new submit button and prompts for the food and submitter name fields.
The SECRET_KEY field in the app config is used by WTForms to create CSRF tokens. It is also used by itsdangerous (included in Flask) to sign cookies and other data.
from flask.ext.wtf import Form
- from wtforms.fields import StringField, SubmitField
- app.config['SECRET_KEY'] = 'please, tell nobody'
- class LunchForm(Form):
- submitter = StringField(u'Hi, my name is')
- food = StringField(u'and I ate')
- # submit button will read "share my lunch!"
- submit = SubmitField(u'share my lunch!')
- from flask import render_template
- @app.route("/")
- def root():
- lunches = Lunch.query.all()
- form = LunchForm()
- return render_template('index.html', form=form, lunches=lunches)
- <html>
- <title>Wut 4 Lunch</title>
- <b>What are people eating?</b>
- <p>Wut4Lunch is the latest social network where you can tell all your friends
- about your noontime repast!</p>
- <ul>
- {% for lunch in lunches %}
- <li><strong>{{ lunch.submitter|safe }}</strong> just ate <strong>{{ lunch.food|safe }}</strong>
- {% else %}
- <li><em>Nobody has eaten lunch, you must all be starving!</em></li>
- {% endfor %}
- </ul>
- <b>What are YOU eating?</b>
- <form method="POST" action="/new">
- {{ form.hidden_tag() }}
- {{ form.submitter.label }} {{ form.submitter(size=40) }}
- <br/>
- {{ form.food.label }} {{ form.food(size=50) }}
- <br/>
- {{ form.submit }}
- </form>
- </html>
from flask import url_for, redirect
- @app.route(u'/new', methods=[u'POST'])
- def newlunch():
- form = LunchForm()
- if form.validate_on_submit():
- lunch = Lunch()
- form.populate_obj(lunch)
- db.session.add(lunch)
- db.session.commit()
- return redirect(url_for('root'))
- if __name__ == "__main__":
- db.create_all() # make our sqlalchemy tables
- app.run()
6.2 Demo App with Django
The Django version of wut4lunch is similar to the Flask version, but is spread across several files in the Django project. First, let's look at the most similar portion: the database model. The only difference between this and the SQLAlchemy version is the slightly different syntax for declaring a database field that holds text.
- # from wut4lunch/models.py
- from django.db import models
- class Lunch(models.Model):
- submitter = models.CharField(max_length=63)
- food = models.CharField(max_length=255)
- from django import forms
- from django.http import HttpResponse
- from django.shortcuts import render, redirect
- from .models import Lunch
- # Create your views here.
- class LunchForm(forms.Form):
- """Form object. Looks a lot like the WTForms Flask example"""
- submitter = forms.CharField(label='Your name')
- food = forms.CharField(label='What did you eat?')
- lunch_form = LunchForm(auto_id=False)
- def index(request):
- lunches = Lunch.objects.all()
- return render(
- request,
- 'wut4lunch/index.html',
- {
- 'lunches': lunches,
- 'form': lunch_form,
- }
- )
- def newlunch(request):
- l = Lunch()
- l.submitter = request.POST['submitter']
- l.food = request.POST['food']
- l.save()
- return redirect('home')
Django provides some nice features for us to manage the lunches that users have submitted, so we can delete lunches that aren't appropriate for our site. Flask and Pyramid don't provide this automatically, and not having to write Yet Another Admin Page when making a Django app is certainly a feature. Developer time isn't free! All we had to do to tell Django-admin about our models is add two lines to wut4lunch/admin.py.
- from wut4lunch.models import Lunch
- admin.site.register(Lunch)
Lastly, let's take a look at the differences in the homepage template.
- <ul>
- {% for lunch in lunches %}
- <li><strong>{{ lunch.submitter }}</strong> just ate <strong>{{ lunch.food }}</strong></li>
- {% empty %}
- <em>Nobody has eaten lunch, you must all be starving!</em>
- {% endfor %}
- </ul>
- <form action="{% url 'newlunch' %}" method="post">
- {% csrf_token %}
- {{ form.as_ul }}
- <input type="submit" value="I ate this!" />
- </form>
6.3 Demo App with Pyramid
Finally, let's take a look at the same program in Pyramid. The biggest difference from Django and Flask here is the templating. Changing the Jinja2 template very slightly was enough to solve our problem in Django. Not so this time, Pyramid's Chameleon template syntax is more reminiscent of XSLT than anything else.
- <!-- pyramid_wut4lunch/templates/index.pt -->
- <div tal:condition="lunches">
- <ul>
- <div tal:repeat="lunch lunches" tal:omit-tag="">
- <li tal:content="string:${lunch.submitter} just ate ${lunch.food}"/>
- </div>
- </ul>
- </div>
- <div tal:condition="not:lunches">
- <em>Nobody has eaten lunch, you must all be starving!</em>
- </div>
One of the big upsides to the Chameleon templating style is that your editor of choice will highlight the syntax correctly, since the templates are valid XHTML. For Django and Flask templates your editor needs to have support for those templating languages to highlight correctly.
- <b>What are YOU eating?</b>
- <form method="POST" action="/newlunch">
- Name: ${form.text("submitter", size=40)}
- <br/>
- What did you eat? ${form.text("food", size=40)}
- <br/>
- <input type="submit" value="I ate this!" />
- </form>
- </html>
Now let's see what backs the application. First, we'll define the form we need and render our homepage.
- # pyramid_wut4lunch/views.py
- class LunchSchema(Schema):
- submitter = validators.UnicodeString()
- food = validators.UnicodeString()
- @view_config(route_name='home',
- renderer='templates/index.pt')
- def home(request):
- lunches = DBSession.query(Lunch).all()
- form = Form(request, schema=LunchSchema())
- return {'lunches': lunches, 'form': FormRenderer(form)}
- @view_config(route_name='newlunch',
- renderer='templates/index.pt',
- request_method='POST')
- def newlunch(request):
- l = Lunch(
- submitter=request.POST.get('submitter', 'nobody'),
- food=request.POST.get('food', 'nothing'),
- )
- with transaction.manager:
- DBSession.add(l)
- raise exc.HTTPSeeOther('/')
7 Summary
Pyramid is the most flexible of the three. It can be used for small apps as we've seen here, but it also powers big-name sites like Dropbox. Open Source communities like Fedora choose it for applications like their community badges system, which receives information about events from many of the project's tools to award achievement-style badges to users. One of the most common complaints about Pyramid is that it presents so many options it can be intimidating to start a new project.
By far the most popular framework is Django, and the list of sites that use it is impressive. Bitbucket, Pinterest, Instagram, and The Onion use Django for all or part of their sites. For sites that have common requirements, Django chooses very sane defaults and because of this it has become a popular choice for mid- to large-sized web applications.
Flask is great for developers working on small projects that need a fast way to make a simple, Python-powered web site. It powers loads of small one-off tools, or simple web interfaces built over existing APIs. Backend projects that need a simple web interface that is fast to develop and will require little configuration often benefit from Flask on the frontend, like jitviewer which provides a web interface for inspecting PyPy just-in-time compiler logs.
All three frameworks came up with a solution to our small list of requirements, and we've been able to see where they differ. Those differences aren't just cosmetic, and they will change how you design your product and how fast you ship new features and fixes. Since our example was small, we've seen where Flask shines and how Django can feel clunky on a small scale. Pyramid's flexibility didn't become a factor because our requirements stayed the same, but in the real world new requirements are thrown in constantly.
Written by Ryan Brown
If you found this post interesting, follow and support us.
Suggest for you:
Zero to Hero with Python Professional Python Programmer Bundle
The Python Mega Course: Build 10 Python Applications
Complete Python Bootcamp (Hot)
Learning Python for Data Analysis and Visualization
Learn Python for Beginners!
I suggest using a code syntax highlighter, all blue is very painful to read, particularly when there are several good options of syntax highlighting in blogs.
ReplyDelete