postgresql when it's not your job

08:30

WordPress to Django+PostgreSQL, Part 4: Configuring Apache, Django, PostgreSQL

19 October 2009

In the previous installment of this series (in which we’re migrating this blog from WordPress to Django and PostgreSQL), we installed Apache, Python 2.6, psycopg and mod_wsgi on the server. A note about the pace: I’m taking this very slowly, spelling out each and every setp. I realize that those of you with more Centos experience (which is probably pretty much everyone) or Django experience are probably tapping your feet and thinking, “Come on, get a move on here!” To those, I beg your indulgence. When reading about a topic I am unfamiliar with, I appreciate a high level of detail, so I don’t hit a weird error message only to be told, “Oh, right, you have to install an old version of x. I skipped that step.”

Installing Django

We have choices here: We can install Django directly from the current Subversion repository, or from an official release. We also have the choice of installing Django as local to a particular user, or global for the whole system. For this install, we’ll do an official release (1.1.1 is current), and install it for the whole system.

[xof@blog ~]$ cd builds
[xof@blog builds]$ wget http://www.djangoproject.com/download/1.1.1/tarball/
[xof@blog builds]$ tar xvfz Django-1.1.1.tar.gz
[xof@blog builds]$ cd Django-1.1.1
[xof@blog Django-1.1.1]$ sudo python2.6 setup.py install
[xof@blog Django-1.1.1]$ python2.6
Python 2.6.3 (r263:75183, Oct 17 2009, 18:29:55) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> 

Where is the project going to live?

Before we configure Apache and mod_wsgi to serve up our new Django project, we should decide where the project is going to live. I like to keep my builds and maintenance account separate from the user that contains the actual stuff being served up by the web server… so let’s create a new user just to hold the project:

[xof@blog ~]$ sudo /usr/sbin/adduser thebuild
[xof@blog ~]$ sudo passwd thebuild
[xof@blog ~]$ sudo /usr/sbin/usermod -a -G apache thebuild

Note: Depending on the permissions your installation sets on /home directories by default, you may need to adjust them so that mod_wsgi can get at the files it needs.

Set up the Django project

Logging in as the project user, first, we’ll make sure that we get the right Python installation by adding an alias to our .bash_profile:

alias python='/usr/bin/python2.6'

Just checking!

[thebuild@blog ~]$ which python
alias python='/usr/bin/python2.6'
    /usr/bin/python2.6

And we can now create the project:

[thebuild@blog ~]$ cd thebuild.com
[thebuild@blog thebuild.com]$ python /opt/python2.6/lib/python2.6/site-packages/django/bin/django-admin.py startproject thebuild

We also need to create a directory for any Python eggs that our application might download (this is, by the way, a “feature” of Python that I really do not care for).

[thebuild@blog thebuild.com]$ mkdir .python-eggs
[thebuild@blog thebuild.com]$ chmod 770 .python-eggs

Now, we need to create a .wsgi file. The .wsgi file is the glue that connects the mod_wsgi module in Apache to our particular application. The Django project itself doesn’t (and shouldn’t) live under the webroot of the web server, but the .wsgi file does:

[thebuild@blog thebuild.com]$ cd ~/thebuild.com
[thebuild@blog ~]$ mkdir apache
[thebuild@blog ~]$ cd apache
[thebuild@blog apache]$ cat >>django.wsgi
#!/opt/python2.6/bin/python

import os, sys

sys.path.append('/home/thebuild/thebuild.com')

os.environ['DJANGO_SETTINGS_MODULE'] = 'thebuild.settings'
os.environ['PYTHON_EGG_CACHE'] = '/home/thebuild/thebuild.com/.python-eggs'

import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()
[thebuild@blog apache]$ 

Next, let’s create the media and admin-media directories, and move the admin media over from the source distribution:

[thebuild@blog apache]$ mkdir admin-media
[thebuild@blog apache]$ mkdir media
[thebuild@blog apache]$ cd admin-media
[thebuild@blog admin-media]$ cp -R /opt/python2.6/lib/python2.6/site-packages/django/contrib/admin/media/* .

Creating a User in PostgreSQL for the Blog

Right now, the only user in the PostgreSQL cluster is the default superuser; that’s not a great choice for the user that Django will use to connect to the database.

It’s also not idea to allow the Django user to create objects in the public schema, but it needs to be able to create objects in some schema if we’re going to use the cool Django model mechanism for table and field creation.

First, let’s create a database for the site:

[postgres@blog ~]$ createdb thebuild
[postgres@blog ~]$ psql thebuild
psql (8.4.1)
Type "help" for help.

thebuild=# CREATE USER django ENCRYPTED PASSWORD 'password';
CREATE ROLE

Then, let’s create a schema just to hold objects related to the site:

thebuild=# CREATE SCHEMA thebuild AUTHORIZATION django;
CREATE SCHEMA

And set an appropriate search path:

thebuild=# ALTER ROLE django SET search_path TO thebuild, public;
ALTER ROLE

… and revoke CREATE on the public schema from the user:

thebuild=# REVOKE CREATE ON SCHEMA public FROM  django;
REVOKE

This means that, by default, anything that the django user creates will go into the schema thebuild, while it still has access to the schema public. However, it won’t be able to create new objects in public.

(Note that we still have rather a barn door open here: We haven’t done anything with pg_hba.conf to require passwords or other security to log in.)

Django Settings

We’ll need to update the settings.py file to match all of the various changes we made. So far, we’ve changed:

DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'thebuild'
DATABASE_USER = 'django'
DATABASE_PASSWORD = 'password'

TIME_ZONE = 'America/Los_Angeles'

MEDIA_ROOT = '/home/thebuild/thebuild.com/apache/media/'
MEDIA_URL = 'http://new.thebuild.com/media/'

ADMIN_MEDIA_PREFIX = '/admin-media/'

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
)

Note that we’ve turned on the admin site by adding django.contrib.admin to installed apps. Now, we can do a syncdb to create the databases the installed apps require:

thebuild@blog thebuild]$ python manage.py syncdb
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'thebuild'): admin
E-mail address: xof@thebuild.com
Password: 
Password (again): 
Superuser created successfully.
Installing index for admin.LogEntry model
Installing index for auth.Permission model
Installing index for auth.Message model
[thebuild@blog thebuild]$ 

Configure Apache

Apache can be tweaked endlessly, of course, but we’re going to do the minimum required to get the server up and ticking. Assuming we’ve done the basics (ServerName, etc.), we add the module info for mod_wsgi:

LoadModule wsgi_module modules/mod_wsgi.so
AddHandler wsgi-script .wsgi

Now, we need to point to the .wsgi file we created above. Moving over to /etc/httpd/conf.d, we can create a file thebuild.conf to contain the virtual host:

<IfModule mod_alias.c>
    Alias /media /home/thebuild/thebuild.com/apache/media
    Alias /admin-media /home/thebuild/thebuild.com/apache/admin-media
</IfModule>

<IfModule mod_wsgi.c>
    WSGIScriptAlias / /home/thebuild/thebuild.com/apache/django.wsgi
    WSGIDaemonProcess thebuild processes=5 threads=6 display-name=%{GROUP}
    WSGIProcessGroup thebuild
    WSGIApplicationGroup %{GLOBAL}

    WSGISocketPrefix run/wsgi
</IfModule>

… restart Apache …

[xof@blog conf]$ sudo /etc/init.d/httpd graceful

… and did it work?

it-lives

Exciting! Now, time to start actually designing our blog application.

Comments are closed.