31 июля 2010 г.

musicmans.ru | Как сделать сайт на Django | Пользователи

Наконец-то мы добрались до самого сладкого. Начнем с приложения users. Мы помним, что в django, как и в python надо писать приложения, а не проекты, чтобы соблюдать принцип DRY. Это основное приложение, которое требуется почти в каждом проекте. Несмотря на то, что в django уже есть django.contrib.auth, класс models.User содержит только минимальный набор полей. Расширение полей этого класса существует в следующих вариантах.
Создадим и переключимся в ветку users.
Перед тем, как создавать приложение, создадим темплейт для сайта - base.html и поместим его в директорию /src/templates. Для удобства редактирования темплейтов рекомендую Django Editor - plugin for Eclipse.

В нем прописаны некоторые темплейты для тегов django (вызываются по ctrl+space). Для редактирования html, просто открываем файл в html редакторе aptana.

Для изменения названия сайта (он по умолчанию создается при первом syncdb) создадим файл в корне src - install.py:
  1. # -*- mode: python; coding: utf-8; -*-  
  2. from django.core.management import setup_environ  
  3. try:  
  4.     import settings.development as settings  
  5. except ImportError:  
  6.     import settings.production as settings  
  7. setup_environ(settings)  
  8.   
  9. from django.contrib.sites.models import Site  
  10. s = Site.objects.get(pk=1)  
  11. s.domain = "musicmans.ru"  
  12. s.name = "Меломаны"  
  13. s.save()  

и запустим выполнение (правой кнопкой на файле - Run As - Python Run). Пропишем SITE_ID=1 в настройках.

Для того, чтобы переменные настроек 'STATIC_URL', 'DEBUG' (и другие в будущем), а также имя и домен сайта были доступны в шаблонах (я их использую в base.html) напишем свои контекстные процессоры для темплейтов. Для этого создадим пакет питона (new - pydev package) в /src/, назовем, например, apps.djutils. В нем мы будем собирать все дополнительную функциональность проекта, которая может пригодиться и в будущем.

Создадим модуль питона (new - pydev module) в этом пакете под названием context_processors, следующим содержимым:
  1. from django.contrib.sites.models import Site, RequestSite  
  2.   
  3. def current_site(request):  
  4.     try:  
  5.         current_site = Site.objects.get_current()  
  6.     except Site.DoesNotExist:  
  7.         current_site = RequestSite(request)  
  8.       
  9.     return {  
  10.             'SITE_NAME': current_site.name,  
  11.             'SITE_DOMAIN': current_site.domain,  
  12.             }  
  13.       
  14. def settings_processor(*settings_list):  
  15.     def _processor(request):  
  16.         from django.conf import settings  
  17.         settings_dict = {}  
  18.         for setting_name in settings_list:  
  19.             settings_dict[setting_name] = getattr(settings, setting_name)  
  20.         return settings_dict  
  21.     return _processor  
  22.   
  23. dj_settings = settings_processor(  
  24.    'STATIC_URL''DEBUG'  
  25. )  

(Не пугайтесь, Site.objects.get_current() кешируется)
В /settings/common.py добавим:
  1. TEMPLATE_CONTEXT_PROCESSORS = (  
  2.                                "django.contrib.auth.context_processors.auth",  
  3.                                "django.core.context_processors.debug",  
  4.                                "django.core.context_processors.i18n",  
  5.                                "django.core.context_processors.media",  
  6.                                "django.contrib.messages.context_processors.messages",  
  7.                                "djutils.context_processors.dj_settings",  
  8.                                "djutils.context_processors.current_site",  
  9.                                )  

Теперь в любом темплейте, использующим RequestContext, мы получаем значение вышеуказанных переменных.

