Have you just gotten started with Python for the web, and are wondering which Python framework to learn or use for your next project? Then you've probably heard of Django, Flask, or both. Although there are other Python web frameworks, such as Pyramid, web2py, and TurboGears, Django and Flask remain the most popular.

This series will offer an in-depth comparison of Flask and Django, looking at various factors. In Part One, we will compare their:

  1. Popularity
  2. Documentation and ease of getting started
  3. Routing system
  4. Templating system

Note that this series doesn't aim to pit the two frameworks against each other and declare an overall winner. I will simply compare and contrast them side-by-side on the aforementioned factors, in the hopes that after reading this article, you'll be able to make a more informed decision on which framework, if any, to use for a particular project. To follow along in this series, it is expected that you have a basic to intermediate understanding of Python programming.

Quick Introductions

Flask

Flask is a Python microframework with the slogan "web development, one drop at a time". It was created by Austrian developer Armin Ronacher, and first released on April 1st, 2010. As explained in its documentation foreward, the "micro" in "microframework" means that Flask aims to be as simple as possible, yet still extensible. In this series, I will use Flask 0.12.2.

Django

Django prides itself on being the "web framework for perfectionists with deadlines". It is developed and maintained by the Django Software Foundation (DSF), and was first released on July 15th, 2005. Being older, it has had more version releases than Flask. Django is free and open source. In this series, I will use Django 1.10.6.

Popularity

I mentioned earlier that Flask and Django are Python's most popular web frameworks, but what does that really mean, and how do they compare? Let's look at the data.

GitHub Stats

At the time of writing this article, Flask has 31,253 stars and 1,869 watchers onGitHub. It's been forked 9,864 times.

Django on the other hand, has 29,862 stars, 1,848 watchers, and has been forked 12,599 times.

Based on these stats, the two frameworks are almost neck and neck, all things considered. However, Flask is doing better with stars and watchers, which becomes significant when one considers that Django is 5 years older. As for other Python frameworks, they simply do not come close to Flask and Django on GitHub.

Projects

Both frameworks have been used in quite a number of active projects. Some of the well-known projects powered by Django include Pinterest, Disqus, Eventbrite, Instagram and Bitbucket.

Pinterest now uses Flask for its API. Other projects using Flask include Twilio, Netflix, Uber, and LinkedIn.

Django seems to be used as the main framework powering many applications, whereas Flask is often used just for API's (such as with Pinterest and Twilio).

Documentation and Ease of Getting Started

Flask

Flask provides an extensive documentation, covering everything from installation to deployment, and includes quickstart instructions as well as a more detailed tutorial.

Installing Flask with Python's package manager, pip, is easy.

$ pip install flask

$ pip freeze
appdirs==1.4.3
click==6.7
Flask==0.12
itsdangerous==0.24
Jinja2==2.9.5
MarkupSafe==1.0
packaging==16.8
pyparsing==2.2.0
six==1.10.0
Werkzeug==0.12.1

The pip install flask command installs Flask as well as a few other necessary packages, which you can view with the pip freeze command. They include Jinja2, a template engine, and Werkzeug, a utility library for WSGI.

Getting Flask up and running (for example, a page that displays "Hello World") is quite simple. All you need to do is create an instance of the Flask class, and then create a route that displays the "Hello World" string to the homepage:

# app.py

from flask import Flask
app = Flask(**name**)

@app.route('/')
def hello_world():
    return 'Hello World!'

All that's left to do is run the file:

$ export FLASK_APP=app.py
$ flask run
_ Serving Flask app "app"
_ Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

And just like that, you have a running app:

Beginners should however beware, especially when building a more complex app. Because Flask aims to be as minimal as possible, it has no command to autogenerate code. Flask doesn't make any decisions for you, be it with regards to directory structure or to database systems. Even the templating system it ships with is easy to change. While this offers convenience for experienced developers, it can pose a bit of a challenge for beginners because it is not immediately obvious what goes where. For this reason, I would recommend following an introductory best practices guide such as this one, to enable you to structure and develop your app in a convenient way that's been tried and tested.

Django

Django's documentation is more extensive than Flask's, which is to be expected because Django is not as minimalistic as Flask.

Django, too, can be easily installed using pip. It also comes with a few packages out of the box:

$ pip install django

$ pip freeze
appdirs==1.4.3
Django==1.10.6
packaging==16.8
pyparsing==2.2.0
six==1.10.0

Getting started with Django involves running an in-built command to create a project, and then another command to create an app. First, we'll create a project called mysite:

$ django-admin startproject mysite

This creates some files and directories as follows:

└── mysite
    ├── manage.py
    └── mysite
        ├── **init**.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

The files contain some autogenerated code, including settings and a default admin URL route. We can access the default homepage before creating any app. To do so, start the Django server like so:

$ cd mysite
$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
November 27, 2017 - 12:21:45
Django version 1.10.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Then navigate to http://127.0.0.1:8000/. You should see this page:

We can also access the admin login page:

Next we'll create a Django app.

$ cd mysite
$ python manage.py startapp new_app

This creates a new_app folder, which has some autogenerated files as seen below:

└── mysite
    └── new_app
        ├── _**init**_.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── _init_.py
        ├── models.py
        ├── tests.py
        └── views.py

Django's start-up commands are convenient, especially for beginners, because they create a ready-to-use directory structure, and it is clear what goes where based on the files that have automatically been created. Note that each new app created must be registered in the INSTALLED_APPS list in the settings.py file:

# mysite/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # add new app here
    'new_app',
]

Routing System

Routing is essential for any website or web application, as it creates URLs and determines what is displayed when each URL is loaded.

Flask

In Flask, routing is done using a route() decorator. Let's take the Hello World example we saw in the previous section:

# app.py

@app.route('/')
def hello_world():
    return 'Hello World!'

The first line is a decorator, that is, a function that takes a callable (a function or a class) as a parameter, modifies it, and returns it. The snippet above can be broken down as follows:

  1. The app in @app.route() is an instance of the Flask class. This class is imported directly from Flask.
  2. The route() decorator takes a string as a parameter. This string tells Flask what URL should trigger the function. In this case, it is simply /, which refers to the index page, that is, the very first page that loads when you access Flask in your browser (http://127.0.0.1:5000/\). If we had put a different string, such as '/hello-world' then we would need to access it at http://127.0.0.1:5000/hello-world.
  3. hello_world() is the function that is triggered by the decorator. In this case, it returns a "Hello World!" string. It can also return a HTML template - but we'll get to that later!

If you attempt to access a URL that has not been defined, you will get a 404 error:

Django

In Django, routing is handled in the urls.py file, which is one of the files created by running the in-built djangoadmin startproject command. Defining a route entails importing the url method from Django and creating an instance of it, specifying the following parameters:

  1. A regex string
  2. A view, or a link to an existing urls module
  3. Optionally, keyword arguments such as a name

The urls.py file contains a urlpatterns list which consists of url() instances. In the next section, we will create our own Django view, and then create a new URL pattern for it. In the meantime, let's look at the URL for the in-built Django admin site, which comes by default in the urls.py file:

# mysite/urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

The instance of url() above includes the regex r^admin/, which tells Django that loading the http://127.0.0.1:8000/admin URL should load the URLs specified in the admin.site.urls module. Note that we import admin from Django.

Like Flask, attempting to access a non-defined URL in Django will give a 404 error:

Templating System

For any web application, there must be a front-end, or a user interface, allowing users to interact with the system. The front-end consists of static HTML pages, usually styled with CSS. Since web apps are not static websites, there needs to be a way to generate HTML dynamically. This is where templates come in.

Flask

Flask uses Jinja2, a modern and designer-friendly template engine for Python, by default.

So far, our Flask app contains only one file, app.py. Let's organize our directory structure before going ahead. First, rename app.py to run.py. Next, create a config.py file in the same directory as run.py. Then, create an app directory. In it, create the following files: __init__.py, and views.py. Your directory structure should now look like this:

├── app
│   ├── **init**.py
│   └── views.py
├── config.py
└── run.py

The config.py file is similar to Django's settings.py, and contains Flask configurations. Add the following code to it:

# config.py

DEBUG = True

Setting debug to True enable Flask's debugging features. Note that is should always be False in production.

Now, let's work on the app/__init__.py file. This is where we will initialize the app. Note that setting instance_relative_config to True, allows us to use app.config.from_object('config') to load the config.py file.

# app/**init**.py

from flask import Flask

# Initialize the app
app = Flask(**name**, instance_relative_config=True)

# Load the views
from app import views

# Load the config file
app.config.from_object('config')

Now, we will edit our views.py file. Views in Flask can be based on classes or functions. the hello_world function we had earlier is an example of a view. Let's transfer it to the views.py file, and add a new view to demonstrate templates:

# app/views.py

from flask import render_template

from app import app


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/show-template')
def show_template():
    return render_template("template.html")

Lastly, let's modify the run.py file, which will be our app's entry-point:

# run.py

from app import app

if **name** == '**main**':
    app.run()

Run the Flask app like so:

$ export FLASK_APP=run.py
$ flask run
_ Serving Flask app "run"
_ Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Now, navigate to http://127.0.0.1:5000/show-template in your browser. You should get an error page due to the fact that template.html doesn't exist - yet. Create a app/templates directory, and in it, we'll create a very simple HTML template:

<!-- templates/template.html -->

<!DOCTYPE html>
<html>
<body>

<h1>Displaying a Template</h1>
<p>Templates in Flask using Jinja2</p>

</body>
</html>

Run your app and navigate to http://127.0.0.1:5000/show-template again; you should see your template in the browser now:

Now, Jinja2 templates contain variables as well as tags. Below are examples of some commonly used Jinja2 syntax:

  • {% ... %} is used for statements. Jinja2 statements can be used in a variety of cases, including for loops, if-else statements, macros (which are similar to functions in regular programming) as well as imports.
  • {{ ... }} is used for variables. To pass a variable to a template, we use a context dictionary, which is defined in the view. We'll cover this in Part Two of the series, under the Views and Forms section.
  • {# ... #} is used for to comment out Jinja2 syntax.

Let's see this in action. Open your template file and add the following:

<!-- templates/template.html -->

<!-- add this after the first paragraph -->

<h2> Statements </h2>
{% if my_variable %}
  <p>
    This paragraph won't be displayed in the template because the variable
    "my_variable" does not exist. The if statement therefore returns False.
  </p>
{% else %}
  <p>
    This paragraph will be displayed.
  </p>
{% endif %}

<h2> Variables </h2>

<p>{{ my_variable }}</p>
<p>
  The variable above will not be displayed because it doesn't exist!
</p>

<h2> Comments </h2>

{% if True %}
  <p>
    This will be displayed because the if statement returns True.
  </p>
{% endif %}

{#
  {% if True %}
    <p>
      This will not be displayed because it is commented out using Jinja2 syntax.
    </p>
  {% endif %}
#}

This should give the following output:

An important feature of Jinja2 templates is template inheritance. This allows you to define a base template that contains content that would need be duplicated across several web pages, such as a logo, navigation bar, and footer. Subsequent templates can then inherit from the base template, thus eliminating the need to duplicate content across several HTML files. This is done by defining a {% extends "base.html" %} tag at the beginning of each subsequent template file, assuming that base.html is the base template.

For more information on Jinja2 templates, take a look at the official documentation.

Django

Fun fact: the Jinja2 template engine was inspired by the Django template language, therefore their syntax is quite similar! Much like Jinja2, the Django templating syntax includes:

  • variables: {{ ... }}
  • tags: {% ... %}
  • filters on variables: {{ variable|filter }}
  • comments: {# ... #} for single line comments and {% comment %} ... {% endcomment %} for multi-line comments

Let's see this in action. Begin by creating a /templates directory in your mysite/new_app directory, and a templates.html file inside it. Your directory structure should now look like this:

└── mysite
    ├── manage.py
    ├── mysite
    │   ├── **init**.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── new_app
        ├── _**init**_.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── _init_.py
        ├── models.py
        ├── templates
        │   └── template.html
        ├── tests.py
        └── views.py

Add the following to the template.html file:

<!-- mysite/new_app/templates/template.html -->

<!DOCTYPE html>
<html>
<body>

<h1>Displaying a Template</h1>
<p>Using Django Templates</p>

<h2> Statements </h2>
{% if my_variable %}
  <p>
    This paragraph won't be displayed in the template because the variable
    "my_variable" does not exist. The if statement therefore returns False.
  </p>
{% else %}
  <p>
    This paragraph will be displayed.
  </p>
{% endif %}

<h2> Variables </h2>

<p>{{ my_variable }}</p>
<p>
  The variable above will not be displayed because it doesn't exist!
</p>

<h2> Comments </h2>

{% if True %}
  <p>
    This will be displayed because the if statement returns True.
  </p>
{% endif %}

{% comment %}
  {% if True %}
    <p>
      This will not be displayed because it is commented out using Django
      multi-line comments.
    </p>
  {% endif %}
{% endcomment %}

{# <p> This is a single-line Django comment. It won't be displayed. </p> #}

</body>
</html>

Next, we'll implement a simple class-based Django view in our mysite/urls.py file to render the template:

# mysite/mysite/urls.py

from django.views.generic import TemplateView

# add the show-template URL below the existing admin URL

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^show-template/$', TemplateView.as_view(template_name="template.html")),
]

Start the Django server, and then navigate to http://127.0.0.1:8000/show-template/. The resulting output will be similar to what we had in our Flask app:

Django templates also has a template inheritance feature, with the base template being inherited with a {% extends "base.html" %} tag, exactly as is done with Jinja2. For more information about the Django template language, visit the official documentation.

Conclusion

That's all for Part One! I hope this article has been informative, and given you a deeper understanding of the basics of getting started with both frameworks. Stay tuned for Part Two of this series, where we'll delve even deeper into comparing and contrasting Flask and Django. We will look at how they handle storing data, testing, and how to use views and forms to carry out CRUD operations in both frameworks. We'll also cover some common packages, libraries, and extensions available for both, as well as their unique features. See you then!