среда, 6 января 2010 г.

Релиз Django 1.2 Alpha

Сегодня случилось приятное событие и зарелизилась джанга 1.2 альфа.

Самые интересные нововведения:

1) Email-бэкенды
Появилась возможность указать бэкенд для отправления емэйлов, в стандартной поставке есть smtp/file/dummy/locmem/console бэкенды, и можно написать свой. Тестировать приложения намного приятнее.

2) -User Messages API +Messages Framework
User Messages API, тот который (user.message_set.create()) был выпилен, и ему на смену пришел фрэймворк messages из контриба.

3) Поддержка множественных баз данных (ура)
Теперь БД определяются следующим образом

DATABASES = {
'default': {
'NAME': 'app_data',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'users': {
'NAME': 'user_data'
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'priv4te'
}
}

И соответственно при работе с ORM можно указать, какую из БД использовать.
Например так:


>>> Author.objects.using('default').all()
# This will run on the 'other' database
>>> Author.objects.using('other').all()
# Specify database on save
>>> user_obj.save(using='legacy_users')


До версии 1.4 старый способ объявления настроек БД выпилен не будет, и будут работать оба.

4) Еще одно очень приятное нововведение - умный if тэг в шаблонах.

Да, можно выкидывать smart_if.py, потому что теперь все его возможности идут из коробки.

5) django.template.loaders.cached.Loader

Лоадер для шаблонов, который кэширует результат загрузки с диска и компиляции шаблона.
По-умолчанию выключен.

6) Кастомная валидация моделей (и моделеформ на их базе)

Правда в Release Notes про нее не написано, но в транке она уже есть.

Все остальное не так ожидаемо (по моему личному мнению) и ознакомиться с этим можно по ссылке:
Release notes (django 1.2 alpha) Читать дальше.

воскресенье, 24 мая 2009 г.

Отладка Django-приложений с помощью pdb

Когда я только начинал знакомство с Django/Python миром, для отладки я использовал что-то вроде
print var1, var2, var3
в нужном месте вида. Однако этот подход категорически неудобен, и я посмотрел в сторону pdb.


Модуль pdb — это интерактивный отладчик для программ написанных на Python. С помощью него можно отлаживать уже готовые программы. Заведем абсолютно бесполезную программу, и назовем ее test.py. Вот ее содержимое:
#!/usr/bin/python
import sys

if len(sys.argv) > 1:
arg = sys.argv[1]
arg = arg + ' for you'
else:
arg = 'Nothing for you'

print arg

Запустим ее:
$ python test.py
Nothing for you
$ python test.py Flowers
Flowers for you

Отлично. Пример очень простой, но все же попробуем запустить ее с использованием отладчика. Привожу лог работы, с моими комментариями:
$ python -m pdb test.py # запускаем test.py с инжектированием модуля pdb
> /home/n0uk/dev/workspace/gtest/test.py(2)()
-> import sys # PDB остановился тут и ждет нашего ввода
(Pdb) l # l или list — вывести исходный код файла, без аргументов выводит 11 строчек до выполняемой строки, и 11 после.
1 #!/usr/bin/python
2 -> import sys # Стрелочка показывает где мы.
3
4 if len(sys.argv) > 1:
5 arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) sys.argv # смотрим переменную sys.argv
*** NameError: name 'sys' is not defined # Она еще не определена.
(Pdb) n # выполняем текущую строку, и переходим к следующей
> /home/n0uk/dev/workspace/gtest/test.py(4)()
-> if len(sys.argv) > 1:
(Pdb) sys.argv
['test.py']
(Pdb) l # оглядываемся
1 #!/usr/bin/python
2 import sys
3
4 -> if len(sys.argv) > 1:
5 arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) sys.argv = ['test.py', 'Only'] # изменим sys.argv "на лету"
(Pdb) sys.argv
['test.py', 'Only']
(Pdb) n # переходим дальше
> /home/n0uk/dev/workspace/gtest/test.py(5)()
-> arg = sys.argv[1]
(Pdb) l
1 #!/usr/bin/python
2 import sys
3
4 if len(sys.argv) > 1:
5 -> arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) n
> /home/n0uk/dev/workspace/gtest/test.py(6)()
-> arg = arg + ' for you'
(Pdb) c # продолжаем выполнение в обычном режиме
Only for you # вот оно наше Only, которое мы подставили в аргументы.

