Let's Take Notes With Django - Part 7

Let's Take Notes With Django - Part 7

part of the Startproject To Deployment - Django Tutorial series

show all in series

back to index

What a time to be alive! DjangoNote is now a 100% functional project with a single awesome application. We're going to create a github repo for our code and then deploy it onto a DigitalOcean server running Ubuntu 14.04.

There are plenty of tutorials out there detailing how to use git and github, both from the GUI and the command line. The whole thing is hugely powerful and feature-rich. We're going to ignore a huge amount of functionality here with the aim of achieving a single, quick goal - but don't let this be your only exposure to all of the things git and github can do!

Head to github, create an account and download the desktop GUI. Create a new repo at our djangonote_project directory and add three lines to our .gitignore file:


This keeps our repo from getting cluttered with .pyc files, keeps our test database private, and keeps our secret key hidden by leaving out our settings.py file. The way we handle this last part is arguably not the proper way to do things - removing one file from version control obviously means that changes to that file aren't tracked, and that can potentially lead to issues. You can create a separate file to store environment variables like the secret key, import those variables into your settings.py and thereby "clean" your settings file. This would free you up to put your settings file into version control. Up to you! Whatever you choose, go ahead and commit / sync your repo.

Congrats, you're using github!

Now let's get this stuff on a webserver. We're going to deploy on a DigitalOcean droplet (cloud server) using their Django one-click install and Ubuntu 14.04. DigitalOcean isn't free, but as of this writing their cheapest server is only $5/month and you can easily find $10 coupon codes online. Also, if you're enjoying the tutorial, please feel free to sign up using my DigitalOcean referral link. I get few dollars of hosting credit if you do this, which would be pretty cool.

Once you've signed up for a DigitalOcean account, we'll follow these steps to get everything deployed:

  1. Create a droplet.

  2. SSH into our droplet and change the root password.

  3. Set up a new user and add them to the sudo list.

  4. Change the SSH port.

  5. Change our nginx and gunicorn configuration files.

  6. Create our project & static directory.

  7. Install requirements and make sure we're running django 1.7.

  8. Alter our settings.py file to contain the proper ALLOWED_HOSTS and database settings.

  9. Deploy our files to the server.

  10. Run collectstatic to get everything in place.

  11. Sync the database and create a superuser.

  12. Set up DNS settings.

1. Create a Droplet

Log in to your DigitalOcean control panel and select "Create Droplet." We'll enter a hostname of "djangonote" and select the $5/month server. Select a region that makes geographic sense for you, then scroll down to "select image." Hit the "Applications" tab and select "Django on 14.04," then scroll to the bottom of the page and create the droplet. In no time at all, you'll receive an email with your droplet name, IP address and root password.

2. SSH into our droplet and change the root password.

Let's open up a terminal and SSH into our droplet. I'm using the IP address I got on my test droplet here - yours will be different:

$ ssh root@

Answer "yes" when asked if you're sure you want to connect, then enter the password emailed to you when prompted. Once you're logged in, you'll immediately be prompted to input that password again, then to choose a new password. Follow the prompts to set your password to something that hasn't been sent to you in cleartext like the first one.

3. Set up a new user and add them to the sudo list.

We're going to set up a new user account on this server and add them to our "sudo" list so that it can do administrative tasks (by prepending commands with "sudo") while being logged. This is a user account on the server; not in our Django project.

While still connected via SSH, add a new user named "demo" with adduser demo (you can skip through the prompts if you want). Then, still logged in as the root user, add our demo user to the sudo group with gpasswd -a demo sudo.

4. Change the SSH port.

You'll be surprised how often your site is the target of automated vulnerability scans and attacks. Even after just a little while online with the default configuration, your ssh access logs will soon fill up with robotic failed login attempts - and that gets obnoxious. We can easily change the default SSH port and eliminate 99% of these automated attacks:

nano /etc/ssh/sshd_config

This opens up our SSH config file in a basic text editor. Find the line that says Port 22 and change it to a different port of your choosing between 1025 and 65536. Port 2121 sounds nice.

