Django by Example GoingLive
Django by Example GoingLive
In the previous chapter, you created a RESTful API for your project. In this chapter,
you will learn how to do the following:
[1]
Going Live
Create a settings/ directory inside your main educa project directory. Rename
the settings.py file of your project to settings/base.py and create the following
additional files so that the new directory looks like this:
settings/
__init__.py
base.py
local.py
pro.py
base.py: The base settings file that contains common and default settings
local.py: Custom settings for your local environment
pro.py: Custom settings for the production environment
We have moved our settings files one directory level lower, so we need BASE_DIR to
point to the parent directory to be correct. We achieve this by pointing to the parent
directory with os.pardir.
Edit the settings/local.py file and add the following lines of code:
from .base import *
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
[2]
Chapter 13
This is the settings file for our local environment. We import all settings defined
in the base.py file and only define specific settings for this environment. We have
copied the DEBUG and DATABASES settings from the base.py file, since these will be
set per environment. You can remove the DATABASES and DEBUG settings from the
base.py settings file.
DEBUG = False
ADMINS = (
('Antonio M', 'email@mydomain.com'),
)
DATABASES = {
'default': {
}
}
These are the settings for the production environment. Let's take a closer look at each
of them:
[3]
Going Live
We have placed the project settings in a different location from the default settings.
py file. You will not be able to execute any commands with the manage.py tool unless
you specify the settings module to use. You will need to add a --settings flag when
you run management commands from the shell or set a DJANGO_SETTINGS_MODULE
environment variable. Open the shell and run the following command:
export DJANGO_SETTINGS_MODULE=educa.settings.pro
This will set the DJANGO_SETTINGS_MODULE environment variable for the current
shell session. If you want to avoid executing this command for each new shell, add
this command to your shell's configuration in the .bashrc or .bash_profile file. If
you don't set this variable, you will have to run management commands, including
the --settings flag. For example:
python manage.py migrate -settings=educa.settings.pro
Installing PostgreSQL
Throughout this book we have been using the SQLite database. This is simple and
quick to setup, but for a production environment you will need a more powerful
database like PostgreSQL, MySQL, or Oracle. We are going to use PostgreSQL for
our production environment. PostgreSQL is the recommended database for Django
because of the features and performance it offers. Django also comes with the
django.contrib.postgres package that allows you to take advantage of specific
PostgreSQL features. You can find more information about this module at https://
docs.djangoproject.com/en/1.8/ref/contrib/postgres/.
If you are using Linux, install the dependencies for PostgreSQL to work with Python
like this:
sudo apt-get install libpq-dev python-dev
If you are using Mac OS X or Windows, you can download PostgreSQL from
http://www.postgresql.org/download/.
[4]
Chapter 13
Let's create a PostgreSQL user. Open the shell and run the following commands:
su postgres
createuser -dP educa
You will be prompted for a password and permissions you want to give to this user.
Enter the desired password and permissions and then create a new database with
the following command:
createdb -E utf8 -U educa educa
Then edit the settings/pro.py file and modify the DATABASES setting to make it
look as follows:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'educa',
'USER': 'educa',
'PASSWORD': '*****',
}
}
Replace the preceding data with the database name and credentials for the user
you created. The new database is empty. Run the following command to apply
all database migrations:
python manage.py migrate
[5]
Going Live
You will see an output with no errors, but several warnings. This means the check
was successful, but you should go through the warnings to see if there is anything
more you can do to make your project safe for production. We are not going to go
deeper into this, but just keep in mind that you should check your project before
production use, to identify any relevant issues.
When you generate a new project using the startproject command, Django creates
a wsgi.py file inside your project directory. This file contains a WSGI application
callable, which is an access point for your application. WSGI is used to both run your
project with the Django development server, and deploy your application with the
server of your choice in a production environment.
Installing uWSGI
Throughout this book, you have been using the Django development server to run
projects in your local environment. However, you need a real web server to deploy
your application in a production environment.
If you are using Mac OS X, you can install uWSGI with the Homebrew package
manager using the command brew install uwsgi. If you want to install uWSGI
on Windows, you will need Cygwin https://www.cygwin.com/. However, it is
recommended to use uWSGI in UNIX-based environments.
[6]
Chapter 13
Configuring uWSGI
You can run uWSGI from the command line. Open the shell and run the following
command from the educa project directory:
uwsgi --module=educa.wsgi:application \
--env=DJANGO_SETTINGS_MODULE=educa.settings.pro \
--http=127.0.0.1:80 \
--uid=1000 \
--virtualenv=/home/zenx/env/educa/
You might have to prepend sudo to this command if you don't have the required
permissions.
With this command, we run uWSGI on our localhost with the following options:
If you are not running the command within the project directory, include the option
--chdir=/path/to/educa/ with the path to your project.
Open http://127.0.0.1/ in your browser. You should see the generated HTML,
but no CSS or images are loaded. This makes sense, since we didn't configure uWSGI
to serve static files.
uWSGI allows you to define custom configuration in an .ini file. This is more
convenient than passing options through the command line. Create the following
file structure inside the main educa/ directory:
config/
uwsgi.ini
Edit the uwsgi.ini file and add the following code to it:
[uwsgi]
# variables
projectname = educa
base = /home/zenx/educa
# configuration
master = true
virtualenv = /home/zenx/env/%(projectname)
[7]
Going Live
pythonpath = %(base)
chdir = %(base)
env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
module = educa.wsgi:application
socket = /tmp/%(projectname).sock
These are custom variables that we will use in the uWSGI options. You can define
any other variables you like as long as the name is different from uWSGI options.
We set the following options:
The socket option is intended for communication with some third-party router
such as Nginx, while the http option is for uWSGI to accept incoming HTTP
requests and route them by itself. We are going to run uWSGI using a socket, since
we are going to configure Nginx as our web server and communicate with uWSGI
through the socket.
You can find the list of all available uWSGI options at http://uwsgi-docs.
readthedocs.org/en/latest/Options.html.
Now you can run uWSGI with your custom configuration using this command:
uwsgi --ini config/uwsgi.ini
You will not be able to access your uWSGI instance from your browser now, since it's
running through a socket. Let's complete the production environment.
[8]
Chapter 13
Installing Nginx
When you are serving a website, you have to serve dynamic content, but you also
need to serve static files such as CSS, JavaScript files, and images. While uWSGI is
capable of serving static files, it adds an unnecessary overhead to HTTP requests.
Therefore, it's recommended to set up a web server like Nginx in front of uWSGI, to
serve static files.
Nginx is a web server focused on high concurrency, performance, and low memory
usage. Nginx also acts as a reverse proxy, receiving HTTP requests and routing them
to different backends. Generally, you will use a web server such as Nginx in front,
for serving static files efficiently and quickly, and you will forward dynamic requests
to uWSGI workers. By using Nginx, you can also apply rules and benefit from its
reverse proxy capabilities.
If you are using Mac OS X, you can install Nginx by using the command brew
install nginx. You can find the Nginx binaries for Windows at http://nginx.
org/en/download.html.
When the client browser launches an HTTP request, the following happens:
[9]
Going Live
Configuring Nginx
Create a new file inside the config/ directory and name it nginx.conf. Add the
following code to it:
# the upstream component nginx needs to connect to
upstream educa {
server unix:///tmp/educa.sock;
}
server {
listen 80;
server_name www.educaproject.com educaproject.com;
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass educa;
}
}
This is the basic configuration for Nginx. We set up an upstream named educa,
which points to the socket created by uWSGI. We use the server directive and
add the following configuration:
Replace /home/zenx/educa/ with your project's absolute path. Then open a shell
and run uWSGI if you are not running it yet:
uwsgi -ini config/uwsgi.in
[ 10 ]
Chapter 13
Since we are using a sample domain name, we need to redirect it to our local host.
Edit your /etc/hosts file and add the following lines to it:
127.0.0.1 educaproject.com
127.0.0.1 www.educaproject.com
By doing so, we are routing both hostnames to our local server. In a production
server, you won't need to do this since you will point your hostname to your server
in your domain's DNS configuration.
Edit the settings/base.py file and add the following code to it:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
We need to export static assets with Django. The collectstatic command copies
static files from all applications and stores them in the STATIC_ROOT directory.
Open the shell and run the following command:
python manage.py collectstatic
/educa/static
Enter yes so that Django copies all files. You will see the following output:
78 static files copied to /educa/static
[ 11 ]
Going Live
Now edit the config/nginx.conf file and add the following code inside the
server directive:
location /static/ {
alias /home/zenx/educa/static/;
}
location /media/ {
alias /home/zenx/educa/media/;
}
Remember to replace the /home/zenx/educa/ path with the absolute path to your
project directory. These directives tell Nginx to serve the static assets located under
/static/ or /media/ paths directly.
We are generating a private key and a 2048-bit SSL certificate that is valid for one
year. You will be asked to enter the following information:
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []: Madrid
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Zenx IT
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []: educaproject.com
Email Address []: email@domain.com
[ 12 ]
Chapter 13
You can fill in the requested data with your own information. The most important
field is the Common Name. You have to specify the domain name for the certificate.
We will use educaproject.com.
This will generate, inside the ssl/ directory, an educa.key private key file and an
educa.crt file, which is the actual certificate.
Our server now listens both to HTTP through port 80 and HTTPS through port 443.
We indicate the path to the SSL certificate with ssl_certificate and the certificate
key with ssl_certificate_key.
[ 13 ]
Going Live
The message might be different depending on your browser. It alerts you that your
site is not using a trusted certificate; the browser cannot verify the identity of your
site. This is because we signed our own certificate instead of obtaining one from a
trusted Certification Authority. When you own a real domain you can apply for a
trusted CA to issue an SSL certificate for it, so that browsers can verify its identity.
If you want to obtain a trusted certificate for a real domain, you can refer to the Let's
Encrypt project created by the Linux Foundation. It is a collaborative project which
aims to simplify obtaining and renewing trusted SSL certificates for free. You can
find more information at https://letsencrypt.org/.
Click the Add Exception button to let your browser know that you trust this
certificate. You will see that the browser displays a lock icon next to the URL,
as shown in the following screenshot:
If you click the lock icon, the SSL certificate details will be displayed.
Great! You have configured a production environment that will offer great
performance to serve your project.
[ 14 ]
Chapter 13
The following schema shows the order in which the middleware methods are
executed during the request and response phases. It also displays the middleware
methods that are (or might be) called:
[ 15 ]
Going Live
During the request phase, the following methods of the middleware are executed:
During the response phase, the following the middleware methods are executed:
[ 16 ]
Chapter 13
Create a new file inside the courses application directory and name it
middleware.py. Add the following code to it:
class SubdomainCourseMiddleware(object):
"""
Provides subdomains for courses
"""
def process_request(self, request):
host_parts = request.get_host().split('.')
if len(host_parts) > 2 and host_parts[0] != 'www':
# get course for the given subdomain
course = get_object_or_404(Course, slug=host_parts[0])
course_url = reverse('course_detail',
args=[course.slug])
# redirect current request to the course_detail view
url = '{}://{}{}'.format(request.scheme,
'.'.join(host_parts[1:]),
course_url)
return redirect(url)
1. We get the hostname that is being used in the request and divide it into
parts. For example, if the user is accessing mycourse.educaproject.com, we
generate the list ['mycourse', 'educaproject', 'com'].
2. We verify that the hostname includes a subdomain by checking if the split
generated more than two elements. If the hostname includes a subdomain,
and this is not 'www', we try to get the course with the slug provided in the
subdomain.
3. If a course is not found, we raise an Http404 exception. Otherwise, we
redirect the browser to the course detail URL using the main domain.
[ 17 ]
Going Live
127.0.0.1 django.educaproject.com
[ 18 ]
Chapter 13
Create the following file structure inside the students application directory:
management/
__init__.py
commands/
__init__.py
enroll_reminder.py
Edit the enroll_reminder.py file and add the following code to it:
import datetime
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.mail import send_mass_mail
from django.contrib.auth.models import User
from django.db.models import Count
class Command(BaseCommand):
help = 'Sends an e-mail reminder to users registered more \
than N days that are not enrolled into any courses yet'
[ 19 ]
Going Live
users = User.objects.annotate(course_count=Count('courses_
enrolled')).filter(course_count=0, date_joined__lte=date_joined)
for user in users:
message = 'Dear {},\n\nWe noticed that you didn\'t enroll
in any courses yet. What are you waiting for?'.format(user.first_name)
emails.append((subject,
message,
settings.DEFAULT_FROM_EMAIL,
[user.email]))
send_mass_mail(emails)
self.stdout.write('Sent {} reminders' % len(emails))
You have created your first management command. Open the shell and run your
command:
python manage.py enroll_reminder --days=20
If you don't have a local SMTP server running, you can take a look at Chapter 2,
Enhancing Your Blog with Advanced Features, where we configured the SMTP settings
for our first Django project. Alternatively, you can add the the following line to the
settings.py file to make Django output e-mails to the standard output during
development:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
[ 20 ]
Chapter 13
Let's schedule our management command, so that the server runs it every day at 8
a.m. If you are using an UNIX based system such as Linux or Mac OS X, open the
shell and run crontab e to edit your cron. Add the following line to it:
0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20
--settings=educa.settings.pro
If you are not familiar with Cron, you can find information about it at
https://en.wikipedia.org/wiki/Cron.
If you are using Windows, you can schedule tasks using the Task scheduler. You
can find more information about it at http://windows.microsoft.com/en-au/
windows/schedule-task#1TC=windows-7.
Another option for executing actions periodically is to create tasks and schedule
them with Celery. Remember that we used Celery in Chapter 7, Building an Online
Shop, to execute asynchronous tasks. Instead of creating management commands
and scheduling them with Cron, you can create asynchronous tasks and execute
them with the Celery beat scheduler. You can learn more about scheduling periodic
tasks with Celery at http://celery.readthedocs.org/en/latest/userguide/
periodic-tasks.html.
Django also includes a utility to call management commands using Python. You can
run management commands from your code as follows:
from django.core import management
management.call_command('enroll_reminder', days=20)
Congratulations! You can now create custom management commands for your
applications and schedule them if needed.
Summary
In this chapter, you configured a production environment using uWSGI and Nginx.
You also implemented a custom middleware and you have learned how to create
custom management commands.
[ 21 ]