Вернемся к шаблону base.html. Код шаблона приводить не буду из-за размеров. Его примерное содержание можно подсмотреть здесь. К нему простенький css. Для того, чтобы css отдавался как статика при разработке на встроенном веб-сервере django, пропишем в urls.py:
  1. from django.conf import settings  
  2. if settings.DEBUG:  
  3.     urlpatterns += patterns('',  
  4.         (r'^media/(?P<path>.*)$''django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),  
  5.     )  
  6. </path>  

а в общие настройки пропишем ADMIN_MEDIA_PREFIX="admin", ибо media по умолчанию занята ADMIN_MEDIA_PREFIX и в случае, если мы ее не переопределим, наша статика работать не будет.

Для сжатия css, а также для перезагрузки закешированного браузером css файла в случае его обновления установим приложение django-compressor:
  1. $ sudo pip install BeautifulSoup  
  2. $ sudo apt-get install git-core  
  3. $ sudo pip install git+git://github.com/mintchaos/django_compressor@9b6966260398ff2dbdd11275e083e028e73c7af8#egg=django_compressor  

(Чтобы установить последнюю версию из репозитория удалите @9b6966260398ff2dbdd11275e083e028e73c7af8 , на данный момент это как раз последний коммит.)
Добавим в requirements.txt
BeautifulSoup==3.1.0.1
git+git://github.com/mintchaos/django_compressor@9b6966260398ff2dbdd11275e083e028e73c7af8#egg=django_compressor
и в приложения в settings - compressor.
Добавим в настройки:
  1. COMPRESS = True  
  2. COMPRESS_URL = STATIC_URL  
  3. COMPRESS_ROOT = STATIC_ROOT  
  4. COMPRESS_CSS_FILTERS = [  
  5.      'compressor.filters.cssmin.CSSMinFilter'  
  6. ]  
  7. COMPRESS_JS_FILTERS = [  
  8.      'compressor.filters.jsmin.JSMinFilter'  
  9. ]  

В случае отсутствия переменной COMPRESS в настройках проекта - приложением используется переменная DEBUG, поэтому, если вы хотите отключить сжатие на время разработки, просто закомментируйте COMPRESS.
Используем встроенные в приложения фильтры. Также можно использовать фильтры от yahoo или google.
После того как приложение создаст каталог CACHE, добавим его в svn:ignore.
Теперь попробуем использовать этот шаблон. Для начала отключим MAINTENANCE_MODE.
Исправим файл url.py
  1. from django.conf.urls.defaults import *  
  2. from views import home_page  
  3.   
  4. urlpatterns = patterns('',  
  5.             url(r'^$', home_page, name="home"),  
  6. )  

Создадим файл /src/view.py для проекта:
  1. # -*- mode: python; coding: utf-8; -*-  
  2. from annoying.decorators import render_to  
  3.  
  4. @render_to('homepage.html')  
  5. def home_page(request):  
  6.      
  7.     return {}  

В этом виде используется декоратор функции @render_to. Он поставляется с приложением django-annoying. Установим, добавим в requirements.txt, просмотрим список возможностей (AutoOneToOne field кстати нам пригодится в приложении users).
homepage.html пока содержит следующие вещи:
  1. {% extends "base.html" %}  
  2. {% load i18n %}  
  3. {% block title %}{% trans "Главная страница" %}{% endblock %}  

При создании темплейтов сразу закладываем возможность будущей интернационализации.
Для работы с html кодом используем firebug и HTML VALIDATOR. Также я использовал тег {% spaceless %} в base.html, чтобы сжать выдаваемый html.

Итак, вернемся к users. Приложение users будет хранить дополнительные поля профилей, и использовать сторонние приложения для регистрации и авторизации по open id. Создадим приложение:



Переместим его в apps и создадим модель, например, такую:
  1. # -*- coding:utf-8 -*-  
  2. from django.db import models  
  3. from django.contrib.auth.models import User  
  4.   
  5. from django.utils.translation import ugettext_lazy as _  
  6.   
  7. from annoying.fields import AutoOneToOneField#@UnresolvedImport  
  8.   
  9. GENDER_CHOICES = (  
  10.     ('M''Мужской'),  
  11.     ('F''Женский'),  
  12. )  
  13.   
  14. class UserProfile(models.Model):  
  15.     user = AutoOneToOneField(User, related_name='user_profile', primary_key=True)  
  16.     date_birth = models.DateField(verbose_name=_(u'Дата Рождения'), blank=True, null=True)  
  17.     gender = models.CharField(verbose_name=_(u'Пол'), max_length=1, choices=GENDER_CHOICES, blank=True, null=True)  
  18.     URL = models.URLField(max_length=150, verbose_name=_(u'Ваш сайт'), blank=True, null=True, verify_exists=False)  
  19.     ICQ = models.CharField(max_length=30, verbose_name=u'ICQ', blank=True, null=True)  
  20.     skype = models.CharField(max_length=100, verbose_name=u'skype', blank=True, null=True)  
  21.     jabber = models.CharField(max_length=100, verbose_name=u'jabber', blank=True, null=True)  
  22.     mobile = models.CharField(max_length=100, verbose_name=_(u'Мобильный телефон'), blank=True, null=True)  
  23.     about = models.TextField(verbose_name=_(u'О себе'), help_text=_(u'Несколько слов о себе.'), blank=True, null=True)  
  24.       
  25.     count_login = models.IntegerField(default=0)  
  26.       
  27.     last_activity_ip = models.IPAddressField(null=True)  
  28.     last_activity_date = models.DateTimeField(null=True)  
  29.       
  30.     class Meta:  
  31.         verbose_name = _(u'Профиль пользователя')  
  32.         verbose_name_plural = _(u'Профили пользователей')  

Добавим в installed apps 'users'. Создаем первоначальную миграцию для приложения users (вызов custom command manage.py, см. изображение выше):


Можно ознакомиться с содержимым users/migrations. Для создания таблицы, вместо syncdb запускаем migrate users. После изменения модели запускаем schemamigration users --auto и снова migrate users для изменения базы.

Итак, профили у нас есть, приступим к регистрации.
$pip install hg+http://bitbucket.org/ubernostrum/django-registration@d36a38202ee3#egg=django-registration
обновляем hg+http://bitbucket.org/ubernostrum/django-registration@d36a38202ee3#egg=django-registration в requirements.txt.
Читаем документацию (быстрый старт).
Добавляем registration в приложения. Добавляем в настройки ACCOUNT_ACTIVATION_DAYS = 3.

Необходимые темплейты для приложения:
**registration/registration_form.html**
**registration/registration_complete.html**
**registration/activate.html**
**registration/activation_complete.html**
**registration/activation_email_subject.txt**
**registration/activation_email.txt**
Вот здесь можно посмотреть пример темплейта (на другое смотреть не надо, сам механизм работы приложения существенно изменился). Создадим их в директории users/templates/users/ .
Хотел перенести все темплейты туда, не вышло, темплейт e-mail'а, отсылаемого при регистрации прописан жестко, а также жестко они прописаны в тестах django-registration (структура ниже).



Теперь добавим в urls.py сайта
  1. urlpatterns = patterns('',  
  2.             url(r'^$', home_page, name="home"),  
  3.             (r'^users/auth/', include('registration.backends.default.urls')),  

Пробуем зайти по адресу http://localhost:8000/users/auth/register/ .

Идея такова: для простой регистрации и регистрации по openid создадим свои backend'ы.

Для начала напишем backend для простой регистрации. Наследуем класс дефолтного бекенда в наше приложение users, копируем urls.py и правим маски url'ов в файле urls.py сайта и бекенда.
__init__.py бекенда:
  1. from registration.backends.default import DefaultBackend#@UnresolvedImport  
  2.   
  3. from users.forms import DJRegistrationForm#@UnresolvedImport  
  4.   
  5. class DjBackend(DefaultBackend):  
  6.   
  7.     def get_form_class(self, request):  
  8.         """ 
  9.         Return the default form class used for user registration. 
  10.         """  
  11.         return DJRegistrationForm  

Код формы регистрации см. ниже.

Все работает но не все устраивает. Для начала мне не нравится длина input. Переопределим аттрибуты виджета, переопределением форм. Создадим файл forms.py в приложении users (код ниже).

Далее. Так как приложение использует отправку почты по smtp нам на данном этапе неплохо было бы его отслеживать. Можно запустить тестовый smtp сервер python (python -m smtpd -n -c DebuggingServer localhost:1025), но я предлагаю пойти другим путем.
Существует замечательное приложение django-mailer, которое собирает почту в базе, а отправляет по крону. Это нам гарантирует доставку почты, а также избавляет от ошибок при отсутствии доступа к smtp серверу. Его мы добавим в общие настройки.
Итак, устанавливаем.
$pip install git+git://github.com/jtauber/django-mailer@eb236b23a597753a0662290bc3b2666882515791#eggs=django-mailer
requirements.txt не забываем.
А теперь используем новую возможность django-1.2 - EMAIL_BACKENDS. Пропишем в настройках:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Добавляем в INSTALLED_APPS, синхронизируем базу.

Пробуем регистрироваться и ищем сериализованный объект сообщения в базе.
Если все работает, добавляем EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' в settings/development.py и наблюдаем тело письма в консоли eclipse.

Не забудем поменять LOGIN_URL и LOGIN_REDIRECT_URL:
LOGIN_URL = "/users/auth/login/"
LOGIN_REDIRECT_URL = "/"

Перейдем к редактированию профиля.

Для редактирования профиля нам потребуется inline formsets.
views.py для users с одной фукнцией для самостоятельного написания:
  1. @login_required  
  2. @render_to('users/edit_profile.html')  
  3. def edit_profile(request):  

urls.py также самый обычный, самостоятельно.
Так как рендериг формсета и других форм по умолчанию нас не устраивает, создадим два подключаемых темплейта в директории djutils/templates/forms_render:
formset_table.html
  1. {{ formset.management_form }}  
  2. {% for form in formset.forms %}  
  3. {% include "forms_render/form_table.html" %}  
  4. {% endfor %}  

form_table.html
  1. table  
  2. {% for field in form %}  
  3.     {% if not field.is_hidden %}  
  4.     <span class="arial-bold-90">{{ field.label_tag }}</span>{{ field }}{{ field.errors }}  
  5.     <div class="hint">{{ field.help_text }}</div>  
  6.     {% else %}  
  7.     {{ field }}  
  8.     {% endif %}      
  9. {% endfor %}  
  10. /table  

table пришлось оставить без кавычек, иначе blogspot выводит нечто непонятное.
Соответственно,
  1. {% include "forms_render/formset_table.html" %}  

в темплейте users/edit_profile.html .

forms.py для приложения users получился такой:
  1. # -*- coding: utf-8 -*-  
  2. from django.forms import ModelForm  
  3. from django import forms  
  4.   
  5. from django.utils.translation import ugettext_lazy as _  
  6.   
  7. from django.contrib.auth import forms as auth_forms  
  8. from django.contrib.auth.models import User  
  9.   
  10. from users.models import UserProfile#@UnresolvedImport  
  11.   
  12. from registration.forms import RegistrationFormUniqueEmail#@UnresolvedImport  
  13.     
  14. class DJRegistrationForm(RegistrationFormUniqueEmail):  
  15.     def __init__(self, *args, **kwargs):  
  16.         super(DJRegistrationForm, self).__init__(*args, **kwargs)  
  17.         self.fields['username'].widget.attrs["size"] = 65  
  18.         self.fields['email'].widget.attrs["size"] = 65  
  19.         self.fields['password1'].widget.attrs["size"] = 65  
  20.         self.fields['password2'].widget.attrs["size"] = 65  
  21.   
  22.   
  23. class AuthForm(auth_forms.AuthenticationForm):  
  24.     def __init__(self, *args, **kwargs):  
  25.         super(AuthForm, self).__init__(*args, **kwargs)  
  26.         self.fields['username'].widget.attrs["size"] = 65  
  27.         self.fields['password'].widget.attrs["size"] = 65  
  28.   
  29. class PassResetForm(auth_forms.PasswordResetForm):  
  30.     def __init__(self, *args, **kwargs):  
  31.         super(PassResetForm, self).__init__(*args, **kwargs)  
  32.         self.fields['email'].widget.attrs["size"] = 65  
  33.   
  34. class EditProfileForm(ModelForm):  
  35.       
  36.     date_birth = forms.DateField(('%d.%m.%Y',), label=_('Дата рождения'), required=False,    
  37.         widget = forms.DateInput(format='%d.%m.%Y', attrs={  
  38.             'class':'input',  
  39.             'size':'65'  
  40.         })  
  41.     )  
  42.       
  43.     def __init__(self, *args, **kwargs):  
  44.         super(EditProfileForm, self).__init__(*args, **kwargs)  
  45.         self.fields['ICQ'].widget.attrs["size"] = 65  
  46.         self.fields['URL'].widget.attrs["size"] = 65  
  47.         self.fields['jabber'].widget.attrs["size"] = 65  
  48.         self.fields['mobile'].widget.attrs["size"] = 65  
  49.         self.fields['skype'].widget.attrs["size"] = 65  
  50.         self.fields['about'].widget.attrs["cols"] = 49  
  51.         self.fields['about'].widget.attrs["rows"] = 8  
  52.       
  53.     class Meta:  
  54.         model = UserProfile  
  55.         fields = ['gender''date_birth''ICQ''URL''jabber''mobile''skype''about' ]  
  56.           
  57.         #не работает http://code.djangoproject.com/ticket/13095  
  58.         #widgets = {  
  59.         #            'date_birth': forms.DateInput(format="%d.%m.%Y"),   
  60.         #           }  

Для проверки выключаем бекенд вывода писем в консоль, добавляем конфигурацию smtp сервера:
  1. EMAIL_HOST='smtp.server.ru'  
  2. EMAIL_HOST_USER='musicmans.ru'  
  3. EMAIL_HOST_PASSWORD='password'  
  4. DEFAULT_FROM_EMAIL='musicmans.ru@server.ru'  
  5. SERVER_EMAIL='musicmans.ru@server.ru'  

Регистрируемся, выполняем команду django-mailer - manage.py send_mail. Проверяем почту.

Создадим crontab для сервера в develop и можно сразу прописать на сервере (отправка почты (раз в пять минут), повторная отправка (раз в двадцать минут), удаление неактивных пользователей (раз в сутки); будем добавлять вручную, ибо не так часто требуется):
  1. */5 *   * * *   vermus  (/usr/bin/python /srv/musicmans/root/src/manage.py send_mail >> /srv/musicmans/logs/cron_mail.log 2>&1)  
  2. 0,20,40 *       * * *   vermus  (/usr/bin/python /srv/musicmans/root/src/manage.py retry_deferred >> /srv/musicmans/logs/cron_mail_deferred.log 2>&1)  
  3. 0   0   * * *   vermus  (/usr/bin/python /srv/musicmans/root/src/manage.py cleanupregistration  >> /srv/musicmans/logs/cleanupregistration .log 2>&1)  

Запускаем тесты, и если все ок, переключаемся в trunk и мержим ветку users, закрываем все задачи в redmine и
$fab production deploy
(можно в него дописать запуск install.py)

Кстати в fabfile.py закрались ошибочки,
if "y" == prompt('Install the necessary applications (y/n)?', default="n"):
install_requirements();
надо выполнять после svn update, а svn update для production.py не будет обновлять maintenance_mode, так как для svn файл уже обновлен, также рестартовать необходимо и uwsgi, смотрим обновленный fabfile.py.

Ну и как обычно, результат смотрим http://musicmans.ru/.



Аутентификация через OpenId и написание тестов для нашего приложения в следующей статье.

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

19 июля 2010 г.

musicmans.ru | Как сделать сайт на Django | Развертывание

Я обещал выкладывать все этапы работы на http://musicmans.ru, поэтому настала пора вывесить табличку "Сайт в разработке" :), заодно наладив работу развертывания.

Итак, задачи: создать приложение по вводу сайта в режим обслуживания, настроить сервер, автоматизировать процесс развертки на сервер с помощью fabric.

Вспомним о том, что у нас есть redmine и mylyn, создадим данные задачи (не забываем создать категории задач в настройках проекта в redmine).

django-maintenancemode

Для ввода сайта в режим обслуживания есть целое приложение.

Устанавливаем:
  1. C:\>c:\Python26\Scripts\pip.exe install django-maintenancemode  
  2. Downloading/unpacking django-maintenancemode  
  3.   Downloading django-maintenancemode-0.9.2.tar.gz  
  4.   Running setup.py egg_info for package django-maintenancemode  
  5. Installing collected packages: django-maintenancemode  
  6.   Running setup.py install for django-maintenancemode  
  7. Successfully installed django-maintenancemode  
  8. Cleaning up...  

Прописываем в requirements.txt:

django-maintenancemode==0.9.2

Настраиваем. В MIDDLEWARE_CLASSES добавляем "maintenancemode.middleware.MaintenanceModeMiddleware".



В templates создаем файл 503.html со статическим содержимым того, что будет выводиться в период обслуживания сайта.

Функции приложения:
* MAINTENANCE_MODE - включает\выключает режим обслуживания, по умолчанию: False.
* Страница 503 не отображается залогиненым админам и клиентам с ip адресами, входящими в INTERNAL_IPS.

Итак, пропишем MAINTENANCE_MODE = True, в development.py и в production.py (в development.py закомментируем вскоре).

Запускаем pydev сервер, отладку, переходим на страницу и видим следующее:



Немного поправим 503.html по своему желанию.

Настройка сервера

Устанавливаем и настраиваем фаерволл:
  1. $ sudo aptitude install ufw  
  2. $ sudo ufw enable  
  3. $ sudo ufw logging on  
  4. $ sudo ufw allow 80/tcp  
  5. $ sudo ufw allow SSH_port  
  6. $ sudo ufw default deny  

Настройку веб сервера выбрал такую (nginx + uwsgi). Тем более, nginx, начиная с версии 0.8.40 поддерживает uwsgi из коробки.
  1. # apt-get install gcc libssl-dev libpcre++-dev make  
  2. # wget http://sysoev.ru/nginx/nginx-0.8.44.tar.gz  
  3. # tar -xzvf nginx-0.8.44.tar.gz  
  4. # cd nginx-0.8.44/  
  5. # ./configure --conf-path=/etc/nginx/nginx.conf \  
  6.   --prefix=/usr \  
  7.   --error-log-path=/var/log/nginx/error.log \  
  8.   --pid-path=/var/run/nginx.pid \  
  9.   --lock-path=/var/lock/nginx.lock \  
  10.   --http-log-path=/var/log/nginx/access.log \  
  11.   --with-http_dav_module \  
  12.   --http-client-body-temp-path=/var/lib/nginx/body \  
  13.   --with-http_ssl_module \  
  14.   --http-proxy-temp-path=/var/lib/nginx/proxy \  
  15.   --with-http_stub_status_module \  
  16.   --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \  
  17.   --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \  
  18.   --http-scgi-temp-path=/var/lib/nginx/scgi \  
  19.   --with-debug \  
  20.   --with-http_flv_module   
  21. # make  
  22. # make install  

Скрипт запуска /etc/init.d/nginx я взял из стандартного пакета debian (устанавливать его не нужно, ибо можно перетереть новые конфиги старыми. В принципе, не страшно, так как мы будем их писать заново, но например mime.types могут отличаться).

Создаем рабочую директорию для сайта, например /srv/musicmans
Структура:
  1. /srv/musicmans   
  2. |  backups  
  3. --|  src  
  4. --|  db  
  5. |  logs  
  6. |  root  
  7. --|  src  
  8. --|  www  

На машине разработчика создаем файл в src wsgi.py (основой файл запуска проекта для веб-сервера):

  1. import os  
  2. import sys  
  3. import locale  
  4. import django.core.handlers.wsgi  
  5.   
  6. DIR=(os.path.abspath(__file__))  
  7. sys.path.append(DIR)  
  8. os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.production'  
  9.   
  10. def force_utf8_hack():  
  11.   reload(sys)  
  12.   sys.setdefaultencoding('utf-8')  
  13.   for attr in dir(locale):  
  14.     if attr[0:3] != 'LC_':  
  15.       continue  
  16.     aref = getattr(locale, attr)  
  17.     locale.setlocale(aref, '')  
  18.     (lang, enc) = locale.getlocale(aref)  
  19.     if lang != None:  
  20.       try:  
  21.         locale.setlocale(aref, (lang, 'UTF-8'))  
  22.       except:  
  23.         os.environ[attr] = lang + '.UTF-8'  
  24.   
  25. force_utf8_hack()  
  26.   
  27.   
  28. application = django.core.handlers.wsgi.WSGIHandler()  

Перед тем, как настраивать сервер, запустим тестирование проекта.



Введем команду test и получим ошибку.

Добавим в development.py:
  1. INSTALLED_APPS += (  
  2.                    'django.contrib.admin',  
  3.                    )  

А также в manage.py:
  1. if settings.DEBUG and command == "test":  
  2.     settings.MAINTENANCE_MODE = False  
  3.   
  4. execute_manager(settings)  

Ибо нам тесты в режиме обслуживания не нужны, да и не отрабатывают они, у меня вышла ошибка отсутствия темплейта 503.html и куча других.

Сделаем коммит.

Вернемся к серверу с сайтом. Сделаем предварительную настройку:

1. Перейдем в директорию /srv/musicmans/ и заберем транк в root:

$export SVN_SSH="ssh -l loginname"

или сделаем пару ключа (она нам все рано пригодиться при использовании fabric). На сервере с сайтом:
  1. $ ssh-keygen -t dsa  
  2. $ cat ~/.ssh/id_dsa.pub  

копируем вывод, добавляем на сервер с кодом в ~/.ssh/authorized_keys2 на сервер (если файла нет, то touch ~/.ssh/authorized_keys2 && chmod 600 ~/.ssh/authorized_keys2 ). Пробуем логиниться без пароля.
  1. $svn checkout --depth=empty svn+ssh://codesrv/repos/musicmans/trunk/backend root  
  2. $cd root/  
  3. $svn update --set-depth=infinity www  
  4. $svn update --set-depth=infinity src  

Так как нам нужны только две директории src и www, то делаем пустой checkout, после чего обновляем две директории с бесконечной вложенностью. После этого svn update будет нам обновлять только директории www и src.

Устанавливаем необходимые приложения для сайта:
  1. vermus@musicmans:~$ cd /srv/musicmans/root/src  
  2. vermus@musicmans:~$ sudo pip install -r requirements.txt --download-cache /usr/src/pipcache/  

Установка postgresql:
  1. # apt-get install postgresql python-psycopg2  
  2. # su postgres  
  3. $ createuser musicmans --no-superuser --no-createdb --no-createrole --login --pwprompt --encrypted  
  4. $ createdb --owner=musicmans --encoding=utf-8 musicmans  

База создана, пробуем синхронизировать django с базой данных (мы это делали уже на машине разработчика, но так как у нас база на сайте будет жить своей жизнью, а девелоперская своей, то сделаем это еще раз, т.е. миграцию данных выполнять не будем):
  1. vermus@musicmans:~$ cd /srv/musicmans/root/src/  
  2. vermus@musicmans:/srv/musicmans/root/src$ python manage.py syncdb  

Итак, все в порядке. Осталось настроить веб-сервер. Конфигурацию мы уже выбрали.

Установим uwsgi сервер:
  1. $ cd /usr/src/  
  2. $ sudo pip install http://projects.unbit.it/downloads/uwsgi-latest.tar.gz  

Настроим скрипт init.d для запуска через файловый сокет сервера uwsgi с проектом (/etc/init.d/uwsgi):
  1. # cat uwsgi  
  2. ### BEGIN INIT INFO  
  3. # Provides:          uwsgi  
  4. # Required-Start:    $all  
  5. # Required-Stop:     $all  
  6. # Default-Start:     2 3 4 5  
  7. # Default-Stop:      0 1 6  
  8. # Short-Description: starts the uwsgi app server  
  9. # Description:       starts uwsgi app server using start-stop-daemon  
  10. ### END INIT INFO  
  11.   
  12. PATH=/sbin:/bin:/usr/sbin:/usr/bin  
  13. DAEMON=/usr/bin/uwsgi  
  14.   
  15. OWNER=uwsgirun  
  16.   
  17. NAME=uwsgi  
  18. DESC=uwsgi  
  19.   
  20. test -x $DAEMON || exit 0  
  21.   
  22. # Include uwsgi defaults if available  
  23. if [ -f /etc/uwsgi ] ; then  
  24.         . /etc/uwsgi  
  25. fi  
  26.   
  27. set -e  
  28.   
  29. DAEMON_OPTS="--socket /var/lib/nginx/uwsgi/musicmans.sock --chmod-socket -d /srv/musicmans/logs/uwsgi.log --pythonpath $PYTHONPATH --module $MODULE"  
  30.   
  31. case "$1" in  
  32.   start)  
  33.         echo -n "Starting $DESC: "  
  34.         start-stop-daemon --start --chuid $OWNER:$OWNER --user $OWNER \  
  35.                 --exec $DAEMON -- $DAEMON_OPTS  
  36.         echo "$NAME."  
  37.         ;;  
  38.   stop)  
  39.         echo -n "Stopping $DESC: "  
  40.         start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \  
  41.                 --exec $DAEMON  
  42.         echo "$NAME."  
  43.         ;;  
  44.   reload)  
  45.         killall -1 $DAEMON  
  46.         ;;  
  47.   force-reload)  
  48.         killall -15 $DAEMON  
  49.        ;;  
  50.   restart)  
  51.         echo -n "Restarting $DESC: "  
  52.         start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \  
  53.                 --exec $DAEMON  
  54.         sleep 1  
  55.         start-stop-daemon --user $OWNER --start --quiet --chuid $OWNER:$OWNER \  
  56.                --exec $DAEMON -- $DAEMON_OPTS  
  57.         echo "$NAME."  
  58.         ;;  
  59.   status)  
  60.         killall -10 $DAEMON  
  61.         ;;  
  62.       *)  
  63.             N=/etc/init.d/$NAME  
  64.             echo "Usage: $N {start|stop|restart|reload|force-reload|status}" >&2  
  65.             exit 1  
  66.             ;;  
  67.     esac  
  68.     exit 0  

