Commits

Luke Plant committed ce6e906

Initial commit

Comments (0)

Files changed (2)

+Django starter fabfile
+======================
+
+The provided fabfile.py is a basic starter fabfile for use with the deployment
+tool 'fabric' and Django projects. It has been used successfully to deploy to a
+site hosted on webfaction.com.
+
+Put this fabfile.py in the directory that contains all your project sources, and
+customize to suit.
+
+
+Requirements
+------------
+
+The fabfile assumes:
+
+- you are using Django 1.3 or greater
+
+- you have your development environment already setup, including
+  having 'fabric' installed.
+
+- your server is basically 'provisioned' i.e.
+
+  - SSH setup for logging in (and you've set up your developer machine
+    for passwordless login).
+
+  - database installed etc., and you will use some other system
+    to create your Django database
+
+  - Python is installed
+
+- use of virtualenv to isolate the environment. You will need to have virtualenv
+  available on your server. For maximum isolation, virtualenv is run with
+  '--no-site-packages'.
+
+- use of 'south' for migrations.
+
+- a project layout like this:
+
+  - some_dir/  - directory containing all sources, under source control
+    - fabfile.py
+    - manage.py
+    - top level Python package directories. For example:
+    - myproject/
+    - myproject/__init__.py
+    - myapp/
+    - etc.
+
+- settings.py on the server that correctly defines and separates STATIC_ROOT and
+  MEDIA_ROOT. STATIC_ROOT is populated by the Django 'collectstatic' command,
+  and files already there are cleared out.
+
+- manage.py loads the correct settings.py when run on the server.
+
+- use of a pip 'requirements.txt' for installation of Python packages, in the
+  same directory as the fabfile. It should contain at least Django and south.
+
+- use of Mercurial for source control, requiring hg to be installed on
+  your server. (This could be easily modified to use git).
+
+- deployment to a POSIX server. (Uses posixpath for path manipulation)
+
+- you can cope with some downtime when deploying, which is needed to update
+  source, dependencies and run migrations.
+
+
+Usage
+-----
+
+Copy the fabfile.py to your top level of your project, and modify.
+
+Use 'fab deploy' to deploy.
+
+You might want to do 'fab first_deployment_mode deploy' for the first deployment.
+
+Use 'fab -l' to see other commands
+
+Tips
+----
+
+To extend for a staging/production system:
+
+1) Change the constants at the top to None where these constants
+   are different for the two environments.
+
+2) Create tasks 'staging' and 'production' which set these globals.
+   These commands must be run before the 'deploy' command.
+"""
+Starter fabfile for deploying a Django project.
+
+Designed for Webfaction, but should work on any similar hosting system.
+
+Change all the things marked CHANGEME. Other things can be left at their
+defaults if you are happy with the default layout.
+"""
+
+import posixpath
+
+from fabric.api import run, local, abort, env, put, settings, cd, task
+from fabric.decorators import runs_once
+from fabric.contrib.files import exists
+from fabric.context_managers import cd, lcd, settings, hide
+
+# Host and login username:
+env.hosts = ['foo@foo.com'] ## CHANGEME!
+
+# Directory where everything to do with this app will be stored on the server.
+DJANGO_APP_DIR = '/home/foo/webapps/myapp_django/' ## CHANGEME!
+
+# Directory where static sources should be collected.
+# This must correspond to the value of STATIC_ROOT in the settings.py
+# that is used on the server.
+STATIC_APP_DIR = '/home/foo/webapps/myapp_static/' ## CHANGEME!
+
+
+# Directory where user uploaded media files should be stored
+# Corresponds to MEDIA_ROOT Django setting, and should be mapped to MEDIA_URL
+USERMEDIA_APP_DIR = '/home/foo/webapps/myapp_usermedia/' ## CHANGEME!
+
+# Subdirectory of DJANGO_APP_DIR in which project sources will be stored
+SRC_SUBDIR = 'src'
+
+# Subdirectory of DJANGO_APP_DIR in which virtualenv will be stored
+VENV_SUBDIR = 'venv'
+
+# Python version
+PYTHON_BIN = "python2.7"
+PYTHON_PREFIX = "" # e.g. /usr/local  Use "" for automatic
+PYTHON_FULL_PATH = "%s/bin/%s" % (PYTHON_PREFIX, PYTHON_BIN) if PYTHON_PREFIX else PYTHON_BIN
+
+
+# Commands to stop and start the webserver that is serving the Django app.
+# CHANGEME!  These defaults work for Webfaction
+DJANGO_SERVER_STOP = posixpath.join(DJANGO_APP_DIR, 'apache2', 'bin', 'stop')
+DJANGO_SERVER_START = posixpath.join(DJANGO_APP_DIR, 'apache2', 'bin', 'start')
+DJANGO_SERVER_RESTART = None
+
+src_dir = posixpath.join(DJANGO_APP_DIR, SRC_SUBDIR)
+venv_dir = posixpath.join(DJANGO_APP_DIR, VENV_SUBDIR)
+
+
+def virtualenv(venv_dir):
+    """
+    Context manager that establishes a virtualenv to use.
+    """
+    return settings(venv=venv_dir)
+
+
+def run_venv(command, **kwargs):
+    """
+    Runs a command in a virtualenv (which has been specified using
+    the virtualenv context manager
+    """
+    run("source %s/bin/activate" % env.venv + " && " + command, **kwargs)
+
+
+def install_dependencies():
+    ensure_virtualenv()
+    with virtualenv(venv_dir):
+        with cd(src_dir):
+            run_venv("pip install -r requirements.txt")
+
+
+def ensure_virtualenv():
+    if exists(venv_dir):
+        return
+
+    with cd(DJANGO_APP_DIR):
+        run("virtualenv --no-site-packages --python=%s %s" %
+            (PYTHON_BIN, VENV_SUBDIR))
+        run("echo %s > %s/lib/%s/site-packages/projectsource.pth" %
+            (src_dir, VENV_SUBDIR, PYTHON_BIN))
+
+
+def ensure_src_dir():
+    if not exists(src_dir):
+        run("mkdir -p %s" % src_dir)
+    with cd(src_dir):
+        if not exists(posixpath.join(src_dir, '.hg')):
+            run("hg init")
+
+
+def push_sources():
+    """
+    Push source code to server
+    """
+    ensure_src_dir()
+    local("hg push -f ssh://%(user)s@%(host)s/%(path)s" %
+          dict(host=env.host,
+               user=env.user,
+               path=src_dir,
+               ))
+    with cd(src_dir):
+        run("hg update")
+
+
+@task
+def webserver_stop():
+    """
+    Stop the webserver that is running the Django instance
+    """
+    run(DJANGO_SERVER_STOP)
+
+
+@task
+def webserver_start():
+    """
+    Startsp the webserver that is running the Django instance
+    """
+    run(DJANGO_SERVER_START)
+
+
+@task
+def webserver_restart():
+    """
+    Restarts the webserver that is running the Django instance
+    """
+    if DJANGO_SERVER_RESTART:
+        run(DJANGO_SERVER_RESTART)
+    else:
+        with settings(warn_only=True):
+            webserver_stop()
+        webserver_start()
+
+
+def build_static():
+    assert STATIC_APP_DIR.strip() != '' and STATIC_APP_DIR.strip() != '/'
+    # Before Django 1.4 we don't have the --clear option to collectstatic
+    run("rm -rf %s/*" % STATIC_APP_DIR)
+
+    with virtualenv(venv_dir):
+        with cd(src_dir):
+            run_venv("./manage.py collectstatic -v 0 --noinput")
+
+    run("chmod -R ugo+r %s" % STATIC_APP_DIR)
+
+
+@task
+def first_deployment_mode():
+    """
+    Use before first deployment to switch on fake south migrations.
+    """
+    env.initial_deploy = True
+
+
+def update_database():
+    with virtualenv(venv_dir):
+        with cd(src_dir):
+            if getattr(env, 'initial_deploy', False):
+                run_venv("./manage.py syncdb --all")
+                run_venv("./manage.py migrate --fake --noinput")
+            else:
+                run_venv("./manage.py syncdb --noinput")
+                run_venv("./manage.py migrate --noinput")
+
+
+@task
+def deploy():
+    """
+    Deploy project.
+    """
+    with settings(warn_only=True):
+        webserver_stop()
+    push_sources()
+    install_dependencies()
+    update_database()
+    build_static()
+
+    webserver_start()