Remember, from now on you'll have to include your port number when you connect via ssh, like so: ssh -p 2121 root@

5. Change our nginx and gunicorn configuration files.

Our nginx configuration file is located at /etc/nginx/sites-enabled/django, and starts off looking like this:

upstream app_server {
server fail_timeout=0;

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

root /usr/share/nginx/html;
index index.html index.htm;

client_max_body_size 4G;
server_name _;

keepalive_timeout 5;

# Your Django project's media files - amend as required
location /media {
alias /home/django/django_project/django_project/media;

# your Django project's static files - amend as required
location /static {
alias /home/django/django_project/django_project/static;

location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;

The only thing we need to fix here is the location /static - it needs to match the STATIC_ROOT in our settings file:

location /static {
alias /var/www/djangonote/static;

If we were working with file uploads, we would need to set up our media directory as well - but we can skip that with our current project. For reference, it's handled exactly like we handle our static assets: define MEDIA_ROOT and MEDIA_DIRS in your settings, create the media directory and change the path in this configuration file to match its location. The directories you want to use to hold these uploads should be nested within this media folder.

Save the changes then open our gunicorn config file at /etc/init/gunicorn.conf. We start with this:

description "Gunicorn daemon for Django project"

start on (local-filesystems and net-device-up IFACE=eth0)
stop on runlevel [!12345]

# If the process quits unexpectadly trigger a respawn

setuid django
setgid django
chdir /home/django

exec gunicorn \
--name=django_project \
--pythonpath=django_project \
--bind= \
--config /etc/gunicorn.d/gunicorn.py \

Pretty close already - we just need to tweak things a little to match our naming conventions. django_project just needs to be djangonote_project for us.

exec gunicorn \
--name=djangonote_project \
--pythonpath=djangonote_project/djangonote \
--bind= \
--config /etc/gunicorn.d/gunicorn.py \

6. Create our project & static directory.

First we'll create a directory to hold our staticfiles with mkdir -p /var/www/djangonote/static.

Then, let's make some alterations to our default file setup. Right now we've got a directory at /home/django/django_project that holds the results of django-admin.py startproject - another django_project directory and a manage.py. Let's remove the top django_project directory and replace it with an empty djangonote_directory. Now the only directory inside /home/django is djangonote_project, and that directory itself is empty.

7. Install requirements and make sure we're running Django 1.7.

Fire up a shell with python - let's check to see what version of Django we're running.

>>>import django
(1, 6, 1, 'final', 0)

Alright, that needs updating. We'll also need to install django_extensions.

$ pip install django --upgrade
$ pip install django_extensions

If you check the Django version again, you should be upgraded to the latest.

8. Alter our settings.py file to contain the proper ALLOWED_HOSTS and database settings.

Right now, our ALLOWED_HOSTS looks like this:


This is fine for local development, but deploying like this means our site can be used as an open proxy for every godawful thing the internet dreams up. No thanks. We'll edit this to only allow our own server IP. If you have a domain name you want to use, you can also add that here as well - both "mydomain.com" and "www.mydomain.com."


Again, this IP is just the random one I drew when playing with a test droplet at DigitalOcean.com. Yours will be different.

When we connected to our droplet via SSH, you might have noticed the following text appear (in the "message of the day"):

You can use the following Postgres database credentials:
DB: django
User: django
Pass: [redacted]

Your password won't be redacted, so this is all the info you need. Open up your settings.py file and find our current database definition:

'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

Comment this out with triple-quotes, and add the following configuration information below it. Commenting it out instead of deleting it makes it easy to flip between development and production settings.

'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'django',
'USER': 'django',
'PASSWORD': 'redacted',
'HOST': '',
'PORT': '5432',

This time, our IP addresses should match. Use your own password and boom - your postgresql database is configured. While we're in here, change DEBUG and TEMPLATE_DEBUG to False.

9. Deploy our files to the server.

There are plenty of options available when it comes to actually moving files from your computer over to your server. We're going to use a less-popular option, SFTP, for our initial deployment because it'll force us to walk the filetree and make sure everything is in order. Also because I'm a little lazy and it's easy.

First, connect to your server via sftp:

sftp -oPort=2121 root@

Enter your root password when prompted. We find ourselves at a prompt. Now we need to navigate two different file systems. Navigate the remote server normally with cd, and the local development machine with lcd. Let's see where we started off:

> pwd
Remote working directory: /root
> cd /home/django

We're in the proper spot on the server - this is where we want to drop our local djangonote_project content. Let's figure out where we are on the local machine:

Local working directory: /whatever

lcd until you're in the directory containing our local djangonote_project directory, then:

>put -r djangonote_project

Some people will argue this point, because (as you'll very quickly see) this pushes all of your .git files to the server as well. If you don't want them there, go one directory deeper and put all of the files manually. Either way, you'll soon be watching text scroll by as your site's files are uploaded. Let's move them around some more!

10. Run collectstatic to get everything in place.

SSH into your server once again - use the new account we created earlier this time, and don't forget the new port.

ssh -p 2121 demo@

Navigate to /home/django/djangonote_project and ls - everything looks to be in order! Move a level deeper, into the djangonote directory. Everything looks to be in order here, too - except we don't need that sqlite3 file in here. We can nuke it with sudo rm db.sqlite3.

Now let's collect our static files with sudo python manage.py collectstatic. Answer yes to the prompts and watch the files fly!

Bonus: Go take a look in our /var/www/djangonote/static directory. When you see how the files are laid out, the fact that we "nested" our static files within an extra folder earlier actually makes sense!

11. Sync the database and create a superuser.

We're in the home stretch - in fact, we've done this step twice already in development. Still connected via SSH (with your non-root account), make sure you're in the directory containing manage.py then hit it with the ol' combo: sudo python manage.py makemigrations, sudo python manage.py migrate and sudo python manage.py createsuperuser.

At this point, we can begin examining the fruits of our labor. Point your browser at the IP address of your server, and you'll hopefully be greeted with the homepage of our DjangoNote project. If you see the dreaded 502 Bad Gateway, go back and check your syntax everywhere. This stuff is finicky. We can stop here if we want, but if you've got a domain name there's one more step you'll want to take - setting up the DNS record.

12. Set up DNS settings.

We're going to let DigitalOcean handle this one - they've got a fantastic tutorial on setting up DNS records with their control panel here. I couldn't say it better!

Hey, we're done! We've built and deployed a fully-functional web app with Django. That wasn't so tough after all!

Our application could use some polish. Why don't you finish it up while I have a break?

  1. We coded in the ability to edit our tags, but there's no link to our edit page anywhere! Remember, it's the same URL as the one that calls the add_tagview, but with ?id=X appended to the end. Use Django's templating powers to add edit links beside each tag on our main index page.

  2. Speaking of our edit pages, we've got a problem both when editing notes and when editing tags. The delete button helpfully disappears when we're in "create mode" - but our blue button always says "Create!" It should say "Edit" when we're in "edit mode." Django's template tags and variables work inside HTML tags, too - see if you can get the display value of our button to dynamically change.

  3. That multiple select widget on the add/edit note page sure is ugly. It'd work better if we used a Javascript library called chosen.js. See if you can find this library online, add its static files to our application, and use it to change the look and feel of our select widget.

  4. We can search for notes associated with a certain tag using our special REST-ful URL structure. But what if we want to search for more than one tag at a time - all notes tagged as both a grocery list AND a to-do list, for example? Can our current tagsearch view handle this? What if you included a special character in the url between your two queries, then split the querystring at that character in the view?

  5. Take a look at our addnote.html template next to addtag.html. If we got clever with template tags, we could probably combine these two templates into one. Can you make it happen?

  6. Understanding how to use version control is the only thing standing between you and a world of hazy, horrible substance abuse. Sharpen your git skills! Fork, pull, merge, squeeze, twist, hug, and explore the github repo for the DjangoNote tutorial project. Break it, then learn by fixing it!

Hope this helped - we'll see you next time!

<< back to blog index