Dotcloud Django 2 这个文档帮助比较大

Django

This document shows how to run a Django project on DotCloud.

We will start with a basic Django project, and add components (database, external applications…) piece by piece.

We will also see which (minor) modifications are needed to run an existing Django project on DotCloud.

Warning

For the impatients our there…

TL,DR? Wait, there are two things that you must know before trying to deploy your Django app on DotCloud:

  1. The wsgi.py file should be at the root of your application.
  2. Your Django project shouldn’t be at the root of your application, but in a subdirectory with a Python module name. So your manage.py file shouldn’t be in the same directory as wsgi.py, but in a subdirectory, with a name such as you can import it.

Naked Django

Let’s start with a minimal Django project.

First, on your local computer, create a directory to hold our DotCloud service and the Django project inside. Some of us love pasta, others love japanese food, so we will try to make both of them happy, and create a ramen project:

$ mkdir ramen-on-dotcloud
$ cd ramen-on-dotcloud
ramen-on-dotcloud$ django-admin startproject ramen

In the ramen-on-dotcloud directory, create a wsgi.py with the following content:

import os
import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'ramen.settings'
import django.core.handlers.wsgi
djangoapplication = django.core.handlers.wsgi.WSGIHandler()
def application(environ, start_response):
    if 'SCRIPT_NAME' in environ:
        del environ['SCRIPT_NAME']
    return djangoapplication(environ, start_response)

Note

DotCloud provides a service nicknamed “python”, which actually provides Python-WSGI functionality. To run any WSGI-based application, you just need to push a wsgi.py file containing a WSGI handler named application, and it will automatically be served by a nginx+uwsgi stack.

Note

DotCloud WSGI stack sets the SCRIPT_NAME variable in the WSGI environment. This variable is required by some frameworks, but it confuses Django. Therefore, our wsgi.py wrapper unsets the variable to avoid unwanted side effects.

Specify the Python packages required by our project. This is done with a pip-like file, conveniently named requirements.txt, located in our project:

ramen-on-dotcloud$ echo django >requirements.txt

The complete specs of the requirements file are detailed at http://www.pip-installer.org/en/latest/requirement-format.html

As seen in firststeps, before pushing our code, we first need to deploy a Python service:

ramen-on-dotcloud$ dotcloud deploy -t python ramen.www

Note

You need to do the “deploy” operation only once, to instanciate the python service.

We can now push the code:

ramen-on-dotcloud$ dotcloud push ramen.www .
# rsync . code
sending incremental file list
./
[...rsync transfers our stuff...]
[...at some point, pip will kick in to install our dependencies...]
# [  -e "code/./requirements.txt" ] && pip install --download-cache=~/.pip-cache -r code/./requirements.txt
Downloading/unpacking django (from -r code/./requirements.txt (line 1))
[...and at the end, supervisord gets restarted...]
Starting supervisor: supervisord.

Note

Don’t forget the dot at the end of the command-line. It is not a typo: it indicates the directory containing your code.

Point your browser to http://www.ramen.dotcloud.com/: you should see the “Welcome to Django” page.

Note

Don’t forget to replace “www.ramen” with the full name of your service, reversed. If you deployed foo.front, go to http://front.foo.dotcloud.com/.

Use a sqlite database

Let’s add a sqlite database and do a “syncdb”.

Edit the file ramen/settings.py and change the database engine to sqlite3, and specify a database name (required for sqlite operation). The beginning of the settings.py file will look like the following:

DATABASES = {
  'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': '/home/dotcloud/ramen.db',
      'USER': '',
      'PASSWORD': '',
      'HOST': '',
      'PORT': '',
  }
}

Warning

You must specify the absolute path to the sqlite file. Why? Because “dotcloud run ramen.www foo” will run the foo script in /home/dotcloud, whereas the WSGI application runs into /home/dotcloud/current. This discrepancy might soon be fixed, but meanwhile, specify absolute paths!

Since we have modified our code, we must push it again:

ramen-on-dotcloud$ dotcloud push ramen.www .

Now run the “syncdb” management command to initialize the sqlite database and create Django tables. This is achieved through the “dotcloud run” command:

ramen-on-dotcloud$ dotcloud run ramen.www python current/ramen/manage.py syncdb

Like a “regular” syncdb operation, this will ask you if you want to create a Django superuser. That’s the perfect timing to do it!

Note

You might be tempted to do “dotcloud run ramen.www ramen/manage.py syncdb”, but it will not work for two reasons.

  1. Your code will be located in the “current” subdirectory on the remote service, and you should therefore prefix “current” when you want to run a remote script.
  2. The manage.py file created by Django starts with “#!/usr/bin/python” instead of “#!/usr/bin/env python”. We do not want to run the system-wide Python interpreter: we want to run the one bundled in a virtual environment (or virtualenv). To be sure to use the correct Python interpreter (instead of the one in /usr/bin/python), we must specify “python” on the “dotcloud run” command line.