Не забываем создать пользователя uwsgirun, под которым будет запускаться uwsgi. Параметр chmod-socket устанавливает права 666 на сокет, если Вас это не устраивает смотрите документацию. Если uwsgi после запуска ругается на права, проверьте права на директорию с сокетом, на директорию с логами.
Создадим файл конфигурации /etc/uwsgi :
  1. PYTHONPATH=/srv/musicmans/root/src  
  2. MODULE=wsgi  

Обратите внимание, что мы указываем имя модуля python, а не имя файла.
Устанавливаем chmod 755 для скрипта /etc/init.d/uwsgi , загружаем при старте системы:
  1. root@musicmans:/var/lib/nginx# chown -R uwsgirun uwsgi  
  2. root@musicmans:/etc/init.d# chmod 755 uwsgi  
  3. root@musicmans:/etc/init.d# update-rc.d -f uwsgi defaults  
  4. root@musicmans:/etc/init.d# /etc/init.d/uwsgi start  

Конфиги nginx: nginx.conf, стандартный из пакета debian. Конфиг сайта:
  1. root@musicmans:/etc/nginx/sites-available# cat musicmans  
  2. #serving Django.  
  3. upstream django {  
  4.     ip_hash;  
  5.     server unix:/var/lib/nginx/uwsgi/musicmans.sock;  
  6.   }  
  7.   
  8. server {  
  9.      listen      80;  
  10.      server_name musicmans.ru;  
  11.      charset     utf-8;  
  12.      error_log   /srv/musicmans/logs/nginx_error.log   info;  
  13.      access_log /srv/musicmans/logs/nginx_access.log;  
  14.   
  15.      # Django admin media.  
  16.      #location /media/admin/ {  
  17.      #               alias lib/python2.6/site-packages/django/contrib/admin/media/;  
  18.      #               }  
  19.   
  20.      # Your project's static media.  
  21.      location /media/ {  
  22.                   alias /srv/musicmans/root/www/media/;  
  23.                          }  
  24.   
  25.      # Finally, send all non-media requests to the Django server.  
  26.      location / {  
  27.                  uwsgi_pass  django;  
  28.                  include     uwsgi_params;  
  29.                  }  
  30.   
  31.      location ~ /.svn/ {  
  32.            deny all;  
  33.                }  
  34.   
  35.        }  