Как видим, мы можем получать состояние переменных в любой момент времени, изменять их во время выполнения, и видеть конечный результат.

Теперь воспользуемся отладчиком в контексте Django. Напишем чего-нибудь несуразное, например поиск по модели пользователей:
from django.shortcuts import render_to_response
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.db.models import Q

def search(query):
""" Search user by email, nickname, last_name, first_name. """
# search in this fields
search_in = 'first_name', 'last_name', 'username', 'email'
filters = None
# make filters for fields
for field in search_in:
kw = {'%s__icontains' % field: query}
filter = Q(**kw)
if filters:
filters = filters | filter
else:
filters = filter
# return qs
return User.objects.filter(filters)

def search_user(request):
""" List users matching query. """
# check query
try:
query = request.GET['query']
except KeyError:
return HttpResponse('Need a `query` parameter.')
# search users
users = search(query.strip())
if users:
response = '%s users matching query.
' % users.count()
response += ','.join([user.username for user in users])
else:
response = '0 users matching query.'
return HttpResponse(response)

Замапим этот урл в urls.py:
    ...
(r'^test/', 'testapp.views.search_user'),
...

Попробуем открыть в браузере и протестировать:



Теперь использование отладчика. Впишем в наш вид такие строки:
def search_user(request):
""" List users matching query. """
import pdb
pdb.set_trace()
...

Запустим dev сервер:
$ ./manage.py runserver
Validating models...
0 errors found

Django version 1.0.2 final, using settings 'gtest.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

И откроем страницу в браузере — http://localhost:8000/test/
Страница не откроется, но в консоли вывалится интерактивный шелл:
> /home/n0uk/dev/workspace/gtest/testapp/views.py(27)search_user()
-> try:
(Pdb)

Где мы можем делать, все, что угодно:
(Pdb) l
22 def search_user(request):
23 """ List users matching query. """
24 import pdb
25 pdb.set_trace()
26 # check query
27 -> try:
28 query = request.GET['query']
29 except KeyError:
30 return HttpResponse('Need a `query` parameter.')
31 # search users
32 users = search(query.strip())
(Pdb) request.GET

(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) n
MultiValueDictKeyError: MultiVal...t: {}>",)
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) l
23 """ List users matching query. """
24 import pdb
25 pdb.set_trace()
26 # check query
27 try:
28 -> query = request.GET['query']
29 except KeyError:
30 return HttpResponse('Need a `query` parameter.')
31 # search users
32 users = search(query.strip())
33 if users:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(29)search_user()
-> except KeyError:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(30)search_user()
-> return HttpResponse('Need a `query` parameter.')
(Pdb) c
[24/May/2009 05:54:12] "GET /test/ HTTP/1.1" 200 25

Ответ ушел в браузер. Посмотрим, что будет если передать параметр:
> /home/n0uk/dev/workspace/gtest/testapp/views.py(27)search_user()
-> try:
(Pdb) request.GET

(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(32)search_user()
-> users = search(query.strip())
(Pdb) s
--Call--
> /home/n0uk/dev/workspace/gtest/testapp/views.py(6)search()
-> def search(query):
(Pdb) l
1 from django.shortcuts import render_to_response
2 from django.http import HttpResponse
3 from django.contrib.auth.models import User
4 from django.db.models import Q
5
6 -> def search(query):
7 """ Search user by email, nickname, last_name, first_name. """
8 # search in this fields
9 search_in = 'first_name', 'last_name', 'username', 'email'
10 filters = None
11 # make filters for fields
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(9)search()
-> search_in = 'first_name', 'last_name', 'username', 'email'
(Pdb) search_in
*** NameError: name 'search_in' is not defined
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(10)search()
-> filters = None
(Pdb) search_in
('first_name', 'last_name', 'username', 'email')
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(12)search()
-> for field in search_in:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(13)search()
-> kw = {'%s__icontains' % field: query}
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(14)search()
-> filter = Q(**kw)
(Pdb) kw
{'first_name__icontains': u'n0uk'}
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(15)search()
-> if filters:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(18)search()
-> filters = filter
(Pdb) l
13 kw = {'%s__icontains' % field: query}
14 filter = Q(**kw)
15 if filters:
16 filters = filters | filter
17 else:
18 -> filters = filter
19 # return qs
20 return User.objects.filter(filters)
21
22 def search_user(request):
23 """ List users matching query. """
(Pdb) c
[24/May/2009 05:55:55] "GET /test/?query=n0uk HTTP/1.1" 200 34

Мы вошли в функцию search при выполнении используя команду s (step).
Таким образом мы можем полностью скакать по нашему коду, и коду Django, и смотреть, "где же эта чертова ошибка, которую мы ищем уже вторые сутки".
Документация к модулю pdb
Также для отладки может быть полезен Django-debug-toolbar, про который можно почитать тут.
Читать дальше.

вторник, 12 мая 2009 г.

Последний редактор в admin

Хочу привести пример, как вывести последнего редактора объекта в админке.
Все действия над объектами в django.contrib.admin логируются моделью django.contrib.admin.models.LogEntry, поэтому нам достаточно просто найти последнее действие для выбранного объекта, и получить из него автора.
Пишем:

from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType

def last_editor(obj):
""" Return last editor for object """
ct = ContentType.objects.get_for_model(obj)
pk = obj.pk
l = LogEntry.objects.filter(content_type=ct, object_id=pk).order_by('-action_time')
if l.count():
l = l[0]
return '%s %s - %s' % (l.user.first_name or l.user.username,
l.user.last_name,
l.action_time.strftime('%d %h at %H:%M'))
else:
return _('Nobody or UFO')
last_editor.short_description = _('Last editor')

Подключаем в list_display необходимого ModelAdmin:

class PostAdmin(admin.ModelAdmin):
list_display = ('title', last_editor)

Открываем список постов в админке:

Только важно понимать, что редактирование объектов извне или изнутри приложения — не логируется, только через административный интерфейс. Читать дальше.

Новое в транке Django 1.1

В Django 1.1 Beta 1 попало достаточно интересных вещей, одна из них — это действия над группой элементов в django.contrib.admin рассмотренная в прошлой записи.
Сейчас я хотел бы рассмотреть другие интересные изменения:

  • Новые методы QuerySetdefer, only
  • Редактируемые поля в списке объектов django.contrib.admin
  • "Неуправляемые" модели
  • Прокси модели

defer и only

По умолчанию ORM в Django загружает из базы данных при запросе, все поля объекта. В случае когда преобразование записи базы данных в объект Python занимает долгое время, или полей очень много, или какое-либо из полей содержит большое количество данных — это невыгодно.
Теперь мы можем сказать ORM выбирать только определенные поля из таблицы, с рассчетом на то, что другие не будут использованы.
Приведу пример, допустим у нас есть модель Book
from django.db import models
from django.contrib.auth.models import User

class Book(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length=255, db_index=True)
content = models.TextField()
def __unicode__(self): return self.title

В поле title — хранится название книги, а в content — текст книги целиком. Если нам необходимо вывести весь список книг, мы сделаем запрос:
books = Book.objects.all()
На что джанго ответит таким запросом в БД:
SELECT "testapp_book"."id", "testapp_book"."author_id", "testapp_book"."title", "testapp_book"."content" FROM "testapp_book" LIMIT 21
Как видим, content — в списке выбираемых полей. Для нас это критично производительностью. А теперь поступим по другому и используем defer:
books = Book.objects.all().defer('content')
И увидим следующий запрос:
SELECT "testapp_book"."id", "testapp_book"."author_id", "testapp_book"."title" FROM "testapp_book" LIMIT 21
Ну вот, жизнь налаживается. Если теперь мы обратимся к полю content экземпляра модели Book:
print books[0].content
То джанго спросит у БД следующее:
SELECT "testapp_book"."content" FROM "testapp_book" WHERE "testapp_book"."id" = 1
Как видим, defer — это отложенная загрузка полей. Чтобы отключить ее для объекта QuerySet достаточно вызвать метод defer с аргументом None. Чтобы отложить загрузку нескольких полей, достаточно перечислить их как аргументы для defer:
Book.objects.defer('author_id', 'content')
Теперь об only. Only — это метод QuerySet, который работает похожим образом с defer, только наоборот. С помощью only мы можем перечислить поля которые нам необходимо получить, а все остальные выбраны из базы не будут. Например если нам необходим только title книги и логин ее создателя, то запрос к ORM будет следующим:
books = Book.objects.select_related().only('title', 'author__username')

Редактируемые поля в админке

Следующее новшество — это возможность редактирования полей объекта, на странице списка всех объектов. Вот как это выглядит:



Вот как это включается в ModelAdmin:
class BookAdmin(admin.ModelAdmin):
def author_name(obj):
return '%s' % obj.author.username
author_name.short_description = 'Author username'
list_display = ['id', 'title', author_name]
list_editable = ['title']
Ключевая строка:
list_editable = ['title']
Собственно она и говорит о том какие поля будут редактируемыми. Есть небольшой нюанс. По умолчанию первое выводимое поле в таблицы — есть ссылка на объект. Поэтому если мы делаем первое поле редактируемым, мы обязательно должны указать в display_links — какие поля будут ссылками, иначе Django скажет нам что-то вроде этого:
'BookAdmin.list_editable[0]' refers to 'title' which is not defined in 'list_display'.

Неуправляемые модели

В Meta-опциях модели, появилась новая опция managed, по-умолчанию равная True. Эта опция включает или исключает модель из поля зрения syncdb. Это полезно, когда у нас уже есть таблица в базе данных, и мы просто создаем для нее модель. В таком случае syncdb при запуске, не будет трогать существующую таблицу, и пытаться создать новую для модели.

Прокси модели

Прокси модели — это модели, которые не имеют реального отображения в базе данных. Они имеют одну и ту же таблицу в БД, как и проксируемая модель. Для чего это нужно? Допустим нам необходимо, чтобы модель User имела ordering по-умолчанию по емэйлу автора, и некоторые дополнительные функции. Вот так будет выглядеть наша прокси модель:
from django.db import models
from django.contrib.auth.models import User

class MyUser(User):

def say(self, message):
print '%s say %s' % (self.username, message)
class Meta:
proxy = True
ordering = ['email']
И вот так мы ее можем использовать:
>>> u = MyUser.objects.get(username='n0uk')
>>> u.say('hello')
n0uk say hello

Интересно? Читаем документацию:

Читать дальше.

понедельник, 11 мая 2009 г.

Действия над группой объектов в django.contrib.admin

Не так давно, в джанго-админке появились Admin Actions (http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/#ref-contrib-admin-actions). О них я и хотел бы сегодня рассказать. В процессе записи такие действия я буду называть AdminAction.

Что это, и зачем оно мне нужно?

На самом деле необходимость такой вещи назрела уже давно. Допустим я хочу удалить все объекты определенной модели из админки (представим, что у меня их ~100 штук), не имея доступа к базе данных. Задача превращается в довольно муторную, и во всех нормальных продуктах давно уже можно пару раз жмакнуть по галочке "Выделить все" и удалить все выделенное за следующие пару кликов. Поэтому выкручивались джангисты как могли. А теперь все просто.

Смотрим как это просто

AdminAction это обычная функция, которая принимает три аргумента.
  1. Экземпляр ModelAdmin, к которому прикручено действие.

  2. HttpRequest объект, пришедший от пользователя.

  3. QuerySet выделенных объектов.
Представим, что у нас есть приложение testapp. В нем есть модель Post
class Post(models.Model):
""" Post model. """

title = models.CharField(max_length=25)
content = models.TextField()
is_draft = models.BooleanField(default=True)

def __unicode__(self): return self.title

Boolean-поле is_draft сигнализирует о том, что запись черновик, и публиковать ее смысла нет. Сделаем свой AdminAction, который будет публиковать выделенные посты, т.е. ставить у постов is_draft=False.

Открываем admin.py и приводим его к такому виду:
from django.contrib import admin
from testapp.models import Post

def make_published(modeladmin, request, queryset):
""" Make all posts published. """
queryset.update(is_draft=False)
make_published.short_description = "Mark selected posts as published"

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = [make_published]

admin.site.register(Post, PostAdmin)

Что мы тут видим. Видим функцию make_published которая как-раз принимает те три аргумента о которых я писал выше. В PostAdmin видим строчку actions = [make_published]. Эта строчка подключает наш AdminAction к PostAdmin. Внутри функции make_published мы просто вызываем метод update у QuerySet и устанавливаем для всех выделенных объектов is_draft=False. Вот и все. Запускаем приложение и смотрим как это работает:



Логичнее было-бы, плодить новые функции внутри класса PostAdmin, и не выносить их наружу, т.к. больше они нигде не используются. Это возможно, просто перепишем наш пример следующим образом:
from django.contrib import admin
from testapp.models import Post

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = ['make_published']

def make_published(self, request, queryset):
""" Make all posts published. """
queryset.update(is_draft=False)
make_published.short_description = "Mark selected posts as published"

admin.site.register(Post, PostAdmin)

Что изменилось? Ну во-первых функция make_published перекочевала в PostAdmin. Во-вторых вместо первого аргумента теперь self. И в третьих в actions она указана строкой. Запускаем, и проверяем, что все работает.

Необходимые вещи

Логично было бы показывать пользователю, сколько постов реально было опубликовано при действии. Для этого мы воспользуемся методом ModelAdmin.message_user(request, message).

Пишем:
from django.contrib import admin
from testapp.models import Post

class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'is_draft')
actions = ['make_published']

def make_published(self, request, queryset):
""" Make all posts published. """
count = queryset.filter(is_draft=True).update(is_draft=False)
self.message_user(request, '%s posts mark as published.' % count)
make_published.short_description = "Mark selected posts as published"

admin.site.register(Post, PostAdmin)

Проверяем:




Отлично. Все работает как нужно.

Интересные возможности

Помимо рассмотренного выше, есть и другие возможности, для гибкой настройки административного интерфейса.

AdminAction может возвращать объект типа HttpResponse, который будет отдан напрямую пользователю. Используя это мы можем делать экспорт моделей, сериализацию выбранных объектов в JSON, предпросмотр нескольких объектов, и вообще, все что придет нам в голову.

Включаем, выключаем

Мы рассмотрели возможность подключения AdminAction к определенному ModelAdmin. Но как могли заметить, стандартное действие удаляющее выделенные объекты, никуда при этом не делось. А что, если нам не нравится стандартный AdminAction, который позволяет удалять выделенные объекты? Это не проблема - AdminAction's можно активировать и деактивировать для всего сайта в целом. Например, чтобы выключить тот самый delete_selected, мы должны написать в admin.py:
admin.site.disable_action('delete_selected')
После этого, мы можем включить действие delete_selected там, где оно нужно, просто указав в списке actions у определенного ModelAdmin. А чтобы активировать наше действие, например - make_all_good для всего сайта в целом, достаточно написать:
admin.site.add_action(make_all_good, 'make_good_selected')

Второй аргумент - не обязателен. Просто используя его мы сможем потом также отключить наше действие, как выше отключили delete_selected.

Чтобы выключить все действия для определенной ModelAdmin, достаточно присвоить action=None, внутри класса.

А если мы захотим иметь разный набор действий в зависимости от каких-либо условий, например от группы пользователя, то нам достаточно переопределить метод ModelAdmin.get_actions(self, request). Который возвращает список actions.

На мой взгляд - меганужная и ожидаемая фича.
Читать дальше.

Настройка окружения и развертка. Часть третья — lighttpd + mod_fastcgi + Flup

В данный момент самыми распространенными способами запуска django приложений, являются Apache + mod_python, и связка lightppd + mod_fastcgi + flup. Второй способ ест меньше ресурсов и в целом рекомендуется для маленьких и средних сайтов (сравнение lighttpd, apache). О нем я и буду рассказывать.

Для начала нам необходимы следующие вещи:
Если у Вас Debian-based операционная система, то lighttpd должен быть в репозитариях, чтобы его установить достаточно ввести команду:
sudo aptitude install lightppd
Flup можно установить используя easy_install
sudo easy_install flup
Дальше — пишем конфиг для lighttpd (приведен пример для Debian, возможно в Вашей системе, будут некоторые отличия):
# Load needed modules
server.modules = (
"mod_access",
"mod_alias",
"mod_accesslog",
"mod_compress",
"mod_rewrite",
"mod_fastcgi",
)

server.document-root = "/var/www/" # Ваш DocumentRoot

## Директория для загружаемых файлов, очищается каждый день.
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )

## Где будем хранить лог ошибок
server.errorlog = "/var/log/lighttpd/error.log"

#### Куда будем писать access логи
accesslog.filename = "/var/log/lighttpd/access.log"

# Указываем, какие у нас могут быть не статические файлы
static-file.exclude-extensions = (".fcgi" )

## Биндимся на порт 8080
server.port = 8080

## Храним PID процесса в этом файле (используется для различных RC скриптов)
server.pid-file = "/var/run/lighttpd.pid"

#### Настройки compress модуля
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ("text/plain", "text/html", "application/x-javascript", "text/css")

# Подключаем определения для mime-типов.
include_shell "/usr/share/lighttpd/create-mime.assign.pl"

# Настройки FastCGI

fastcgi.server = (
"/myproject.fcgi" => (
"main" => (
"socket" => "/myproject.sock",
"check-local" => "disable",
)
),
)
alias.url = (
# подключаем admin-media по location -- /media/
"/media" => "/usr/local/lib/python2.6/dist-packages/django/contrib/admin/media/",
)

# делаем реврайт урлов
url.rewrite-once = (
"^(/media.*)$" => "$1",
"^/favicon\.ico$" => "/media/favicon.ico",
"^(/.*)$" => "/myproject.fcgi$1",
)

Отлично, сохраняем это все в lighttpd.conf, и запускаем lighttpd
lighttpd -f lighttpd.conf
Заходим по адресу http://localhost:8080/ и видим 500-ую ошибку. Дальше нам необходимо запустить связку django + flup.

Перед тем, как продолжить хочу сказать, что иногда у меня получались кривые урлы в админке, привязанные к /myproject.fcgi. Чтобы их точно не было, откроем settings.py проекта, и впишем туда следующую опцию:
FORCE_SCRIPT_NAME = ''
Теперь все готово. Запускаем:
python manage.py runfcgi daemonize=false socket=/myproject.sock method=threaded
Заходим по адресу http://localhost:8080/ и любуемся. Если до сих пор видим ошибку с кодом 500, проверяем, имеет ли lighttpd право на запись в /myproject.sock.

Все возможные параметры к команде runfcgi мы можем узнать набрав:
./manage.py help runfcgi
Вот так все просто.
Читать дальше.

Настройка окружения и развертка. Часть вторая — Fabric

В первой части, был описан процесс создания виртуального окружения для нашего django-проекта. В этой части я хотел бы рассмотреть Fabric (http://www.nongnu.org/fab/) — программу для удаленного деплоймента.
Для того, чтобы начать нам нужен сам Fabric, установим его скачав с сайта (http://ftp.twaren.net/Unix/NonGNU/fab/), либо воспользуемся easy_install

sudo easy_install fabric

Если все прошло хорошо, можно продолжать.

Наш первый fabfile


Fabric — это утилита, которая логинится на удаленные сервера по ssh, умеет скачивать и загружать файлы. Работа с ней состоит из двух частей. Первая часть — сама программа fab. Вторая — fabfile, который говорит Fabric о том, что мы хотим сделать.

Создадим наш первый fabfile, который выведет в консоль, знакомую всему миру приветственную фразу:
# ./fabfile
def hello():
""" Print hello world to localhost. """
local('echo "Hello world!"')

Мы только что описали функцию hello. Функции fabfile — это команды. Т.е. мы создали команду hello для Fabric. Запустим ее:
$ fab hello
Fabric v. 0.1.1.
Running hello...
[localhost] run: echo "Hello world!"
Hello world!
Done.

Все прошло отлично. Теперь поподробнее. Как я уже сказал, функции внутри fabfile это команды. Запуск команд производится как:

fab <имя_команды>

Функция local — используемая внутри нашей команды hello, запускает переданный ей аргумент на локальной системе. Т.е. практически это аналог os.system().

Справка о командах


Если мы введем команду fab list — мы увидим список всех доступных команд для fabfile в текущей директории. Если введем fab help:hello — увидим справку по команде hello. Все очень просто. Можете посмотреть вывод fab help и сориентируетесь сами.

Подключаемся


Теперь продолжим, и попробуем заставить Fabric выполнить команду на удаленном сервере. Пусть это будет тот же вывод приветственного сообщения.
Изменяем наш fabfile до такого состояния:
config.fab_hosts = ['127.0.0.1']

def hello():
""" Print hello world! """
local('echo "Hello world!"')

def hello_remote():
""" Print Hello world! on remote system. """
run('echo "Hello world! from $(fab_host) to $(fab_user)"')

Запускаем:
$ fab hello_remote
Fabric v. 0.1.1.
Running hello_remote...
Logging into the following hosts as n0uk:
127.0.0.1
Password for n0uk@127.0.0.1:
[127.0.0.1] run: echo "Hello world! from 127.0.0.1 to n0uk"
[127.0.0.1] out: Hello world! from 127.0.0.1 to n0uk
Done.

Краткое описание примера:

  • config.fab_hosts — список удаленных хостов, на которых нам нужно выполнить команду.

  • hello_remote — наша новая команда, выводит приветственное сообщение на удаленном сервере.

  • run — функция, запускающая команду на удаленном сервере (почти как local)

  • $(fab_host) и $(fab_user) — переменные Fabric. Вместо них соответственно будут выведены config.fab_host, и config.fab_user. В config мы можем определять также свои переменные. Альтернативный синтаксис для вывода переменных — это %(variable_name)s.


После запуска Fabric входит по SSH на удаленные сервера перечисленные в config.fab_hosts, и просит ввести Ваш пароль. После входа, выполняет необходимые нам команды. По умолчанию вход по SSH производится под логином текущего пользователя, чтобы изменить это поведение, необходимо определить перменную config.fab_user.

Кроме local, run есть еще команды put, sudo, и другие, полный список которых можно найти в документации к Fabric. Команда put — выполняет загрузку файлов на удаленный хост. Синтаксис put(source_filename, target_filename).

Практически у всех функций есть дополнительный ключевой аргумент fail. Он может быть равен одному из 'abort', 'ignore', 'warn', и говорит Fabric о том, что необходимо сделать, если выполнение команды завершилось неудачно.

Двигаемся дальше


Попробуем себе представить, что у нас есть два сервера. На первый разработчики выкладывают разрабатываемую версию продукта/приложения. А на втором крутится стабильная версия, так называемый production. Было бы неплохо используя Fabric, заставить его выполнять одни и те-же команды, на разных хостах, но не на всех сразу, а выборочно. Посмотрим как это реализуется, приводим наш fabfile к следующему виду ('localhost' — наш development сервер, а 'production' — соответственно рабочий):
def dev():
config.fab_hosts = ['localhost']

def production():
config.fab_hosts = ['production']

def deploy():
'Deploy the app to the target environment'
require('fab_hosts', provided_by = [dev, production])
run('echo "Deploy project on $(fab_host)"')
# make deployment

Обратите внимание, что теперь config.fab_hosts мы определяем не в глобальном пространстве, а его устанавливают команды dev и production. Это и есть основная идея того, что мы делаем. Т.е. теперь при запуске последовательно двух команд dev и deploy, мы запустим команду deploy на development сервере. Проверим это:
$ fab deploy
Fabric v. 0.1.1.
Running deploy...
The 'deploy' command requires a 'fab_hosts' variable.
Get the variable by running one of these commands:
dev
production
$ fab dev deploy
Fabric v. 0.1.1.
Running dev...
Running deploy...
Logging into the following hosts as n0uk:
localhost
Password for n0uk@localhost:
[localhost] run: echo "Deploy project on localhost"
[localhost] out: Deploy project on localhost
Done.
$ fab production deploy
Fabric v. 0.1.1.
Running production...
Running deploy...
Logging into the following hosts as n0uk:
production
Password for n0uk@production:
[127.0.0.2] run: echo "Deploy project on production"
[127.0.0.2] out: Deploy project on production
Done.

Как видите, выполняя сразу по две команды, мы можем "выбирать" на каком сервере выполнить необходимую команду. Обратили внимание на вывод программы при запуске без указания сервера для развертки? Самая первая. Так вот, в ней Fabric нам любезно указал, что переменная config.fab_hosts не определена, а очень нужна, и определяется в командах dev или production. Это поведение — результат работы функции require. В которой мы затребовали config.fab_hosts и сказали, где его искать.

Еще глубже


Как видим, Fabric — очень гибкий, и развивающийся инструмент. Использовать при развертке его можно как с самописными .sh скриптами, можно совместно с Maven, или чем-нибудь вроде Make. Вот тут находится похожий текст на английском — http://www.nongnu.org/fab/user_guide.html, а здесь — http://www.nongnu.org/fab/api.html, описание API.

Удачного деплоймента.
Читать дальше.