Deployment

The following example documents one way to deploy comics. As comics is a standard Django project with an additional batch job for , it may be deployed in just about any way a Django project may be deployed. Please refer to Django’s deployment documentation for further details.

In the following examples we assume that we are deploying comics at http://comics.example.com/, using Apache, mod_wsgi, and PostgreSQL. The Django application and batch job is both running as the user comics-user. The static media files, like comic images, are served from http://comics.example.com/media/, but may also be served from a different host.

Database

comics should theoretically work with any database supported by Django. Though, development is mostly done on SQLite 3 and PostgreSQL 8.x. For production use, PostgreSQL is the recommended choice.

Note

If you are going to use SQLite in a deployment with Apache and so on, you need to ensure that the user the web server will be running as has write access to the directory the SQLite database file is located in.

Additional database indexes

Out of the box, comics will create a few extra database indexes that will make it a lot more performant. In addition, creating the following indexes will improve performance a bit more:

CREATE INDEX comics_release_comic_id_pub_date
    ON comics_release (comic_id, pub_date);

Example Apache vhost

This example requires your Apache to have the mod_wsgi module. For efficient static media serving and caching, you should probably enable mod_deflate and mod_expires for /media and /static.

<VirtualHost *:80>
    ServerName comics.example.com
    ErrorLog /var/log/apache2/comics.example.com-error.log
    CustomLog /var/log/apache2/comics.example.com-access.log combined

    # Not used, but Apache will complain if the dir does not exist
    DocumentRoot /var/www/comics.example.com

    # Static media hosting
    Alias /media/ /path/to/comics/media/
    Alias /static/ /path/to/comics/static/

    # mod_wsgi setup
    WSGIDaemonProcess comics user=comics-user group=comics-user threads=50 maximum-requests=10000
    WSGIProcessGroup comics
    WSGIScriptAlias / /path/to/comics/comics/wsgi/__init__.py
    <Directory /path/to/comics/comics/wsgi>
        Require all granted
    </Directory>
</VirtualHost>

For details, please refer to the documentation of the Apache and mod_wsgi projects.

Available settings

The Django docs got a list of all available settings. Some of them are repeated here if they are especially relevant for comics. In addition, comics adds some settings of its own, which are all listed here as well.

comics.settings.base.ACCOUNT_ACTIVATION_DAYS = 7

Number of days an the account activation link will work

comics.settings.base.ACCOUNT_INVITATION_DAYS = 7

Number of days an invitation will be valid

comics.settings.base.COMICS_BROWSER_REFRESH_INTERVAL = 60

Number of seconds browsers at the latest view of “My comics” should wait before they check for new releases again

comics.settings.base.COMICS_GOOGLE_ANALYTICS_CODE = None

Google Analytics tracking code. Tracking code will be included on all pages if this is set.