Включаем сайт
  1. # ln -s /etc/nginx/sites-available/musicmans /etc/nginx/sites-enabled/musicmans  

Перезапускаем /etc/init.d/uwsgi restart и /etc/init.d/nginx restart.

Заходим http://musicmans.ru/:



Процесс развертывания кода и структуры базы данных на сервер с помощью fabric

Установим на машину разработчика pip и fabric.
  1. #pip install fabric  

Создаем fab файл с командами fabric в корне проекта для установки необходимых приложений из requirements.txt, обновления кода, миграции базы данных и перезапуска Nginx:

* Включить режим обслуживания сайта (см. выше).
* Сделать резервную копию базы данных.
* Сделать резервную копию кода (src) сайта.
* Обновить код с репозитория subversion.
* Запустить миграцию базы данных (South).
* Выключить режим обслуживания сайта.

У нас fabric 0.9.1, а в 1.0 обещают поддержку django. Ну а пока ее нет создаем fabfile.py в корне проекта следующего содержания (перевод windows консоли для понимания удаленного UTF8 в случае ошибок - шрифт cmd окна Lucida Console (или любой другой true type), далее команда chcp 65001).

  1. # -*- mode: python; coding: utf-8; -*-  
  2. import sys  
  3. from fabric.api import env, run, prompt, local, get, cd, sudo, require  
  4. from fabric.state import output  
  5. from fabric.contrib.files import uncomment  
  6. import datetime  
  7.   
  8. now = datetime.datetime.now()  
  9.   
  10. def production():  
  11.     #здесь данные об удаленном сервере с сайтом  
  12.     env.environment = "production"  
  13.       
  14.     env.hosts = ['codesrv']  
  15.     env.user = 'vermus'  
  16.     env.path = '/srv/musicmans/root'  
  17.     env.root_path = '/srv/musicmans'  
  18.       
  19.     env.db_name = 'musicmans'  
  20.     env.db_user = 'musicmans'  
  21.       
  22. def deploy():  
  23.     """ 
  24.     In the current version fabfile no initial database creation and configure the virtual server host. 
  25.     """   
  26.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  27.       
  28.     if env.environment == 'production':  
  29.         if "y" != prompt('Are you sure you want to update the production site (test & check in trunk release code!)? (y/[n])?', default="n"):  
  30.             return  
  31.       
  32.     if "y" == prompt('Set MAINTENANCE_MODE (y/n)?', default="y"):  
  33.         maintenance_mode()   
  34.       
  35.     if "y" == prompt('Create database backup? (y/n)?', default="y"):  
  36.         backup_db()  
  37.           
  38.     if "y" == prompt('Create source code backup? (y/n)?', default="y"):  
  39.         backup_src()  
  40.   
  41.     update_from_svn()  
  42.   
  43.     if "y" == prompt('Install the necessary applications (y/n)?', default="n"):  
  44.         install_requirements();  
  45.   
  46.     migrate_database()  
  47.   
  48.     maintenance_mode(set=False)      
  49.   
  50.     restart_webserver()          
  51.   
  52. def install_requirements():  
  53.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  54.     print(" * install the necessary applications...")  
  55.       
  56.     requirements_file = env.path+'/src/requirements.txt'  
  57.       
  58.     args = ['install',  
  59.                 '-r', requirements_file,  
  60.                 '--download-cache''/usr/src/pipcache/'  
  61.                 ]  
  62.       
  63.     sudo('pip %s' % ' '.join(args))  
  64.   
  65. def maintenance_mode(set=True):  
  66.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  67.     print(" * change production.py and restart nginx...")  
  68.     if set:  
  69.         uncomment(env.path+'/src/settings/production.py''MAINTENANCE_MODE = True')  
  70.     else:  
  71.         comment(env.path+'/src/settings/production.py''MAINTENANCE_MODE = True')  
  72.           
  73.     restart_webserver()   
  74.   
  75. def backup_db():  
  76.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  77.     print(" * create database dump...")  
  78.       
  79.     db_name = env.db_name  
  80.     db_user = env.db_user  
  81.   
  82.     backup_file = "backup_%d_%d_%d_%d_%d.sqlgzip" % (now.day, now.month, now.year, now.hour, now.minute)  
  83.     backup_dir = env.root_path+'/backups/db'  
  84.     with cd(backup_dir):  
  85.         run("echo dbpassword | pg_dump -W -U %s -F c %s > %s" % (db_user, db_name, backup_file))  
  86.   
  87. def backup_src():  
  88.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  89.     print(" * create source code backup...")  
  90.     backup_dir = env.root_path+'/backups/src'  
  91.     backup_file = "backup_%d_%d_%d_%d_%d.tar.gz" % (now.day, now.month, now.year, now.hour, now.minute)  
  92.     src_dir = env.path+'/src'  
  93.       
  94.     run("mkdir -p %s" % backup_dir+'/all')  
  95.     run("cp -f -R %s %s" % (src_dir, backup_dir+'/all'))  
  96.     run("cp -f -R %s %s" % (env.path+'/www/static', backup_dir+'/all'))  
  97.       
  98.     with cd(backup_dir):  
  99.         run ('tar -zcf %s %s' % (backup_file, backup_dir+'/all'))  
  100.         run ('rm -f -R %s' % (backup_dir+'/all'))  
  101.           
  102.          
  103. def update_from_svn():  
  104.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  105.     with cd(env.path):  
  106.         run('svn update'#svn checkout сделаем вручную первый раз  
  107.   
  108. def migrate_database():  
  109.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  110.     with cd(env.path+'/src'):  
  111.         run('python manage.py migrate --no-initial-data')  
  112.         run('python manage.py syncdb')  
  113.           
  114. def restart_webserver():  
  115.     require('environment', provided_by=[production])#дописать по желанию dev и stage  
  116.     print(" * restart nginx")  
  117.     sudo('/etc/init.d/uwsgi restart', pty=True)  
  118.     sudo('/etc/init.d/nginx force-reload', pty=True)  

Схема такая:
Запуск $fab production deploy с машины разработчика - логин по ssh на сервер с сайтом (fabric выполняет автоматически при выполнении run, sudo и др., используя данные из env), далее выполняются необходимые действия, в том числе svn+ssh с сервера с кодом с транка.

Добавим в /etc/postgresql/8.4/main/pg_hba.conf следующую строчку:
  1. # "local" is for Unix domain socket connections only  
  2. local   musicmans   musicmans                         md5  
  3. local   all         all                               ident  

Обратите внимание, что строчку добавляем перед ident. Она позволит нам соединяться пользователю без логина с указанием пароля при беакпе базы.

Статья получилась объемной, старался быстрее закончить с технической стороной и перейти наконец к созиданию. :)

