Instalacja Django 1.1 + konfiguracja projektu na Mac OSX Snow Leopard (64bit)

W ostatnim poście obiecałem wspomnieć jeszcze o Django, dzisiaj postaram się przeprowadzić krok po kroku przez konfiguracje środowiska (na przykładzie Mac OSX 10.6 Snow Leopard – 64bit), instalacje stosu programistycznego, uruchomienia projektu oraz konfiguracji wdrożenia. W celu przeprowadzenia instalacji niezbędnych składników niezbędny będzie system portów (macport)

W pierwszej kolejności przeprowadzamy uaktualnienie systemu port oraz jego repozytorium wersji, jest to niezbędne gdyż wiele poprawek jest wprowadzanych na bieżąco (tutaj muszę zaznaczyć pochwalić community macporta za szybką reakcje, zgłoszony ticket – problem z py25-graphviz, rozwiązali w 45 minut od zgłoszenia):

$ sudo port selfupdate

Następnie przystępujemy do instalacji GITa z systemu portów (we wcześniejszych postach sugerowałem użycie pre-kompilowanej wersji binarnej, ze względu na błąd w zależnościach gitsvn do sqlite, jednak jak wspomniałem powyżej macport mnie przekonał):

$ sudo port install git-core +gitweb +svn

Kolejnym krokiem jest skonfigurowanie zmiennych GITa (w analogiczny sposób jak w poście dotyczącym Ruby on Rails i Leopard):

$ git config --global user.name "Your Name"
$ git config --global user.email youemail@youdomain.com
$ git config --global color.branch auto
$ git config --global color.diff auto
$ git config --global color.interactive auto
$ git config --global color.status auto
$ git config --global core.editor "mate -w"

W tym momencie możemy przystąpić do instalacji niezbędnej do naszej pracy bazy danych (w tym przypadku jest to Postgres w wersji 8.3, którego od dłuższego czasu faworyzuje w stosunku do mysql’a):

$ sudo port install postgresql83 postgresql83-server
$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.postgresql83-server.plist
$ sudo mkdir -p /opt/local/var/db/postgresql83/defaultdb
$ sudo chown postgres:postgres /opt/local/var/db/postgresql83/defaultdb
$ sudo su postgres -c '/opt/local/lib/postgresql83/bin/initdb 
-D /opt/local/var/db/postgresql83/defaultdb'

Następnym krokiem jest instalacja Pythona z systemu portów, co prawda Snow Leopard jest dostarczany z wersją 2.6, lecz mimo wszystko pozwoliłem sobie zainstalować wersję 2.5 (zalecaną do pracy z django):

$ sudo port install python25
$ sudo port install python_select

Musimy jeszcze ustawić w systemie odpowiednie ścieżki i aliasy tak by komendy wskazywały na wersję zainstalowaną przez macporty (uwaga: w momencie instalacji miałem problem związany z błędnym działaniem polecenia select_python, obecnie problem ten już jest rozwiązany):

$ sudo python_select python25

Aby skorzystać z możliwości jakie daje nam Django (obecnie 1.1), należy doinstalować następujące składniki (obsługa bazy postgres, ssl, pil, itd.):

$ sudo port install py25-psycopg2 +postgresql83
$ sudo port install py25-crypto py25-chardet py25-dateutil py25-socket-ssl py25-pil py25-pygraphviz

Poprawność instalacji i konfiguracji Pythona z systemu portu możemy potwierdzić wywołując polecenie:

$ python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"

wynikiem działania powinna być ścieżka do katalogu …/python2.5/site-packages

Na tym etapie gotowi jesteśmy zainstalować framework Django wraz z przydatnymi narzędziami:

$ sudo easy_install django
$ sudo easy_install south
$ sudo easy_install fabric
$ sudo easy_install django-extensions
$ sudo easy_install django-debug-toolbar

W tym momencie możemy utworzyć nasz pierwszy projekt:

$ django-admin.py startproject learningdjango

Efektem wykonania powyższego polecenia jest utworzenie katalogu learningdjango zawierającego pliki naszego projektu:

$ tree learningdjango
learningdjango
|-- __init__.py
|-- manage.py
|-- settings.py
`-- urls.py

Teraz jest odpowiedni moment na skonfigurowanie naszego projektu jako repozytorium GIT. W tym celu wykonujemy następujące polecenie z poziomu katalogu naszego projektu

$ git init

Tworzymy również plik .gitignore w którym definiujemy reguły ignorowania plików przez nasze repozytorium, z następującą zawartością:

.DS_Store
*.pyc
dev.db
local_settings.py
local_urls.py
media/*

następnie dodajemy nasze pliki do systemu wersjonowania i zabezpieczamy:

$ git add .
$ git commit -m "initial import."

Proponuje również stworzenie brancha (i przełączenie się na niego) w którym będziemy pracowali nad naszą konfiguracją projektu

$ git branch first_configuration
$ git checkout first_configuration

Pierwszym niezbędnym krokiem jest stworzenie bazy danych (polecam do tego instalacje pgAdmin z binarek dostępnych na stronie projektu postgres), oraz skonfigurowanie projektu w pliku settings.py.

W tym celu wprowadzamy następujące zmiany w pliku settings.py, zmiany te będą miały charakter “produkcyjny”:

wyłączamy tryb debug (dlaczego to robimy wyjaśnię w kolejnych krokach):

DEBUG = False
TEMPLATE_DEBUG = DEBUG

następnie konfigurujemy ADMINS (jest to lista osób wraz z ich mailami które zostaną poinformowane w przypadku wystąpienia nie obsłużonych/nie oczekiwanych wyjątków naszej aplikacji – oczywiście gdy wyżej wspomniana opcja DEBUG = False)

ADMINS = (
    ('Andrzej Sliwa', 'moj.mail@moja.domena'),
)

Kolejnym krokiem jest zdefiniowanie konfiguracji bazy danych (postgres):

DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'learningdjango_prod'
DATABASE_USER = 'learningdjango_prod'
DATABASE_PASSWORD = '123;)'
DATABASE_HOST = '127.0.0.1'
DATABASE_PORT = ''

Następnie konfigurujemy wysyłanie poczty (za pomocą gmaila)

EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'mojekonto@gmail.com'
EMAIL_HOST_PASSWORD = '123;)'
EMAIL_PORT = 587

Istotnym krokiem w przypadku konfiguracji django jest konfiguracja ścieżek

MEDIA_ROOT = "/home/andrzejsliwa/webapps/learningdjango/public/media/"

Definiujemy url do plików statycznych takich jak grafika, css itp. (w przypadku wersji produkcyjnej apache zajmuje się hostowaniem plików statycznych)

MEDIA_URL = 'http://learningdjango.pl/media/'

Ustawiamy opcje redirectu:

LOGIN_REDIRECT_URL = '/'

Ustawiamy url do plików panelu administracyjnego

ADMIN_MEDIA_PREFIX = '/admin_media/'

Następnie wskazujemy katalog szablonów (na produkcji):

TEMPLATE_DIRS = (
    '/home/andrzejsliwa/webapps/learningdjango/learningdjango/templates',
)

Włączamy niezbędne aplikacje:

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

Ostatnim niezbędnym składnikiem naszego pliku settings.py są polecenia:

try:
    from local_settings import *
except ImportError:
    pass

Cały plik po naszych zmianach prezentuje się następująco:

DEBUG = False
TEMPLATE_DEBUG = DEBUG

ADMINS = (
    ('Andrzej Sliwa', 'moj.mail@moja.domena'),
)

MANAGERS = ADMINS

DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'learningdjango_prod'
DATABASE_USER = 'learningdjango_prod'
DATABASE_PASSWORD = '123;)'
DATABASE_HOST = '127.0.0.1'
DATABASE_PORT = ''

# Email Server configuration
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'mojekonto@gmail.com'
EMAIL_HOST_PASSWORD = '123;)'
EMAIL_PORT = 587

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = "/home/andrzejsliwa/webapps/learningdjango/public/media/"
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = 'http://learningdjango.pl/media/'

LOGIN_REDIRECT_URL = '/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/admin_media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = '3x%axew739n_qae5g^cofle)#row(pl+zb&*42%bw&a#@_ccxc'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
#     'django.template.loaders.eggs.load_template_source',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

ROOT_URLCONF = 'learningdjango.urls'

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    '/home/andrzejsliwa/webapps/learningdjango/learningdjango/templates',
)

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

try:
    from local_settings import *
except ImportError:
    pass

Dzieki wyżej wymienionym poleceniom nadpiszemy naszą konfiguracje produkcyjną ustawieniami developerskimi.

Na tym etapie również zabezpieczamy wyniki naszej pracy:

git add .
git commit -m "initial config."

Następnie tworzymy przykładowy plik local_settings.py.example który wygląda następująco

import os
here = lambda *x: os.path.join(os.path.abspath(os.path.dirname(__file__)), *x)

DEBUG = True
TEMPLATE_DEBUG = DEBUG

DATABASE_ENGINE = 'postgresql_psycopg2' 
DATABASE_NAME = 'learningdjango_dev'    
DATABASE_USER = 'postgres'              
DATABASE_PASSWORD = ''                  
DATABASE_HOST = ''                      
DATABASE_PORT = ''                      

MEDIA_ROOT = here('media')

MEDIA_URL = 'http://localhost/media/'

TEMPLATE_DIRS = (
    here('templates'),
)

W pliku tym nadpisujemy ustawienia produkcyjne pliku konfiguracyjnego, oraz plik ten dodajemy do naszego repozytorium jako plik wzorcowy konfiguracji developerskiej ustawień:

$ git add local_settings.py.example
$ git commit -m "added local_settings example"

Następnie przechodzimy do konfiguracji pliku urls.py który powinien wyglądać następująco

from django.conf.urls.defaults import *

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Example:
    # (r'^learningdjango/', include('learningdjango.foo.urls')),
    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
    # to INSTALLED_APPS to enable admin documentation:
    (r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    (r'^admin/', include(admin.site.urls)),
)

try:
    from local_urls import *
except ImportError:
    pass

Również w pliku urls.py korzystamy z możliwości nadpisania konfiguracji produkcyjnej za pomocą pliku local_urls.py, w tym celu tworzymy przykładowy plik konfiguracji developerskiej:

from django.conf.urls.defaults import *
from learningdjango.urls import urlpatterns

import os
here = lambda *x: os.path.join(os.path.abspath(os.path.dirname(__file__)), *x)

urlpatterns += patterns("django.views",
    url(r"^media/(?P
.*)", "static.serve", { "document_root": here('media'),})
)

plik ten dodajemy do naszego repozytorium jako plik wzorcowy konfiguracji developerskiej ustawień:

$ git add local_urls.py.example
$ git commit -m "added urls example"

Na tym etapie możemy zakończyć pracę nad konfiguracją wracając do głównego brancha i mergując zmiany (takie podejście do pracy z lokalnymi branchami jest podejściem zalecanym.)

$ git checkout master
$ git merge first_configuration

Ostatnią czynnością przed uruchomieniem naszego skonfigurowanego projektu jest skopiowanie przykładów konfiguracji developerskiej tak by zostały one wczytane podczas uruchomienia (ponieważ nazwy tych plików są ignorowane nie ma obawy że trafią na serwer produkcyjny)

$ cp local_settings.py.example local_settings.py
$ cp local_urls.py.example local_urls.py

W tym momencie możemy uruchomić nasz testowy projekt

$ ./manage.py syncdb
$ ./manage.py runserver

W oknie przeglądarki możemy spróbować otworzyć słynny panel administracyjny django

http://localhost:8000/admin

Na zakończenie dzisiejszego odcinka przygód z Django, przedstawię również przykładowy skrypt instalacyjny dla naszej aplikacji na serwerach hostingu webfaction, plik ten nosi nazwę fabfile.py:

# globals
config.app_name = 'learningdjango'
# enviroments
def prod():
    "Use the production server"
    config.fab_hosts = ['learningdjango.pl']
    config.fab_user = 'andrzejsliwa'
    # git configuration
    config.git_server = 'mygitserver'
    config.git_user = 'git'
    config.git_user_name = 'my@mail.com'
    config.git_user_email = 'Andrzej Sliwa'
    # destination configuration
    config.remote_destination = '~/webapps/$(app_name)'
    config.remote_sitepackages_path = "$(remote_destination)/lib/python2.5"
    config.remote_app_config_dir = "$(remote_destination)/$(app_name)/apache"
    config.remote_apache_dir = "$(remote_destination)/apache2"

# tasks
def test():
    "Run the test suite and bail out if it fails"
    local("./manage.py test", fail="abort")

def setup():
    "Prepare setup for deployment."
    run('mkdir -p $(remote_destination)/public/media')
    run('ln -s $(remote_sitepackages_path)/django/contrib/admin/media $(remote_destination)/public/media/admin')
    run('cd $(remote_destination); git clone $(git_user)@$(git_server):$(app_name).git')
    run('git config --global user.email "$(git_user_email)"')
    run('git config --global user.name "$(git_user_name)"')
    run('easy_install-2.5 south')
    run('easy_install-2.5 django-debug-toolbar')
    run('easy_install-2.5 django-extensions')
    invoke(copy_app_conf)
    invoke(restart)

def copy_app_conf():
    "Copy all deployment configuration files."
    run('cp $(remote_app_config_dir)/$(app_name).conf $(remote_apache_dir)/conf/httpd.conf')
    run('cp $(remote_app_config_dir)/$(app_name).wsgi $(remote_destination)/$(app_name).wsgi')
    run('cp $(remote_app_config_dir)/robots.txt $(remote_destination)/robots.txt')

def restart():
    "Restart apache server"
    run('$(remote_apache_dir)/bin/restart')

def stop():
    "Stop apache server"
    run('$(remote_apache_dir)/bin/stop')

def start():
    "Start apache server"
    run('$(remote_destination)/apache2/bin/start')

def migrate():
    "Run migrations"
    run('cd $(remote_destination)/$(app_name); python2.5 manage.py syncdb ')
    invoke(restart)

def rollback():
    "Rollback last changes."
    run('cd $(app_path); git checkout HEAD~1')
    invoke(copy_app_conf)
    invoke(restart)

def deploy():
    "Deploy current code on server."
    require('fab_hosts', provided_by = [prod,])
    local('git push')
    run('cd $(remote_destination)/$(app_name); git stash; git stash clear; git checkout master; git pull')
    invoke(copy_app_conf)
    invoke(restart)
    invoke(debugoff)

def debugon():
    "Turn debug mode on"
    require('fab_hosts', provided_by = [prod,])
    run("cd $(remote_destination)/$(app_name); sed -i -e 's/DEBUG = .*/DEBUG = True/' settings.py")
    invoke(restart)

def debugoff():
    "Turn debug mode off"
    require('fab_hosts', provided_by = [prod,])
    run("cd $(remote_destination)/$(app_name); sed -i -e 's/DEBUG = .*/DEBUG = False/' settings.py")
    invoke(restart)

def access_log():
    "Show latest 100 lines of access log"
    run('tail -n 100 $(remote_apache_dir)/logs/access_log')

def error_log():
    "Show latest 100 lines of error log"
    run('tail -n 100 $(remote_apache_dir)/logs/error_log')

Uzupełenieniem konfiguracji wymaganej przez plik instalacji są pliki (learningdjango.conf, learningdjango.wsgi, robots.txt) znajdujące się w katalogu apache w projekcie naszej aplikacji:

$ tree learningdjango
learningdjango
|-- __init__.py
|-- apache
|   |-- robots.txt
|   |-- learningdjango.conf
|   `-- learningdjango.wsgi
|-- fabfile.py
|-- local_settings.py
|-- local_settings.py.example
|-- local_urls.py
|-- local_urls.py.example
|-- manage.py
|-- settings.py
`-- urls.py

Plik learningdjango.conf zawiera:

ServerRoot "/home/andrzejsliwa/webapps/starface/apache2"
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule wsgi_module modules/mod_wsgi.s
SetHandle

ServerRoot "/home/andrzejsliwa/webapps/learningdjango/apache2"
LoadModule dir_module modules/mod_dir.so
LoadModule env_module modules/mod_env.so
LoadModule alias_module modules/mod_alias.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule wsgi_module modules/mod_wsgi.so

KeepAlive Off
Listen 10753
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog logs/access_log combined
ServerLimit 2

<Location "/robots.txt">
    SetHandler None
</Location>

<Location "/admin_media">
    SetHandler default
</Location>

<Location "/media">
    SetHandler none
</Location>

Alias /media /home/andrzejsliwa/webapps/learningdjango/public/media
Alias /admin_media /home/andrzejsliwa/webapps/learningdjango/lib/python2.5/django/contrib/admin/media
Alias /robots.txt /home/andrzejsliwa/webapps/learningdjango/robots.txt
WSGIScriptAlias / /home/andrzejsliwa/webapps/learningdjango/learningdjango.wsgi

Plik learningdjango.wsgi zawiera:

import os
import sys                                                                                                                                         

sys.path = ['/home/andrzejsliwa/webapps/learningdjango', '/home/andrzejsliwa/webapps/learningdjango/lib/python2.5'] + sys.path
from django.core.handlers.wsgi import WSGIHandler                                                                                                  

os.environ['DJANGO_SETTINGS_MODULE'] = 'learningdjango.settings'
application = WSGIHandler()

Plik robots.txt zabezpiecza nas przed niechcianym indeksowaniem naszej nie ukonczonej jeszcze aplikacji:

User-agent: * Disallow: /

Wyżej zaprezentowana konfiguracja pozwala na wygodne skonfigurowanie i przeprowadzenie wdrożenia aplikacji:

$ fab prod setup
$ fab prod deploy

Wyżej prezentowane pliki i konfiguracje nie stanowią oczywiście w pełni gotowego rozwiązania, stanowią jedynie przykład syntezy zalecanych rozwiązań znalezionych w dokumentacji projektów i przykładach wykorzystania. Mogą stanowić dobrą bazę do dalszej pracy nad aplikacją, środowiskiem programistycznym i produkcyjnym.

W następnym poście postaram się zaprezentować podstawy tworzenia aplikacji wraz z wykorzystaniem najciekawszych rozszerzeń środowiska programistycznego Django.

blog comments powered by Disqus