comics.settings.base.COMICS_IMAGE_BLACKLIST = ('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 'f8021551b772384d1f4309e0ee15c94cea9ec1e61ba0a7aade8036e40e3179fe', 'dd040144f802bab9b96892cc2e1be26b226e7b43b275aa49dbcc9c4a254d6782', '61c66a1c84408df5b855004dd799d5e59f4af99f4c6fe8bf4aabf8963cab7cb5', '01237a79e2a283718059e4a180a01e8ffa9f9b36af7e0cad5650dd1a08665971', '181e7d11ebd3224a910d9eba2995349da5d483f3ae9643a2efe4f7dd3d9f668d', '6dec8be9787fc8b103746886033ccad7348bc4eec44c12994ba83596f3cbcd32', 'f56248bf5b94b324d495c3967e568337b6b15249d4dfe7f9d8afdca4cb54cd29', '0a929bfebf333a16226e0734bbaefe3b85f9c615ff8fb7a777954793788b6e34', 'cde5b71cfb91c05d0cd19f35e325fc1cc9f529dfbce5c6e2583a3aa73d240638', '60478320f08212249aefaa3ac647fa182dc7f0d7b70e5691c5f95f9859319bdf', '38eca900236617b2c38768c5e5fa410544fea7a3b79cc1e9bd45043623124dbf', 'e90e3718487c99190426b3b38639670d4a3ee39c1e7319b9b781740b0c7a53bf')

SHA256 of blacklisted images

comics.settings.base.COMICS_LOG_FILENAME = '/home/docs/checkouts/readthedocs.org/user_builds/comics/checkouts/latest/comics.log'

Comics log file path on disk

comics.settings.base.COMICS_MAX_DAYS_IN_FEED = 30

Maximum number of days to show in a feed

comics.settings.base.COMICS_MAX_RELEASES_PER_PAGE = 50

Maximum number of releases to show on one page

comics.settings.base.COMICS_NUM_DAYS_COMIC_IS_NEW = 7

Number of days a new comic on the site is labeled as new

comics.settings.base.COMICS_SITE_TITLE = 'example.com'

Name of the site. Used in page header, page title, feed titles, etc.

comics.settings.base.DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': '/home/docs/checkouts/readthedocs.org/user_builds/comics/checkouts/latest/db.sqlite3'}}

Database settings. You will want to change this for production. See the Django docs for details.

comics.settings.base.INVITATIONS_PER_USER = 10

Number of invitations each existing user can send

comics.settings.base.INVITE_MODE = False

Turn invitations off by default, leaving the site open for user registrations

comics.settings.base.MEDIA_ROOT = '/home/docs/checkouts/readthedocs.org/user_builds/comics/checkouts/latest/media'

Path on disk to where downloaded media will be stored and served from

comics.settings.base.MEDIA_URL = '/media/'

URL to where downloaded media will be stored and served from

Time the user session cookies will be valid. 1 year by default.

comics.settings.base.STATIC_ROOT = '/home/docs/checkouts/readthedocs.org/user_builds/comics/checkouts/latest/static'

Path on disk to where static files will be served from

comics.settings.base.STATIC_URL = '/static/'

URL to where static files will be served from

comics.settings.base.TIME_ZONE = 'UTC'

Default time zone to use when displaying datetimes to users

Example settings/local.py

To change settings, you should not change the settings files shipped with comics, but instead override the settings in your own comics/comics/settings/local.py. Even if you do not want to override any default settings, you must add a local.py which at least sets SECRET_KEY and most probably your database settings. A full local.py may look like this:

# Local settings -- do NOT commit to a VCS

# Make this unique, and don't share it with anybody.
SECRET_KEY = 'djdjdk5k4$(DA=!SDAD!)12312415151368edkfjgngnw3m!$!Dfafa'

# You can override any settings here, like database settings.

ADMINS = (
    ('Comics Webmaster', 'comics@example.com'),
)
MANAGERS = ADMINS

# Database settings
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'comics',
        'USER': 'comics',
        'PASSWORD': 'topsecret',
        'HOST': 'localhost',
        'PORT': '',
    }
}

# Internal IP addresses
INTERNAL_IPS = ('127.0.0.1',)

# Media
MEDIA_ROOT = '/var/www/comics.example.com/media/'
MEDIA_URL = 'http://comics.example.com/media/'
STATIC_ROOT = '/var/www/comics.example.com/static/'
STATIC_URL = 'http://comics.example.com/static/'

# Caching
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
CACHE_MIDDLEWARE_KEY_PREFIX = 'comics'

Of course, you should change most, if not all, of these settings for your own installation. If your are not running a memcached server, remove the part on caching from your local.py.

Collecting static files

When you’re not running in development mode, you’ll need to collect the static files from all apps into the STATIC_ROOT. To do this, run:

python manage.py collectstatic

You have to rerun this command every time you deploy changes to graphics, CSS and JavaScript. For more details, see the Django documentation on staticfiles.

Example cronjob

To get new comics, you should run comics_getreleases regularly. In addition, you should run cleanupinvitation once in a while to remove expired user invitations and cleanupregistration to delete expired users. One way is to use cron e.g. by placing the following in /etc/cron.d/comics:

MAILTO=comics@example.com
PYTHONPATH=/path/to/comics
1 * * * * comics-user python /path/to/comics/manage.py comics_getreleases -v0
1 3 * * * comics-user python /path/to/comics/manage.py cleanupinvitation -v0
2 3 * * * comics-user python /path/to/comics/manage.py cleanupregistration -v0

If you have installed comics’ dependencies in a virtualenv instead of globally, the cronjob must also activate the virtualenv. This can be done by using the python interpreter from the virtualenv:

MAILTO=comics@example.com
PYTHONPATH=/path/to/comics
1 * * * * comics-user /path/to/comics-virtualenv/bin/python /path/to/comics/manage.py comics_getreleases -v0
1 3 * * * comics-user /path/to/comics-virtualenv/bin/python /path/to/comics/manage.py cleanupinvitation -v0
2 3 * * * comics-user /path/to/comics-virtualenv/bin/python /path/to/comics/manage.py cleanupregistration -v0

By setting MAILTO any exceptions raised by the comic crawlers will be sent by mail to the given mail address. 1 * * * * specifies that the command should be run 1 minute past every hour.