Не забываем про задачи (перспектива planning), указываем время, потраченное на задачи и закрываем их.

ps. Если вы хотите сразу обеспечить полноценную защиту сайта от различных атак, которая требует более тонкой настройки системы, то следует обратиться к профессионалам или почитать соответствующую литературу. Нам пока некому завидовать, поэтому оставляем все как есть на данном этапе.

4 июля 2010 г.

musicmans.ru | Как сделать сайт на Django | Миграция моделей и данных

В прошлый раз мы остановились на том, что запустили django на машине разработчика. Использование virtualenv оставим на самостоятельное рассмотрение. А вот pip мы все-таки установим. Тем более, что у нас в проекте уже лежит пустой файл requirements.txt - файл формата pip, со списком приложений, необходимых для нашего проекта. Кстати, пора в него уже прописать:
Django==1.2.1
South==0.7.2

Далее, качаем pip, разархивируем, устанавливаем:
#apt-get install python-setuptools
pip-0.7.2#python setup.py install

Теперь перейдем к теме.

South привносит в django возможность миграции структуры и данных модели. На практике это означает, что если мы что-то поменяли в модели (добавили/удалили поле) то south сам увидит изменения и создаст инструкции для внесения изменений в БД, которые останется только применить на всех экземплярах приложения.

Основные особенности, которые отмечают разработчики:

* отслеживание изменений в модели и создание миграций
* независимость от движков БД (заявлена поддержка 5 разных типов БД)
* создание миграций только для выбранного приложения (application)
* сообщение о возможных конфликтах при комите миграций от других разработчиков

Далее:

Устанавливаем South
>C:\Python26\Scripts\pip.exe install South
или
#pip install South

Вы уже наверное заметили, что у меня две машины разработчика. Одна под windows, другая Kubuntu, поэтому в будущем буду выводить консольные команды той системы, в которой работаю, поскольку они аналогичны.

Добавляем south в INSTALLED_APPS и делаем syncdb (пробуем из gui).



Так как по умолчанию в INSTALLED_APPS прописано несколько стандартных приложений, в том числе django.contrib.auth, поэтому в консоли выполним создание суперпользователя сайта (первый запуск syncdb).

South применяется отдельно для каждого приложения в проекте, а так как у нас нет приложений, то пока настройку South можно считать законченной (подробнее по вышеприведенным ссылкам или в документации). Плюс, мы еще вернемся к обсуждению South позже.

3 июля 2010 г.

musicmans.ru | Как сделать сайт на Django | Cтруктура проекта

Структура проекта может быть по вашему вкусу. Тут нет каких-то жестких правил, хотя правила в создании django приложений (не проекта) все-таки есть. Итак, приведем в пример структуру pinax сайта проекта:
  1. apps  
  2. deploy  
  3. media  
  4. site_media  
  5. templates  
  6. tests  
  7.   
  8. __init__.py  
  9. context_processors.py  
  10. manage.py  
  11. urls.py  
  12. settings.py  

