Category: python

Adding Time taken to respond to a request in the header of a Django Rest Framework Response. A suitable place to use a Mixin or not?

Ever wanted to add the time taken for a response to your API, so the client knows how long that takes?
I first noticed this cool feature on AWX, a management platform for ansible playbooks.

There’s response headers looked like this:


So I checked out their source code and copied how they did it:

In the package extends the base DRF ApiView class and adds a bit of stuff, the important stuff for us is:

    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        response['X-API-Node'] = settings.CLUSTER_HOST_ID
    def initialize_request(self, request, *args, **kwargs):
        self.time_started = time.time()

So I created a class extending for ApiView:

class APIView(views.APIView):
    Add timing to the base APIView class
    def initialize_request(self, request, *args, **kwargs):
        self.time_started = time.time()
        return super().initialize_request(request, *args, **kwargs)
    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        if time_started:
            time_elapsed = time.time() - self.time_started
            response['X-API-Time'] = '%0.3fs' % time_elapsed
        return response

Now all I needed to do was entend from my_package.ApiView instead of restframework.views.ApiView and I would get an X-API-Time header.

It worked well for a while, but then I needed to use more Generic Class Based Views (Which extend from ApiView)…check out CDRF. So I could just add these methods, to the extended versions of restframework.generics.ListCreateView etc.

But that just doesn’t feel right. Simply because I can see I am repeating myself multiple times. I just be able to define this functionality once and have all similar objects (those that extend from ApiView) have that functionality…

Using a Mixin to add the Time taken to Respond

I just created a mixin, by extracting those functions into a single class that inherits from nothing:

class TimedAPIMixin:
    def initialize_request(self, request, *args, **kwargs):
        self.time_started = time.time()
        return super().initialize_request(request, *args, **kwargs)

    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        if time_started:
            time_elapsed = time.time() - self.time_started
            response['X-API-Time'] = '%0.3fs' % time_elapsed
        return response

Using them in the other views:



from rest_framework import views
from rest_framework import generics

class APIView(TimedAPIMixin, views.APIView):

class ListCreateAPIView(TimedAPIMixin, generics.ListCreateAPIView):

class ListAPIView(TimedAPIMixin, generics.ListAPIView):

It only works if the mixin is the first thing it inherits from, otherwise the first class will take preference.

Trying the Pelican Static Site Generator and Abandoning it

I have a Jekyll site called fixes

I wanted to move away from Ruby and try the language I prefer and saw that pelican was the most popular. I also wanted to move away to improve the way categories are shown and to speed up the site.

Don’t Use Pelican

After trying it out I have come to the conclusion that it is best to avoid it.

The problems I had:

  • When setting up it creates a fab file for you, for the convenience of deploying and running the site locally. Unfortunately it is based off of the old fabric that required python 2. Using fabric > 2 gave errors.
  • It is September 2018, the last release of Pelican was in January 2017. The author is relying on the community to review pull requests and it seems the community is not alive and kicking anymore.
  • The default theme is not what I deem elegant or simple.
  • AWS is used by default to show assets (like the github ribbon), which is slower and less reliable than a locally hosted version in my opinion. I don’t want to be reliant on some external service.
  • It also uses Google fonts for the default font. Yet more bloat and reliance on Google.
  • Themes are in a seperate repo and are complicated to use.
  • I found that 50% of the themes give errors when you are building them
  • All themes require significant customization.
  • Extra stuff is in yet another repo called pelican plugins, which also requires more technical skill and doc reading to make work.
  • None of the themes I tried were light weight and looked descent and required not much effort.

All in all, things have just gotten a bit too complex and complicated. Too much unnecessary shit is included in the standard package and too much necessary stuff requires external repos and customisation.

Things don’t just work, so I am going to look for another solution or stick with Jekyll.

Deploying a django website to a Ubuntu 16.04 server with python3.6, gunicorn, nginx and Mysql

Getting up and running on your local development setup and being able to build and see the changes you are making is one of the numerous reasons we like django.

The built in development webserver helps a lot in this regard.

As soon as we have to deploy the site on a server and make it public so that other people can see the project and progress the job is more do it yourself and can be hard at times. In this post, I will show you how I did it with Ubuntu 16.04, nginx, gunicorn and with a Mysql Database.

I tried to use an ansible script to build an idempotent setup but it became tedious and decided that the initial server configuration will be a snowflake but change deploys will be done with ansible.


ubutnu16.04-django-nginx-mysql-gunicorn ubutnu16.04-django-nginx-mysql-gunicorn ubutnu16.04-django-nginx-mysql-gunicorn


There are a few topics and a flow of how to get the site deployed:

  1. Provisioning a server
  2. Configuring the server with requirements
  3. Settings
  4. Requirements
  5. Basic Django Setup
  6. Getting gunicorn to work
  7. Configure Nginx to Proxy Pass

Provisioning a Server

I want an ubuntu 16.04 server mainly because it has long term support and I am used to ubuntu. With your hosting client create the server and then ssh in with:

ssh user@123.345.566.789

The best thing to do now is take care of basic security and initial setup of the ubuntu 16.04 server

The most important thing is creating an ssh key and logging in with ssh and disabling password login.

Configuring the server with requirements

One key thing is that when installing python 3.6 on ubuntu 16.04 is that it is not part of that version and was released later.

Ensure to install the following before compiling (otherwise it wont work or pip will give you an ssl issue:

sudo apt install zlib1g-dev build-essential libssl-dev libffi-dev

You should build python3.6 from source and not use a ppa. This tutorial on installing python3.6 from source on ubuntu 16.04 is the one I used. Update: If you are intalling python 3.7 you will need to install libffi-dev.

To set python to point to python3.7:

sudo update-alternatives --install /usr/bin/python python /usr/local/bin/python3.7 1

Everything else can be installed with apt:

  • python-mysqldb
  • mysql-server
  • nginx
  • git
  • libmysqlclient-dev

Remember when installing mysql-server that you will set a root password. You can run mysql_secure_installation to ensure the server is secure.

The nginx version that installs will be 1.10.3 which is a tad old and if you want a recent version you can check linux packages installing nginx latest.


In your django project it is important to ensure that you split into a directory with files

  • settings/
  • settings/
  • settings/ contains global generic settings then in and you can import those settings with:

from . base import *
and then override and add settings you need.
Remember that when running a command you should specify the settings with:
./ collectstatic --setting=config.settings.staging
Although setting an environment variable is better with:
export DJANGO_SETTINGS_MODULE=config.settings.staging
Another thing is typing settings in on your local machine will become tedious so better to default to your local settings in with:

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")

Remember to ensure mail servers and external integrations are not production or live settings when they should not be


A good idea is to split up your requirements the same way you did for settings.

  • requirements/base.txt
  • requirements/local.txt
  • requirements/production.txt

You can inherit from base with -r base.txt as the first line.

Basic Django Setup

Create the database and user

Now you can create a mysql schema and a mysql user for that database. See this tutorial on how to create a new user and grant permissions.

Create the Environment

python3.6 -m venv env

source env/bin/activate

Now ensure that the settings environment variable is set in the environment by adding the following to the end of env/bin/activate:

export DJANGO_SETTINGS_MODULE=config.settings.staging

deactivate and reactivate with source env/bin/activate

Importantly you can put the django setting above in post_activate only if you use virtualenvwrapper it does not work with the native python virtualenv module.

Setup the database and static files

./ migrate

./ collectstatic

./ createsuperuser

Test the site works

With the ALLOWED_HOSTS = [‘xxx’, ]  and DATABASES updated in settings you can test the site with the development server with: runserver

You will need to enable the 8000 port first with sudo ufw allow 8000

You can test if the site works at: http://server_domain_or_IP:8000

Getting Gunicorn to Work

Great news. If everything has worked up till now we can now get gunicorn to work.

pip install gunicorn

Make sure to add the to your requirements/production.txt

Run gunicorn:

gunicorn --bind settings_module.wsgi

Now you can test again.

If everything is good we want this service to be managed now by the os, so that it starts automatically on a system start and can be monitored with logs. For that unforunately we need to create a systemd service.

vim /etc/systemd/system/gunicorn.service

Add the following:

Description=gunicorn daemon

ExecStart=/var/www/<project_name>/env/bin/gunicorn --access-logfile - --workers 3 --bind unix:/var/www/<project_name>/<project_name>.sock config.wsgi:application


Important to note that an environment file is given .gunicorn_env

The contents of this file will contain all the environment variables needed so in our case just:


You now need to create and enable the service:

sudo systemctl start gunicorn

sudo systemctl enable gunicorn

Configure Nginx to Proxy Pass

The final step is serving the site through nginx

sudo vim /etc/nginx/sites-available/<project_name>

Add the following:

server {
    listen 80;
    server_name <server_domain_or_ip>;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /var/www/<folder_name>;

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/www/<folder_name>/.sock;


You then need to create a symlink to the enabled sites folder and remove the default site symlink there.

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

Check you nginx config with:

sudo nginx -t

then restart nginx

sudo service nginx restart

Open port 80 and delete the old 8000 port rule:

sudo ufw delete allow 8000

sudo ufw allow 'Nginx Full'



So it is not that difficult but it is difficult if you are trying to create a repeatable thing, which I hope one of you reading this will do. Another thing you can do is add whitenoise for simplified static file serving which I have not tried yet.

If you have any issues you can comment below or troubleshoot on the source of this article

Update: Using Pipenv

So pipenv is now the recommended way to manage both pip and your virtual environment so here are a few modifications to the commands:

Install pipenv:

sudo pip3 install pipenv

#Install dependencies
pipenv install

pipenv run ./ migrate

# Remember to put environment variables in .bashrc

# Test running
pipenv run ./ runserver

pipenv install gunicorn

# Test gunicorn
pipenv run gunicorn --bind settings_module.wsgi

# Before changing the gunicorn config we need to find where gunicorn is
pipenv --venv

# Replace the gunicorn binary location with that in previous command

Important Note for CentOS 7

Disable SeLinux if you get this nginx error:

2019/06/20 15:29:42 [crit] 29877#0: *12 connect() to unix:/var/www/window/window.sock failed (13: Permission denied) while connecting to upstream, client:, server: _, request: "GET / HTTP/1.1", upstream: "http://unix:/var/www/window/window.sock:/", host: ""

so turn off selinux with:

sudo setenforce 0