Your sqlite database and Django basic tables have been created. But we can’t do anything with it yet; so in the next paragraph, we will activate the Django administration site.

Activate the administration site

To show off our new shiny (local) database, we will activate the administration site.

Edit ramen/settings.py, and uncomment the following line (in the INSTALLED_APPS list) by removing the #:

# 'django.contrib.admin',

Edit ramen/urls.py; uncomment the two following lines:

# from django.contrib import admin
# admin.autodiscover()

And this one:

# (r'^admin/', include(admin.site.urls)),

Push your code (again!):

ramen-on-dotcloud$ dotcloud push ramen.www .

You can now go to http://www.ramen.dotcloud.com/admin/ (remember to replace the beginning of the URL with your actual service and deployment name), and sign on using the login and password that you specified when doing the initial syncdb.

But, wait! Instead of the nice theme of the Django administration site, we are getting dull, blank forms! That’s because we are missing the admin media (CSS and JS static files used by Django admin). We will see in the next paragraph how to fix that.

Static files and admin media

By default, the Python service will deliver the “static” subdirectory as static files. We must therefore do the following:

  • create this static directory (since it does not exist yet in our Django project);
  • copy (or symlink) Django admin media files into this static directory;
  • instruct Django (through settings.py) of the path to use.

Django admin media files are shipped inside the Django distribution. On a Debian system, it is generally under /usr/share/pyshared/django/contrib/admin/media. On our DotCloud system, Django was installed inside a virtual environment, and the files should be somewhere around /home/dotcloud/env/lib/python2.6/site-packages/django/contrib/admin/media (phew!). Since this path could possibly change, we will use a dynamic method to compute the media path.

In the ramen-on-dotcloud directory, create a file named “postinstall”, with the following content:

#!/usr/bin/env python
import os
# To import anything under django.*, we must set this variable.
os.environ['DJANGO_SETTINGS_MODULE'] = 'ramen.settings'
# Import the admin module. The media directory is right under it!
import django.contrib.admin
# Retrieve the absolute path of the admin module.
admindir = os.path.dirname(django.contrib.admin.__file__)
# Add /media behind it.
mediadir = os.path.join(admindir, 'media')
# Compute the path of the symlink under the static directory.
staticlink = os.path.join('static', 'admin_media')
# If the link already exists, delete it.
if os.path.islink(staticlink):
    os.unlink(staticlink)
# Install the correct link.
os.symlink(mediadir, staticlink)

Then, make this script executable:

ramen-on-dotcloud$ chmod +x postinstall

This script will be automatically executed each time you do a “dotcloud push”. It will automatically re-create a symlink, /static/admin_media, pointing to the right admin media directory. This will continue to work even if this directory changes (because you install a newer version of Django, or because DotCloud Python service changes a few internal details).

Edit ramen/settings.py, find the ADMIN_MEDIA_PREFIX line, and update it as follows:

ADMIN_MEDIA_PREFIX = '/static/admin_media/'

Create the static directory (don’t forget this step or the postinstall script will fail):

ramen-on-dotcloud$ mkdir static

Last step is, of course, “dotcloud push ramen.www .” to upload your modified files and run the postinstall script.

Go back to the admin login page: the admin theme should now show up.

Use a postgresql database

As you know, sqlite is not the best choice for a production Django project. Therefore, we will see how to add a PostgreSQL service on DotCloud, and how to configure our Django project to use it.

Creating a PostgreSQL deployment is very simple:

$ dotcloud deploy -t postgresql ramen.sql

Note

We called our PostgreSQL service “ramen.sql”, but we could have named it “bento.wasabi” as well. However, it is a good practice to use the same deployment name (here, “ramen”) for related services. Also, it makes sense to use a distinctive name (hence “sql”).

Retrieve the connection parameters of the newly created service:

$ dotcloud info ramen.sql
config:
    postgresql_password: bAJYyA/_;(
deployment: ramen
name: ramen.sql
ports:
-   name: ssh
    url: ssh://postgres@sql.ramen.dotcloud.com:1088
-   name: sql
    url: pgsql://root:bAJYyA/_;(@sql.ramen.dotcloud.com:1087
type: postgresql

Warning

Your parameters will be different from those indicated here. In the following instructions, take care to replace the parameters with your parameters, of course.

Create the ramen database on the PostgreSQL service:

$ dotcloud run ramen.sql -- createdb -O root ramen

Note

We could also have created a “ramen” user in the PostgreSQL database; but for this example, it was not deemed necessary.

Note

Note that we had to use “dash dash” after the “run” command, else the -O option would have been catched by the DotCloud CLI.

Update ramen/settings.py to use your new PostgreSQL database; you should edit the DATABASES section in the beginning of the file so it looks like the following:

DATABASES = {
  'default': {
      'ENGINE': 'django.db.backends.postgresql_psycopg2',
      'NAME': 'ramen',
      'USER': 'root',
      'PASSWORD': 'bAJYyA/_;(',
      'HOST': 'sql.ramen.dotcloud.com',
      'PORT': '1087',
  }
}

Push the new settings:

ramen-on-dotcloud$ dotcloud push ramen.www .

Since we changed the database backend, we should issue a syncdb again:

ramen-on-dotcloud$ dotcloud run ramen.www python current/ramen/manage.py syncdb

It will ask you again to create the Django superuser.

Create a staging deployment

If you are an experienced Django developer and a die-hard system administrator, you might be left unimpressed with this tutorial. After all, we had to create a custom wsgi.py script and a postinstall hook to make everything work properly. Don’t leave now! We have more for you.

Let’s setup a staging environment to test new code without touching production.

Create the new Python service:

ramen-on-dotcloud$ dotcloud deploy -t python ramen.staging

Push your code to the new Python service:

ramen-on-dotcloud$ dotcloud push ramen.staging .

Point your browser to http://staging.ramen.dotcloud.com/admin/; voilà, you’re done.

Nice, huh?

Convert an existing Django project

Let’s summarize the work needed to convert an existing Django project to run on DotCloud:

  1. Specify your Python dependencies in a pip-style requirements.txt file.
  2. Convert your Django project to a WSGI application (using a wsgi.py boilerplate file as seen above).
  3. Move static files (media files and URLs) to /static.
  4. Install the postinstall hook to handle admin media (this can be skipped if you do not use the adminsitration site at all).
  5. If you want to run the database service on DotCloud, deploy it, retrieve its connection parameters and edit your settings.py accordingly.
  6. Push your code to a python service.

Troubleshooting

If things don’t work as expected, check the logs! The easiest thing is to run “dotcloud logs ramen.www”, and then repeat the request which caused problems in the first place.

If you want to examine older logs, use “dotcloud ssh ramen.www”, and check the logs – the first one should be the nginx error log, in /var/log/nginx/ramen-www.error.log; and then the uwsgi log, in /var/log/supervisor/uwsgi.log.

Advanced debugging with werkzeug

If you already used the werkzeug debugger (or if you didn’t, but are looking for an awesome online debugger for your Django app), maybe you want to know how to get it running on DotCloud.

It usually involves installing django-extensions and then invoking “manage.py runserver_plus”. How do we do that on DotCloud?

Follow those simple steps:

  1. Use the following wsgi.py file:
    import os
    import sys
    os.environ['DJANGO_SETTINGS_MODULE'] = 'ramen.settings'
    import django.core.handlers.wsgi
    djangoapplication = django.core.handlers.wsgi.WSGIHandler()
    def application(environ, start_response):
        if 'SCRIPT_NAME' in environ:
            del environ['SCRIPT_NAME']
        return djangoapplication(environ, start_response)
    # The following lines enable the werkzeug debugger
    import django.views.debug
    def null_technical_500_response(request, exc_type, exc_value, tb):
        raise exc_type, exc_value, tb
    django.views.debug.technical_500_response = null_technical_500_response
    from werkzeug.debug import DebuggedApplication
    application = DebuggedApplication(application, evalex=True)
  2. Add “werkzeug” to your requirements.txt file.
  3. And finally “dotcloud push” your app code.

The new wsgi.py file does two things:

  1. Wrap the WSGI application with the werkzeug debugger. Any exception raised (and uncaught) by the WSGI application will trigger the werkzeug debugger.
  2. Disable the standard exception-catching mechanism of Django – if we don’t do that, exceptions will be caught before they make it to the werkzeug debugger.

Now, exceptions will be shown not only with a traceback, but also an interactive JS-enabled console, allowing you to inspect the whole stack (including local variables and everything) with a Python prompt!

Warning

When you enable the werkzeug debugger, anyone triggering an exception will be able to access your code and your data (including database passwords and other sensitive credentials). Be careful and don’t leave it enabled longer than necessary, or add an extra protection layer to bar unauthorized users!

Once you’re done with the debugging, restore your old wsgi.py file and push your code again (you can leave werkzeug in your requirements.txt if you like; that does not matter).

External resources

Would you like to know more?

If you wrote something interesting as well, don’t hesitate to ping us so we can add it there!

- EOF -