Мы подсмотрим у профессионалов, virtualenv мы использовать не будем, поэтому немного подсократим:

PROJECT_ROOT/
|-- apps/ # Django приложения сайта
|-- etcs/ # Разные конфигурационные файлы
|-- settings/ # Настройки
|-- templates/ # темплейты для всего сайта
|-- __init__.py # Инициализация пакета python
|-- requirements.txt #файл формата pip со списком приложений для сайта
|-- manage.py # файл запуска\управления проекта
`-- urls.py # Главный URLconf

Разделение setting для разработки и развертывания одна из постоянно обсуждаемых вещей в django мире. Начнем с того, что разделим общие настройки и настройки развертывания плюс добавим несколько своих директорий.

|-- PROJECT_ROOT/
| |-- apps/
| |-- etc/
| |-- settings/
| | |-- __init__.py
| | |-- common.py
| | |-- development.py
| | `-- production.py
| |-- templates/
| |-- __init__.py
| |-- manage.py
| `-- urls.py
|-- www/
| |-- media/
| | |-- static/
|-- develop/

Статику (изображения, css, яваскрипт), которую веб сервер будет отдавать напрямую, мы вынесем за пределы PROJECT_ROOT.

Общие настройки определим в модуле settings.common. В development и production будут находиться специфичные настройки, а также они будут импортровать common, а manage.py будет загружать development.py, а в случае его отсутствия (svn:ignore) - production.py.

У меня получилась следующая структура проекта:



Обратите внимание на директорию develop, там будут лежать вещи, которые относятся к разработке, например отправим туда файл конфигурации development.py, вдруг кому-нибудь придется развертывать проект для разработки, тогда он легко сможет взять файл оттуда. Также обратите внимание на файл develompent.py в самом проекте, декоратор иконок subversive говорит нам о том, что он находится в svn:ignore.

Итак, переместим settings.py в common.py.

Подредактируем файлы примерно следующим образом (вывожу только то, что изменилось):

common.py:
  1. # -*- mode: python; coding: utf-8; -*-  
  2. ''''' 
  3. Created on 02.07.2010 
  4. '''  
  5. import os  
  6. import sys  
  7.   
  8. ADMINS = (  
  9.     ('Vermus''admin@musicmans.ru'),  
  10. )  
  11.   
  12. MANAGERS = ADMINS  
  13.   
  14. TIME_ZONE = 'Europe/Moscow'  
  15.   
  16. LANGUAGE_CODE = 'ru-RU'  
  17.   
  18. #Определяем корень проекта  
  19. PROJECT_ROOT = os.path.normpath(os.path.dirname(os.path.dirname(__file__)))  
  20.   
  21. #Добавляем apps в системную переменную path  
  22. sys.path.insert(0, os.path.join(PROJECT_ROOT, 'apps'))  
  23.   
  24. #URL к медиа файлам  
  25. MEDIA_URL = '/media/'  
  26. #путь в системе к медиа файлам  
  27. MEDIA_ROOT = os.path.join(os.path.dirname(PROJECT_ROOT), os.path.join('www', MEDIA_URL.strip('/') ))  
  28.   
  29. #URL к статическим файлам  
  30. STATIC_URL = MEDIA_URL + 'static/'  
  31. #пусть в системе к статическим файлам  
  32. STATIC_ROOT = os.path.join(MEDIA_ROOT, 'static')   
  33.   
  34. ROOT_URLCONF = 'urls'  
  35.   
  36. #Админка нам не нужна, убираем  
  37. #ADMIN_MEDIA_PREFIX = '/media/'  
  38.   
  39. TEMPLATE_DIRS = (  
  40.     os.path.join(PROJECT_ROOT, 'templates'),  
  41. )  

development.py (svn:ignore):
  1. # -*- mode: python; coding: utf-8; -*-  
  2.   
  3. from common import *  
  4.   
  5. DEBUG = True  
  6. TEMPLATE_DEBUG = DEBUG  
  7.   
  8. DATABASES = {  
  9.     'default': {  
  10.         'ENGINE''django.db.backends.postgresql_psycopg2'# Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.  
  11.         'NAME''musicmans'# Or path to database file if using sqlite3.  
  12.         'USER''user'# Not used with sqlite3.  
  13.         'PASSWORD''pass'# Not used with sqlite3.  
  14.         'HOST''localhost'# Set to empty string for localhost. Not used with sqlite3.  
  15.         'PORT'''# Set to empty string for default. Not used with sqlite3.  
  16.     }  
  17. }  

production.py:
  1. # -*- mode: python; coding: utf-8; -*-  
  2.   
  3. from common import *  
  4.   
  5. DEBUG = False  
  6.   
  7. DATABASES = {  
  8.     'default': {  
  9.         'ENGINE''django.db.backends.postgresql_psycopg2'# Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.  
  10.         'NAME''musicmans'# Or path to database file if using sqlite3.  
  11.         'USER''user'# Not used with sqlite3.  
  12.         'PASSWORD''pass'# Not used with sqlite3.  
  13.         'HOST''localhost'# Set to empty string for localhost. Not used with sqlite3.  
  14.         'PORT'''# Set to empty string for default. Not used with sqlite3.  
  15.     }  
  16. }  

Итак, единственное, что пока отличает production от develompent, переменная DEBUG и настройки базы данных (для нашего случая можно установить один пароль, и они тоже не будут отличаться, можно переместить в common.py). Остальное мы постарались сделать независимым от платформы и месторасположения проекта в системе.

Отключим в eclipse Window -> Preferences => Pydev -> Editor -> Code Analysis unused wild import, иначе будем получать много предупреждений от Code Analysis (в связи с нашими import *).

Отредактируем manage.py (в его начале мы разбираемся с settings проекта, потом настраиваем eclipse для отладки django проекта):
  1. #!/usr/bin/env python  
  2. import sys  
  3.   
  4. from django.core.management import execute_manager  
  5.   
  6. try:  
  7.     import settings.development as settings  
  8. except ImportError:  
  9.     try:  
  10.         import settings.production as settings  
  11.     except:  
  12.         sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)  
  13.         sys.exit(1)  
  14.   
  15. if __name__ == "__main__":  
  16.     if len(sys.argv) > 1:  
  17.         command = sys.argv[1]  
  18.     if settings.DEBUG and (command == "runserver" or command == "testserver"):  
  19.         # Make pydev debugger works for auto reload.  
  20.         try:  
  21.             import pydevd  
  22.         except ImportError:  
  23.             sys.stderr.write("Error: " +  
  24.                 "You must add org.python.pydev.debug.pysrc to your PYTHONPATH.")  
  25.             sys.exit(1)  
  26.      
  27.         from django.utils import autoreload  
  28.         m = autoreload.main  
  29.         def main(main_func, args=None, kwargs=None):  
  30.             import os  
  31.             if os.environ.get("RUN_MAIN") == "true":  
  32.                 def pydevdDecorator(func):  
  33.                     def wrap(*args, **kws):  
  34.                         pydevd.settrace(suspend=False)  
  35.                         return func(*args, **kws)  
  36.                     return wrap  
  37.                 main_func = pydevdDecorator(main_func)  
  38.       
  39.             return m(main_func, args, kwargs)  
  40.       
  41.         autoreload.main = main  
  42.       
  43.     execute_manager(settings)  

Выполняем все действия по вышеприведенной ссылке, чтобы запустить отладку проекта. Кстати, в новом pydev появился конфигуратор отладчика для django:



Но он многого не дает, как я понял, единственное --no-reload прописан по умолчанию и в аргументах можно просто указать runserver.

Итак, запускаем отладку:



И заходим браузером по адресу http://127.0.0.1:8000/, и видим классику: