Публикации с меткой «musicmans.ru»

В чем я?!

musicmans.ru | Как сделать сайт на Django | Жанры, направления, стили

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

Начнем с жанров, направлений и стилей. Создадим приложение genre, сразу создаем модель жанра.
Чтобы создать модель, нам надо определиться, что такое жанр собственно? К сожалению, в русском интернете, большая путница и мешанина из жанров, стилей и направлений. Нет ни исследований, ни достаточно устоявшихся критериев. Прочитав эту заметку начал задумываться структуре систематизации жанров и стилей. И главное, неплохо было бы найти уже существующую отлаженную структуру (Amazon). После долгих поисков (amg, amazon, mp3.com, discogs) остановился на варианте от amg.

Итак, напишем модель жанра, направления и стиля и заполним их структурой.

Так как я один, и у меня нет редакторов, то пришлось воспользоваться результатом чужих трудов и спарсить структуру жанров, направлений и стилей с amg. Надеюсь они на меня за это не в обиде.

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

После того как данные в базе, сделаем initial data для приложения:

>python manage.py dumpdata genre > apps\genre\fixtures\initial_data.json

Теперь попробуем вывести дерево жанров \ стилей.

На данном этапе понимаем, что вывод "детей" стилей сулит нам лавинообразные запросы к базе данных (select_related нам тоже не поможет, он не срабатывает для приложений с полями отношения ForeignKey с null=True ( father = models.ForeignKey('self', verbose_name=_(u'Родитель'), related_name='child_dirs_styles', null=True) ) ), поэтому воспользуемся приложением для хранения деревьев в базе данных django-treebeard (документация).

C:\>c:\Python26\Scripts\pip.exe install git+git://github.com/tabo/django-treebeard@8eb52a4f4274615e86a7572a8bab39b79d718b88

Добавляем 'treebeard' в INSTALLED_APPS. Если вы используете админку, то настройка немного посложней.

Воспользуемся моделью хранения деревьев Nested Sets. Не стоит пугаться последней ссылки, тем то и хорошо приложение treebeard, что за нас уже решен вопрос хранения деревьев в SQL базе. Нам лишь стоит воспользоваться набором функций.

Смотрим нашу модель:

# -*- coding:utf-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from treebeard.ns_tree import NS_Node #@UnresolvedImport

GENRE_DIR_STYLE = (
(0, _('Музыка')),
(1, _('Жанр')),
(2, _('Направление')),
(3, _('Стиль')),
)

class GenreDirStyle(NS_Node):
name = models.CharField(max_length=1000, verbose_name=_(u'Title'))
name_ru = models.CharField(max_length=1000, verbose_name=_(u'Название'), blank=True, null=True)
type = models.IntegerField(choices=GENRE_DIR_STYLE)
description = models.CharField(max_length=10000, verbose_name=_(u'Описание'), blank=True)

class Meta:
ordering = ["name"]
verbose_name = _(u'Жанр, направление, стиль')


Пробуем, работает ли миграции South с treebeard:

./manage.py schemamigration genre --auto

получаем сообщение следующего вида

? The field 'GenreDirStyle.lft' does not have a default specified, yet is NOT NULL.






South нас просит указать обязательное значение по умолчанию. Нажимаем 2, и значение 0.

Как создать дерево? Просто:
1. Создаем корень дерева.
pop_music = GenreDirStyle.add_root(name = "Популярная музыка", type = 0)#оно сразу сохраняется save()
2. Создаем жанр:
pop_music.add_child(name = "Rock", type = 1)

И здесь сталкиваемся с проблемой, в случае парсинга (см. выше) html и создания базы "на лету". Поэтому читаем basic-usage внимательней и переписываем примерно так:

>>> get = lambda node_id: Category.objects.get(pk=node_id)
>>> root = Category.add_root(name='Computer Hardware')
>>> node = get(root.id).add_child(name='Memory')
>>> get(node.id).add_sibling(name='Hard Drives')

>>> get(node.id).add_sibling(name='SSD')

>>> get(node.id).add_child(name='Desktop Memory')

>>> get(node.id).add_child(name='Laptop Memory')

>>> get(node.id).add_child(name='Server Memory')


Не забудем обновить json данные, после изменения и миграций моделей.
Для вывода дерева жанров используем функцию get_tree.


# -*- coding: utf-8 -*-
from annoying.decorators import render_to#@UnresolvedImport
from django.db import transaction
from django.shortcuts import get_object_or_404

from models import GenreDirStyle

@render_to('genres/genre_tree.html')
def genre_tree(request):

pop_genre = GenreDirStyle.objects.get(name="Популярная музыка", type=0)
classic_genre = GenreDirStyle.objects.get(name="Классическая музыка", type=0)

pop_tree = GenreDirStyle.get_tree(pop_genre)
classic_tree = GenreDirStyle.get_tree(classic_genre)

return {
'pop_tree': pop_tree,
'classic_tree': classic_tree
}

@render_to('genres/genre_genre.html')
def genre_genre(request, genre_id):

genre=get_object_or_404(GenreDirStyle, id=genre_id)

return {
'genre': genre
}

На данном этапе django-toolbar показывал замечательные 5 запросов за 13 мс. А вот общая генерация страницы занимала 6573.00 ms. Это очень долго, хотя при выключенном debug режиме ощутимо быстрее. Все упирается в рендеринг. Проэтому применим кеш в темплейте (на шесть часов, например):

{% load cache %}
{% cache 21600 pop_tree_chache %}
{% for node in pop_tree %}
{% include "genres/genre_node.html" %}
{% endfor %}
{% endcache %}

А также включим на время (позже настроим memcache на сервере) кеширование в память, в settings/common.py:

CACHE_BACKEND = 'locmem:///'

Темплейты интуитивно понятны, покажу лишь темплейт жанра, включаемый в цикл вывода дерева.

{% load dj_tags %}


(Ужасно, blogger все сломал, смотрите здесь)
Обратите внимание на фильтр multiply и substract. Это не стандартные фильтры django, а написаные в нашем приложении dj_tags.

# -*- coding: utf-8 -*-
from django import template
register=template.Library()

@register.filter(name='multiply')
def multiply(value, arg):
return value * arg

@register.filter(name='subtract')
def subtract(value, arg):
return int(value) - int(arg)

Итак, мы познакомились с хранением деревьев в базе данных с django, их выводом, затронули кеширование, написали пару темплейт тегов.

Ну, окончание, как обычно, тесты, мерж, развертывание.




ps. Не забудем обновить django (>c:\Python26\Scripts\pip.exe install --upgrade Django и прописать в requirements.txt)!

В чем я?!

musicmans.ru | Как сделать сайт на Django | Пользователи. Личные сообщения. Уведомления

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

#pip install git+git://github.com/robhudson/django-debug-toolbar
Смело ставим последнюю версию, ибо если что-то сломается, то на машине разработчика это не критично.
development.py:

INSTALLED_APPS += (
'django.contrib.admin',#tests
'debug_toolbar',
)

MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)

INTERNAL_IPS = ('127.0.0.1',)


Обновляем localhost:


Поинтересуйтесь содержимым пунктов меню, там много интересного.

Итак, личные сообщения. Опять все написано за нас. Устанавливаем из транка (кстати, вот консольный svn клиент для windows):

>pip.exe install svn+http://django-messages.googlecode.com/svn/trunk/@141#egg=django_messages

141-я ревизия транка на данный момент, как написано на странице проекта это будущая версия 0.5, совместимая с Django 1.2 (как раз то, что нам нужно). Не забываем requirements.txt (кстати, я подумал, что неплохо было бы сначала добавлять строчку в requirements.txt, а ставить основываясь на файле. Надо только использовать одну директорию с кешом pip, чтобы он постоянно не скачивал дистрибутивы. Ставить установленные приложения по новой он не будет, но мы будем уверены, что не забудем прописать все приложения в requirements.txt и не испытаем проблем на сервере с сайтом).

Документация по настройке. Тут кстати возникает путаница, здесь приложение указано как django_messages, а в документации используется messages, так что используем django_messages.

Изменим темплейты.

Скопируем из приложения и изменим под свои нужды.



В документации к приложению читаем, что оно использует django-mailer и django-notification, если они установлены. Первое у нас есть, ставим второе (хорошее приложение, пригодится в будущем).
>pip.exe install git+git://github.com/jtauber/django-notification@3f023adf0ce2eafcee744904e2c358792f253721@egg=notification

Пропишем 'django_messages', 'notification' в приложения, настроим url.py. Синхронизируем базу.

Просмотрим таблицу notification_noticetype, в ней должны находиться оповещения о работе с личными сообщениями. Приложение notification рассмотрим ниже.

Как только мы видим список чего бы то ни было (список входящих сообщений), сразу рождаются мысли о пагинации. И как не удивительно :-) , django поддерживает ее из коробки, а чтобы было совсем просто установим приложение django-pagination.

>pip.exe install git+git://github.com/ericflo/django-pagination@47e7ec874cd7dddda5ed13ffb6993a64dced2537

Настраиваем. Добавляем css разметку.

А также переведем пару строк, так как в приложении нет русской локализации, djutils\locale\ru_RU\LC_MESSAGES\django.po:
msgid "previous"
msgstr "назад"

msgid "next"
msgstr "вперед"

Не забудем скомпилировать.

в темплейтах с сообщениями добавляем (с месторасположением самостоятельно, 2-ка для теста):

{% load pagination_tags %}
{% autopaginate message_list 2 %}
{% paginate %}


В итоге получается:


Чтобы пользователи не были у нас безликими, давайте организуем им вывод профайлов.
В user/url.conf

url(r'^profile/(?P\d+)/$', users_views.userprofile, name='users_profile'),

Вид:

@login_required
@render_to('users/user_profile.html')
def userprofile(request, userprofile_id):

request_user = get_object_or_404(User, id=int(userprofile_id), is_active=True)

return {
'request_user' : request_user,
}

Отмечу, что получать надо user, а не наш UserProfile, который еще может быть не создан.

Соответственно, напишем темплейт.



django_messages\templates\notification\ переведите вручную, перевод, который идет с приложением не работает (устарел наверное). Скопируем темплейты в users/templates, создадим папку locale в приложении и запустим создание файла перевода:
>python C:\Python26\Lib\site-packages\django\bin\django-admin.py makemessages -e .html,.txt --locale=ru_RU
в директории users.
После редактирования скомпилируем.

Темлейт для уведомлений notices.html для самостоятельного написания (можно подсмотреть в pinax).

Что такое notification? Это приложение для уведомлений. Когда происходит событие в системе, мы имеем возможность создать уведомление для пользователя, с возможностью настройки дополнительных рассылок уведомлений:

* при логине на сайт.
* по почте (настраивается пользователем).
* по rss

Подключим контекстный процессор "notification.context_processors.notification".

Мне не понравилась часть приложения по работе с url. Поэтому скопировал приложение себе в проект и поправил под свои нужды, получилось примерно следующее:



Пишите сообщения. :)

В чем я?!

musicmans.ru | Как сделать сайт на Django | Пользователи. Дополнительная аутентификация

В прошлый раз мы остановились на том, что начали создавать приложение users. Давайте создадим openid аутентификацию на сайте.
Я обещал писать бекенды для django-registration, но оказалось существует замечательное приложение для всевозможных видов аутентификации с последующим созданием пользователя и авторизации.

* OpenID - yandex.ru, rambler.ru, yahoo.ru, google.com
* OAuth - twitter.com
* OpenAPI - Вконтакте.ру
* FacebookConnect - facebook.com

Устанавливаем:
c:\Python26\Scripts\pip.exe install hg+http://bitbucket.org/offline/django-p
ublicauth@7371e8f71be1#egg=django-publicauth

Прописываем в requirements.txt.
Добавляем 'publicauth' в INSTALLED_APPS. Добавляем 'annoying.middlewares.RedirectMiddleware' в MIDDLEWARE_CLASSES.

Добавим 'publicauth.PublicBackend' в AUTHENTICATION_BACKENDS.

Запускаем syncdb.

OpenID

Необходимо установить python-openid (2.2.5).

Добавим
(r'', include('publicauth.urls')),
в файл /users/urls.py , тем самым переопределив url login и logout приложения нашими url'ами, и оставив остальные, необходимые для работы приложения. Ознакомимся с содержимым темплейта login.html, и добавим следующую форму в наш login.hetml:


Openid URL




Создадим директорию publicauth в users/templates/ , скопируем туда содержимое и поправим под наш сайт.
Также в файле users/forms.py создадим форму ExtraForm (форма для заполнения дополнительных полей после сторонней аутентификации, например имя пользователя).
Подсмотреть можно здесь.

PUBLICAUTH_EXTRA_FORM = "users.forms.ExtraForm"

Пробуем логиниться.

Приложение требует установленного и настроенного messages framework, как указано в документации (для самостоятельного рассмотрения) для вывода сообщений. У меня уже оно почти настроено (сообщение при сохранении профиля реализованно как раз через него).

Сообщения к сожалению не переведены, придется сделать это самим.

Создадим файл src/djutils/locale/ru_RU/LC_MESSAGES/django.po и переведем файл.

msgid "To complete registration, check your email and activate your account"
msgstr "Для завершения регистрации проверьте e-mail и активируйте учетную запись"

msgid "We are sorry, but registration is disabled. Come back later"
msgstr "Извините, но регистрация закрыта"

msgid "Please fill openid url field"
msgstr "Пожалуйста, заполните поле openid"

msgid "Your authentication provider returned bad response, please try again"
msgstr "Ваш провайдер аутентификации вернул влохой ответ, попробуйте еще раз"

msgid "You have cancelled OpenID authentication"
msgstr "Вы отменили аутентификацию по OpenID"

msgid "OpenID authentication failed. Reason: %s"
msgstr "Аутентификация OpenID провалилась. Причина: %s"

msgid "You have successfully logged out"
msgstr "Вы успешно вышли"

msgid "Your existing account was merged with new authentication account"
msgstr "Существующая учетная запись была объединена с новой учетной записью"

msgid "Your account is not activated. Please activate it first."
msgstr "Ваша учетная запись не активирована. Пожалуйста, активируйте ее сначала."

msgid "You have successfully authenticated"
msgstr "Вы успешно аутентифицированны"

msgid "Invalid response received from facebook server, please start the authentication process again"
msgstr "Неверный ответ от сервера facebook, пожалуйста запустите процесс аутентификации еще раз"

msgid "Invalid response received from OpenID server, please start the authentication process again"
msgstr "Неверный ответ от сервера OpenID, пожалуйста запустите процесс аутентификации еще раз"

msgid "Invalid response received from vkontakte server, please start the authentication process again"
msgstr "Неверный ответ от сервера vkontakte, пожалуйста запустите процесс аутентификации еще раз"

Скомпилируем (запуск в директории src\apps\djutils\)
>python C:\Python26\Lib\site-packages\django\bin\django-admin.py compilemessages.

добавим 'django.middleware.locale.LocaleMiddleware', в MIDDLEWARE_CLASSES.

ВКонтакте

Делаем все как по ссылке на хабр. Единственно, я не понял для чего нужно VKONTAKTE_API_KEY, работает и без него, да и в бекенде vkontakte.py он не используется.

Facebook

Делаем по мануалу, ссылка на документацию на facebook.

В настройки надо добавить
FACEBOOK_PROFILE_MAPPING={ 'name': 'username', }

OAuth (Twitter)

Install python-oauth:
>pip.exe install oauth

Настройки:

TWITTER_CONSUMER_KEY = "key"
TWITTER_CONSUMER_SECRET = "secret"
TWITTER_REQUEST_TOKEN_URL = "https://twitter.com/oauth/request_token"
TWITTER_ACCESS_TOKEN_URL = "https://twitter.com/oauth/access_token"
TWITTER_AUTHORIZE_URL = "https://twitter.com/oauth/authorize"
TWITTER_API_URL = "http://twitter.com/users/show.json?user_id=%s"
TWITTER_PROFILE_MAPPING = { 'screen_name': 'username', }

Темплейт:




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

Как обычно, запускаем тесты, мержим транк, делаем развертывание, и сравниваем musicmans.ru.



ps. По поводу тестов. Да все уже написано:
http://djangotesting.com/
http://habrahabr.ru/blogs/django/91471/
http://pyobject.ru/blog/2009/09/13/django-external-test-tools/

Разбираемся, пишем.

pps. Насткнулся на баг в тестах django-registration.
Вот решение (developmet.py)

TEST = False
manage_command = filter(lambda x: x.find('manage.py') != -1, sys.argv)
if len(manage_command) != 0:
command = sys.argv.index(manage_command[0]) + 1
if command < /> TEST = sys.argv[command] == "test"

if TEST:
LANGUAGE_CODE = 'en-us'

В чем я?!

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:

# -*- mode: python; coding: utf-8; -*-
from django.core.management import setup_environ
try:
import settings.development as settings
except ImportError:
import settings.production as settings
setup_environ(settings)

from django.contrib.sites.models import Site
s = Site.objects.get(pk=1)
s.domain = "musicmans.ru"
s.name = "Меломаны"
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, следующим содержимым:

from django.contrib.sites.models import Site, RequestSite

def current_site(request):
try:
current_site = Site.objects.get_current()
except Site.DoesNotExist:
current_site = RequestSite(request)

return {
'SITE_NAME': current_site.name,
'SITE_DOMAIN': current_site.domain,
}

def settings_processor(*settings_list):
def _processor(request):
from django.conf import settings
settings_dict = {}
for setting_name in settings_list:
settings_dict[setting_name] = getattr(settings, setting_name)
return settings_dict
return _processor

dj_settings = settings_processor(
'STATIC_URL', 'DEBUG'
)

(Не пугайтесь, Site.objects.get_current() кешируется)
В /settings/common.py добавим:

TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.contrib.messages.context_processors.messages",
"djutils.context_processors.dj_settings",
"djutils.context_processors.current_site",
)

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

Вернемся к шаблону base.html. Код шаблона приводить не буду из-за размеров. Его примерное содержание можно подсмотреть здесь. К нему простенький css. Для того, чтобы css отдавался как статика при разработке на встроенном веб-сервере django, пропишем в urls.py:

from django.conf import settings
if settings.DEBUG:
urlpatterns += patterns('',
(r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
)

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

Для сжатия css, а также для перезагрузки закешированного браузером css файла в случае его обновления установим приложение django-compressor:

$ sudo pip install BeautifulSoup
$ sudo apt-get install git-core
$ 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.
Добавим в настройки:

COMPRESS = True
COMPRESS_URL = STATIC_URL
COMPRESS_ROOT = STATIC_ROOT
COMPRESS_CSS_FILTERS = [
'compressor.filters.cssmin.CSSMinFilter'
]
COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.JSMinFilter'
]

В случае отсутствия переменной COMPRESS в настройках проекта - приложением используется переменная DEBUG, поэтому, если вы хотите отключить сжатие на время разработки, просто закомментируйте COMPRESS.
Используем встроенные в приложения фильтры. Также можно использовать фильтры от yahoo или google.
После того как приложение создаст каталог CACHE, добавим его в svn:ignore.
Теперь попробуем использовать этот шаблон. Для начала отключим MAINTENANCE_MODE.
Исправим файл url.py

from django.conf.urls.defaults import *
from views import home_page

urlpatterns = patterns('',
url(r'^$', home_page, name="home"),
)

Создадим файл /src/view.py для проекта:

# -*- mode: python; coding: utf-8; -*-
from annoying.decorators import render_to

@render_to('homepage.html')
def home_page(request):

return {}

В этом виде используется декоратор функции @render_to. Он поставляется с приложением django-annoying. Установим, добавим в requirements.txt, просмотрим список возможностей (AutoOneToOne field кстати нам пригодится в приложении users).
homepage.html пока содержит следующие вещи:

{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Главная страница" %}{% endblock %}

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

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



Переместим его в apps и создадим модель, например, такую:

# -*- coding:utf-8 -*-
from django.db import models
from django.contrib.auth.models import User

from django.utils.translation import ugettext_lazy as _

from annoying.fields import AutoOneToOneField#@UnresolvedImport

GENDER_CHOICES = (
('M', 'Мужской'),
('F', 'Женский'),
)

class UserProfile(models.Model):
user = AutoOneToOneField(User, related_name='user_profile', primary_key=True)
date_birth = models.DateField(verbose_name=_(u'Дата Рождения'), blank=True, null=True)
gender = models.CharField(verbose_name=_(u'Пол'), max_length=1, choices=GENDER_CHOICES, blank=True, null=True)
URL = models.URLField(max_length=150, verbose_name=_(u'Ваш сайт'), blank=True, null=True, verify_exists=False)
ICQ = models.CharField(max_length=30, verbose_name=u'ICQ', blank=True, null=True)
skype = models.CharField(max_length=100, verbose_name=u'skype', blank=True, null=True)
jabber = models.CharField(max_length=100, verbose_name=u'jabber', blank=True, null=True)
mobile = models.CharField(max_length=100, verbose_name=_(u'Мобильный телефон'), blank=True, null=True)
about = models.TextField(verbose_name=_(u'О себе'), help_text=_(u'Несколько слов о себе.'), blank=True, null=True)

count_login = models.IntegerField(default=0)

last_activity_ip = models.IPAddressField(null=True)
last_activity_date = models.DateTimeField(null=True)

class Meta:
verbose_name = _(u'Профиль пользователя')
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'а, отсылаемого при регистрации прописан жестко (структура ниже).



Теперь добавим в urls.py сайта

urlpatterns = patterns('',
url(r'^$', home_page, name="home"),
(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 бекенда:

from registration.backends.default import DefaultBackend#@UnresolvedImport

from users.forms import DJRegistrationForm#@UnresolvedImport

class DjBackend(DefaultBackend):

def get_form_class(self, request):
"""
Return the default form class used for user registration.
"""
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 с одной фукнцией для самостоятельного написания:

@login_required
@render_to('users/edit_profile.html')
def edit_profile(request):

urls.py также самый обычный, самостоятельно.
Так как рендериг формсета и других форм по умолчанию нас не устраивает, создадим два подключаемых темплейта в директории djutils/templates/forms_render:
formset_table.html

{{ formset.management_form }}
{% for form in formset.forms %}
{% include "forms_render/form_table.html" %}
{% endfor %}

form_table.html


{% for field in form %}
{% if not field.is_hidden %}

{% else %}

{% endif %}
{% endfor %}
{{ field.label_tag }}{{ field }}{{ field.errors }}
{{ field.help_text }}
{{ field }}


Соответственно,


{% include "forms_render/formset_table.html" %}


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

forms.py для приложения users получился такой:

# -*- coding: utf-8 -*-
from django.forms import ModelForm
from django import forms

from django.utils.translation import ugettext_lazy as _

from django.contrib.auth import forms as auth_forms
from django.contrib.auth.models import User

from users.models import UserProfile#@UnresolvedImport

from registration.forms import RegistrationFormUniqueEmail#@UnresolvedImport

class DJRegistrationForm(RegistrationFormUniqueEmail):
def __init__(self, *args, **kwargs):
super(DJRegistrationForm, self).__init__(*args, **kwargs)
self.fields['username'].widget.attrs["size"] = 65
self.fields['email'].widget.attrs["size"] = 65
self.fields['password1'].widget.attrs["size"] = 65
self.fields['password2'].widget.attrs["size"] = 65


class AuthForm(auth_forms.AuthenticationForm):
def __init__(self, *args, **kwargs):
super(AuthForm, self).__init__(*args, **kwargs)
self.fields['username'].widget.attrs["size"] = 65
self.fields['password'].widget.attrs["size"] = 65

class PassResetForm(auth_forms.PasswordResetForm):
def __init__(self, *args, **kwargs):
super(PassResetForm, self).__init__(*args, **kwargs)
self.fields['email'].widget.attrs["size"] = 65

class EditProfileForm(ModelForm):

date_birth = forms.DateField(('%d.%m.%Y',), label=_('Дата рождения'), required=False,
widget = forms.DateInput(format='%d.%m.%Y', attrs={
'class':'input',
'size':'65'
})
)

def __init__(self, *args, **kwargs):
super(EditProfileForm, self).__init__(*args, **kwargs)
self.fields['ICQ'].widget.attrs["size"] = 65
self.fields['URL'].widget.attrs["size"] = 65
self.fields['jabber'].widget.attrs["size"] = 65
self.fields['mobile'].widget.attrs["size"] = 65
self.fields['skype'].widget.attrs["size"] = 65
self.fields['about'].widget.attrs["cols"] = 49
self.fields['about'].widget.attrs["rows"] = 8

class Meta:
model = UserProfile
fields = ['gender', 'date_birth', 'ICQ', 'URL', 'jabber', 'mobile', 'skype', 'about' ]

#не работает http://code.djangoproject.com/ticket/13095
#widgets = {
# 'date_birth': forms.DateInput(format="%d.%m.%Y"),
# }

Для проверки выключаем вывод бекед вывода писем в консоль, добавляем конфигурацию smtp сервера:

EMAIL_HOST='smtp.server.ru'
EMAIL_HOST_USER='musicmans.ru'
EMAIL_HOST_PASSWORD='password'
DEFAULT_FROM_EMAIL='musicmans.ru@server.ru'
SERVER_EMAIL='musicmans.ru@server.ru'

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

Создадим crontab для сервера в develop и можно сразу прописать на сервере (отправка почты (раз в пять минут), повторная отправка (раз в двадцать минут), удаление неактивных пользователей (раз в сутки); будем добавлять вручную, ибо не так часто требуется):

*/5 * * * * vermus (/usr/bin/python /srv/musicmans/root/src/manage.py send_mail >> /srv/musicmans/logs/cron_mail.log 2>&1)
0,20,40 * * * * vermus (/usr/bin/python /srv/musicmans/root/src/manage.py retry_deferred >> /srv/musicmans/logs/cron_mail_deferred.log 2>&1)
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. Как сделать подсвечивающиеся меню расскажу отдельным постом, если кто заинтересуется.

В чем я?!

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

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

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

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

django-maintenancemode

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

Устанавливаем:

C:\>c:\Python26\Scripts\pip.exe install django-maintenancemode
Downloading/unpacking django-maintenancemode
Downloading django-maintenancemode-0.9.2.tar.gz
Running setup.py egg_info for package django-maintenancemode
Installing collected packages: django-maintenancemode
Running setup.py install for django-maintenancemode
Successfully installed django-maintenancemode
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 по своему желанию.

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

Устанавливаем и настраиваем фаерволл:

$ sudo aptitude install ufw
$ sudo ufw enable
$ sudo ufw logging on
$ sudo ufw allow 80/tcp
$ sudo ufw allow
$ sudo ufw default deny

Настройку веб сервера выбрал такую (nginx + uwsgi). Тем более, nginx, начиная с версии 0.8.40 поддерживает uwsgi из коробки.

# apt-get install gcc libssl-dev libpcre++-dev make
# wget http://sysoev.ru/nginx/nginx-0.8.44.tar.gz
# tar -xzvf nginx-0.8.44.tar.gz
# cd nginx-0.8.44/
# ./configure --conf-path=/etc/nginx/nginx.conf \
--prefix=/usr \
--error-log-path=/var/log/nginx/error.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--http-log-path=/var/log/nginx/access.log \
--with-http_dav_module \
--http-client-body-temp-path=/var/lib/nginx/body \
--with-http_ssl_module \
--http-proxy-temp-path=/var/lib/nginx/proxy \
--with-http_stub_status_module \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
--http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
--http-scgi-temp-path=/var/lib/nginx/scgi \
--with-debug \
--with-http_flv_module
# make
# make install

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

Создаем рабочую директорию для сайта, например /srv/musicmans
Структура:

/srv/musicmans
| backups
--| src
--| db
| logs
| root
--| src
--| www

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


import os
import sys
import django.core.handlers.wsgi

DIR=(os.path.abspath(__file__))
sys.path.append(DIR)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.production'

application = django.core.handlers.wsgi.WSGIHandler()

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



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

Добавим в development.py:

INSTALLED_APPS += (
'django.contrib.admin',
)

А также в manage.py:

if settings.DEBUG and command == "test":
settings.MAINTENANCE_MODE = False

execute_manager(settings)

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

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

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

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

$export SVN_SSH="ssh -l loginname"

или сделаем пару ключа (она нам все рано пригодиться при использовании fabric). На сервере с сайтом:

$ ssh-keygen -t dsa
$ cat ~/.ssh/id_dsa.pub

копируем вывод, добавляем на сервер с кодом в ~/.ssh/authorized_keys2 на сервер (если файла нет, то touch ~/.ssh/authorized_keys2 && chmod 600 ~/.ssh/authorized_keys2 ). Пробуем логиниться без пароля.

$svn checkout --depth=empty svn+ssh://codesrv/repos/musicmans/trunk/backend root
$cd root/
$svn update --set-depth=infinity www
$svn update --set-depth=infinity src

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

Устанавливаем необходимые приложения для сайта:

vermus@musicmans:~$ cd /srv/musicmans/root/src
vermus@musicmans:~$ sudo pip install -r requirements.txt --download-cache /usr/src/pipcache/

Установка postgresql:

# apt-get install postgresql python-psycopg2
# su postgres
$ createuser musicmans --no-superuser --no-createdb --no-createrole --login --pwprompt --encrypted
$ createdb --owner=musicmans --encoding=utf-8 musicmans

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

vermus@musicmans:~$ cd /srv/musicmans/root/src/
vermus@musicmans:/srv/musicmans/root/src$ python manage.py syncdb

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

Установим uwsgi сервер:

$ cd /usr/src/
$ sudo pip install http://projects.unbit.it/downloads/uwsgi-latest.tar.gz

Настроим скрипт init.d для запуска через файловый сокет сервера uwsgi с проектом (/etc/init.d/uwsgi):

# cat uwsgi
### BEGIN INIT INFO
# Provides: uwsgi
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the uwsgi app server
# Description: starts uwsgi app server using start-stop-daemon
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/uwsgi

OWNER=uwsgirun

NAME=uwsgi
DESC=uwsgi

test -x $DAEMON || exit 0

# Include uwsgi defaults if available
if [ -f /etc/uwsgi ] ; then
. /etc/uwsgi
fi

set -e

DAEMON_OPTS="--socket /var/lib/nginx/uwsgi/musicmans.sock --chmod-socket -d /srv/musicmans/logs/uwsgi.log --pythonpath $PYTHONPATH --module $MODULE"

case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --chuid $OWNER:$OWNER --user $OWNER \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \
--exec $DAEMON
echo "$NAME."
;;
reload)
killall -1 $DAEMON
;;
force-reload)
killall -15 $DAEMON
;;
restart)
echo -n "Restarting $DESC: "
start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \
--exec $DAEMON
sleep 1
start-stop-daemon --user $OWNER --start --quiet --chuid $OWNER:$OWNER \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
status)
killall -10 $DAEMON
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|reload|force-reload|status}" >&2
exit 1
;;
esac
exit 0

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

PYTHONPATH=/srv/musicmans/root/src
MODULE=wsgi

Обратите внимание, что мы указываем имя модуля python, а не имя файла.
Устанавливаем chmod 755 для скрипта /etc/init.d/uwsgi , загружаем при старте системы:

root@musicmans:/var/lib/nginx# chown -R uwsgirun uwsgi
root@musicmans:/etc/init.d# chmod 755 uwsgi
root@musicmans:/etc/init.d# update-rc.d -f uwsgi defaults
root@musicmans:/etc/init.d# /etc/init.d/uwsgi start

Конфиги nginx: nginx.conf, стандартный из пакета debian. Конфиг сайта:

root@musicmans:/etc/nginx/sites-available# cat musicmans
#serving Django.
upstream django {
ip_hash;
server unix:/var/lib/nginx/uwsgi/musicmans.sock;
}

server {
listen 80;
server_name musicmans.ru;
charset utf-8;
error_log /srv/musicmans/logs/nginx_error.log info;
access_log /srv/musicmans/logs/nginx_access.log;

# Django admin media.
#location /media/admin/ {
# alias lib/python2.6/site-packages/django/contrib/admin/media/;
# }

# Your project's static media.
location /media/ {
alias /srv/musicmans/root/www/;
}

# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass django;
include uwsgi_params;
}

}

location ~ /.svn/ {
deny all;
}.


Включаем сайт

# 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.

#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).


# -*- mode: python; coding: utf-8; -*-
import sys
from fabric.api import env, run, prompt, local, get, cd, sudo, require
from fabric.state import output
from fabric.contrib.files import uncomment
import datetime

now = datetime.datetime.now()

def production():
#здесь данные об удаленном сервере с сайтом
env.environment = "production"

env.hosts = ['codesrv']
env.user = 'vermus'
env.path = '/srv/musicmans/root'
env.root_path = '/srv/musicmans'

env.db_name = 'musicmans'
env.db_user = 'musicmans'

def deploy():
"""
In the current version fabfile no initial database creation and configure the virtual server host.
"""
require('environment', provided_by=[production])#дописать по желанию dev и stage

if env.environment == 'production':
if "y" != prompt('Are you sure you want to update the production site (test & check in trunk release code!)? (y/[n])?', default="n"):
return

if "y" == prompt('Install the necessary applications (y/n)?', default="n"):
install_requirements();

if "y" == prompt('Set MAINTENANCE_MODE (y/n)?', default="y"):
maintenance_mode() #выключается автоматически, при апдейте production.py с svn сервера

if "y" == prompt('Create database backup? (y/n)?', default="y"):
backup_db()

if "y" == prompt('Create source code backup? (y/n)?', default="y"):
backup_src()

update_from_svn()
migrate_database()

restart_webserver()

def install_requirements():
require('environment', provided_by=[production])#дописать по желанию dev и stage
print(" * install the necessary applications...")

requirements_file = env.path+'/src/requirements.txt'

args = ['install',
'-r', requirements_file,
'--download-cache', '/usr/src/pipcache/'
]

run('pip %s' % ' '.join(args))

def maintenance_mode():
require('environment', provided_by=[production])#дописать по желанию dev и stage
print(" * change production.py and restart nginx...")
uncomment(env.path+'/src/settings/production.py', '#MAINTENANCE_MODE = True')

restart_webserver()

def backup_db():
require('environment', provided_by=[production])#дописать по желанию dev и stage
print(" * create database dump...")

db_name = env.db_name
db_user = env.db_user

backup_file = "backup_%d_%d_%d_%d_%d.sqlgzip" % (now.day, now.month, now.year, now.hour, now.minute)
backup_dir = env.root_path+'/backups/db'
with cd(backup_dir):
run("echo dbpassword | pg_dump -W -U %s -F c %s > %s" % (db_user, db_name, backup_file))

def backup_src():
require('environment', provided_by=[production])#дописать по желанию dev и stage
print(" * create source code backup...")
backup_dir = env.root_path+'/backups/src'
backup_file = "backup_%d_%d_%d_%d_%d.tar.gz" % (now.day, now.month, now.year, now.hour, now.minute)
src_dir = env.path+'/src'

run("mkdir -p %s" % backup_dir+'/all')
run("cp -f -R %s %s" % (src_dir, backup_dir+'/all'))
run("cp -f -R %s %s" % (env.path+'/www/static', backup_dir+'/all'))

with cd(backup_dir):
run ('tar -zcf %s %s' % (backup_file, backup_dir+'/all'))
run ('rm -f -R %s' % (backup_dir+'/all'))


def update_from_svn():
require('environment', provided_by=[production])#дописать по желанию dev и stage
with cd(env.path):
run('svn update') #svn checkout сделаем вручную первый раз

def migrate_database():
require('environment', provided_by=[production])#дописать по желанию dev и stage
with cd(env.path+'/src'):
run('python manage.py migrate --no-initial-data')
run('python manage.py syncdb')

def restart_webserver():
require('environment', provided_by=[production])#дописать по желанию dev и stage
print(" * restart nginx")
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 следующую строчку:

# "local" is for Unix domain socket connections only
local musicmans musicmans md5
local all all ident

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

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

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

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

В чем я?!

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

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

Далее, качаем pip, разархивируем, устанавливаем:
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 позже.

В чем я?!

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

Структура проекта может быть по вашему вкусу. Тут нет каких-то жестких правил, хотя правила в создании django приложений (не проекта) все-таки есть. Итак, приведем в пример структуру pinax сайта проекта:

apps
deploy
media
site_media
templates
tests

__init__.py
context_processors.py
manage.py
urls.py
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:

# -*- mode: python; coding: utf-8; -*-
'''
Created on 02.07.2010
'''

ADMINS = (
('Vermus', 'admin@musicmans.ru'),
)

MANAGERS = ADMINS

TIME_ZONE = 'Europe/Moscow'

LANGUAGE_CODE = 'ru-RU'

import os, sys

#Определяем корень проекта
PROJECT_ROOT = os.path.normpath(os.path.dirname(os.path.dirname(__file__)))

#Добавляем apps в системную переменную path
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'apps'))

#URL к медиа файлам
MEDIA_URL = '/media/'
#путь в системе к медиа файлам
MEDIA_ROOT = os.path.join(os.path.dirname(PROJECT_ROOT), os.path.join('www', MEDIA_URL.strip('/') ))

#URL к статическим файлам
STATIC_URL = MEDIA_URL + 'static/'
#пусть в системе к статическим файлам
STATIC_ROOT = os.path.join(MEDIA_ROOT, 'static')

ROOT_URLCONF = 'urls'

#Админка нам не нужна, убираем
#ADMIN_MEDIA_PREFIX = '/media/'

TEMPLATE_DIRS = (
os.path.join(PROJECT_ROOT, 'templates'),
)

development.py (svn:ignore):

# -*- mode: python; coding: utf-8; -*-
DEBUG = True
TEMPLATE_DEBUG = DEBUG

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'musicmans', # Or path to database file if using sqlite3.
'USER': 'user', # Not used with sqlite3.
'PASSWORD': 'pass', # Not used with sqlite3.
'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}

from common import *

production.py:

# -*- mode: python; coding: utf-8; -*-
DEBUG = False

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'musicmans', # Or path to database file if using sqlite3.
'USER': 'user', # Not used with sqlite3.
'PASSWORD': 'pass', # Not used with sqlite3.
'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}

from common import *

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

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

Отредактируем manage.py (в его начале мы разбираемся с settings проекта, потом настраиваем eclipse для отладки django проекта):


from django.core.management import execute_manager
try:
import settings.development as settings
except ImportError:
try:
import settings.production as settings
except:
import sys
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__)
sys.exit(1)

if __name__ == "__main__":
if len(sys.argv) > 1:
command = sys.argv[1]
if settings.DEBUG and (command == "runserver" or command == "testserver"):
# Make pydev debugger works for auto reload.
try:
import pydevd
except ImportError:
sys.stderr.write("Error: " +
"You must add org.python.pydev.debug.pysrc to your PYTHONPATH.")
sys.exit(1)

from django.utils import autoreload
m = autoreload.main
def main(main_func, args=None, kwargs=None):
import os
if os.environ.get("RUN_MAIN") == "true":
def pydevdDecorator(func):
def wrap(*args, **kws):
pydevd.settrace(suspend=False)
return func(*args, **kws)
return wrap
main_func = pydevdDecorator(main_func)

return m(main_func, args, kwargs)

autoreload.main = main

execute_manager(settings)

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



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

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



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

В чем я?!

musicmans.ru | Как сделать сайт на Django | Настраиваем Eclipse

Подготовка Eclipse

1. Качаем Eclipse на машину разработчика (windows, linux).

2. В Eclipse - Help->Install New Software, выбираем из выпадающего списка Helios - http://download.eclipse.org/releases/helios, выбираем:
General Purpose Tools - Marketplace Client 1.0.0.v20100611-0430
Это новый удобный клиент репозитория приложений для Eclipse. Следует учесть, что в этом репозитории находятся и платные приложения, так что, проверяйте информацию нажатием на кнопочку "i". Почитать обзор.

3. Заходим в Help-Eclipse Marketplace, устанавливаем pydev (поддержка python).
4. Заходим в Help-Eclipse Marketplace, устанавливаем mylyn.

Почитать про Mylyn здесь и здесь.

5. Обеспечиваем синхронизацию задач mylyn в eclipse и redmine.

6. Заходим в Help-Eclipse Marketplace, устанавливаем Subversive (считайте меня консерватором) (после перезапуска устанавливаем коннектор SVN Kit последней версии).

7. Устанавливаем Aptana (Aptana Studio 3.0.0 Beta, 2я не установилась на Helios) (редактирование шаблонов html, css, может быть и js).
Update Site: http://download.aptana.com/studio3/plugin/install

Subversion

Будем использовать через svn+ssh://. Устанавливаем subversion и ssh на сервер для управления кодом.
#apt-get install subversion ssh

Создаем репозиторий:

root@codeserver:~# cd /
root@codeserver:/# mkdir repos
root@codeserver:/# cd repos/
root@codeserver:/repos# svnadmin create musicmans


Добавляем пользователя и устанавливаем владельца:

#useradd -m svnuser
#chown -R svnuser:svnuser /repos/musicmans/

Проверяем, что пользователь может логиниться по ssh.
Далее, в eclise на машине разработчика переходим в перспективу SVN Repository Exploring, в Repository Location, и добавляем New-Repository Location.

Указываем URL: svn+ssh://codeserver/repos/musicmans/, указываем логин, пароль, включаем Enable Struсture Detection, ну и смотрим другие параметры.

После успешного подключения создаем структуру репозитория в New -> Project Structure, Monolithic layout.

Создание проекта

Далее, нам надо создать каркас django проекта и выложить его в trunk.

Как установить python (2.6), django (1.2), рассказывать не буду, мануалов масса.

В новом pydev появилась замечательная вещь, как создание Django проекта из мастера создания проектов.

Переходим в перспективу Pydev, в контекстном меню package explorer указываем New - Project:



Далее, введем название проекта, директорию, а также необходимо настроить интерпретатор python, нажав на кнопку autoconfig:


Выбираем версию django. Выбираем Database Engine. От обещанного sqlite3 я отказался, решив использовать postgresql как в разработке, так и на рабочем сервере. Считаем что, postgresql установлен на машине разработчика (мануалов масса на любую систему).

После окончания работы мастера наблюдаем примерно такую картину:



Еще в pydev появилось вот такое меню для проекта:



Поменяем кодировку проекта на UTF-8, если необходимо (пункт меню Properties).

Далее, расшарим проект в репозиторий.

Subversive замечательно справляется со структурой trunk, branches, tags.

Сделаем так, что у нас проект будет находится в trunk под именем backend, то есть trunk/backend, ведь в будущем у нас может появиться и другие проекты, которые надо будет разместить в транке, например проект gwt.

В контекстном меню проекта указываем -> Team - Share Project, далее несколько окон по умолчанию, в окне указания расположения проекта:
use specified name: backend
Нажимаем далее, в комментарии видим что-то похожее:
Share project "musicmans" into "svn+ssh://codeserver/repos/musicmans"
И коммитим изменения, в окне с коммитом необходимо добавить *.setting, *.pydevproject и *.project в svn:ignore (контекстное меню в списке ресурсов).
Перейдем в SVN Repository Exploring, в Repository Location нажмем на кнопку обновить:



Предварительную настройку директорий и проекта выполнем в транке, а конкретные задачи создав ветку и переключившись в нее. Смотрите, как это просто делается в subversive:





Создаем и сразу переключаемся в ветку. Пока делать этого не будем. На этом настройка Eclipse пока закончена.

В чем я?!

musicmans.ru | Как сделать сайт на Django | Начало

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

Технологии.

Серверная сторона - django. Конечно будем использовать сторонние django приложения, и не будем писать тесты, тестировать будут пользователи. :) Элементарные вещи о django рассматриваться не будут, для этого есть django book.

Клиентская сторона - наверное gwt. Пока не определился, но думаю внедрим.

Инструменты и техническая сторона

Техническая сторона - покупайте домен и vds. VDS можете купить два или сделать собственный сервер, например, дома, один для сайта, другой для хранения и управления кодом.
Собственно, предыдущие посты как раз были подготовкой к работе.
Система контроля версий - Subversion.
Управление проектом и баг-трекер - Redmine.
Среда разработки Eclipse (кстати вышел Helios 3.6) с pydev, Subversive + расширения по вкусу плюс второй экземпляр Eclpise для gwt.

Ну вот вроде бы и все. Если не все, допишу позже.
Сроки. Ориентируюсь на полгода до более-менее приличного сайта (потому что есть еще основная работа, к сожалению :) ).
Все этапы разработки сразу будут выкладываться на сайт http://musicmans.ru .

Следить за постами о разработке можно по тегу musicmans.ru.

Метки

.net .NET C# .sort 1.2 2009 2010 404 error admin ajax amazon analytics and apache api archlinux asp.net async asynchronous autocomplete bash blender blog blogengine blogs book bootstrap bot bpython buildout byteflow bzr C c plus plus C++ cache cbv Chaco checkio chrome ci ckeditor class based views clojure closure cms cms с удобной админкой code coding style collectd COM comet competition conference ConfigParser contest Context continuous integration CouchDB coverage CppCMS cpyext cpython crud csrf CSS ctypes curl custom model fields cx_freeze cython database db dbm dbqueries debian debug debugging decorator decorators deploy deployment descriptor design dev devconf developers development diveintopython Django django 1.2 django 1.3 django advent django framework django template django trunk django weblog django-admin-tools django-cms django-compressor django-hosts django-piston django-registration django-sphinx django.admin djangoadvent djangocms djangodash doc documentation drupal e-legion eclipse EGit emacs encoding Enthought epoll erlang event exception ExtJS fabric facebook fastcgi finaloption fixtures fonts forms formset fp framework freebsd freeswitch fs2web ftp fun funcparserlib functional gae gamin gandi generic views gettext gevent gil git github gitosis Google Google App Engine google picasa Google Translate google wave Google Web Toolkit grab grablab greenlet gtd gui haskell hg hgshelve highlighter host hosting how-to howto html html5lib Hudson humor i18n icfpc ide idiomatic image-scripting improvements Internet interpreter ipython ironpython izmenimsya.ru jabber java javascript jenkins jetbrains JIT job jquery json jstree jython kde kiev kiyv kyivpy l10n ldap library libs Life Links linux Linux & Unix LLVM logging logs lxml Mac OS X magic mail markdown Matplotlib Mayavi maybe mediavirus meetup memcache Memcached memory messages metaclass middleware migration mikrotik mkd model models mod_python mod_wsgi mongodb monitoring mptt musicmans.ru musicx mvc my-projects mysql netCDF networkx newforms newforms-admin news nginx Nhibernate nix nose NoSQL numpy oop open source OpenID openoffice opster optimization oracle orm os pagination parsing path patterns pdf PDF-принтер PEP PEP8 performance performance optimization perl personality photo php picture-driven computing PIL pinax pingback pip plasma plone plugin plugins postgresql programming progress bar psycopg2 py2exe pybb pybbm pycamp pycharm pycon pycow pycurl pydev pygtk pylons PyNGL pypy pyqt PyQt4 pyrad pyramid PySide Python Python 2.5 python 2.7 python 3 python c api python speed python-mssql python3 pywinauto Qt Qt4 queue rabbitmq radius raw sql re redis redsolution redsolution cms regexp regular expressions release repoze.bfg RequestContext reusable apps robokassa rss ru ruby ruby-on-rails sample satchmo scalability SciPy scraping screencast search selenium self.error seo server setattr settings setuptools shell sikuli sms snippet socket.io software sorting south sphinx spider sql sqlalchemy sqlite ssh startup step-by-step subdomain subversion svn SyntaxHighlighter system tags tdd tddspry teh drama template templates templatetags test testing thinkpad threading threads tips tips and tricks tools tornadio tornado tornado server tricks tutorial tweepy twisted twitter typography uapycon Ubuntu ucsvlog uml Uncategorized unicode unit test unit testing UnitTest Unladen Swallow upload urllib urls utf-8 uwsgi validation vcs versioning video vim virtualenv Visual Studio vkontakte voip wave web web-devel web-services web-разработка webdev webfaction webkit webpy websockets webtest widget widgets Win API windows Wirbel work wrapper wsgi wxPython wxWidgets wysiwyg xapian xml xmonad xmpp xpath yandex youtube zip zomg zope [cdata[cbv]] [cdata[ci]] [cdata[class based views]] [cdata[continuous integration]] [cdata[django framework]] [cdata[django-sphinx]] [cdata[django]] [cdata[nginx]] [cdata[python]] [cdata[virtualenv]] [cdata[программирование]] автоматизация администрирование администрирование django админка алгоритмы архитектура атрибуты базы данных Без рубрики безопасность библиотеки блоге бот веб-разработка видео Визуализация данных вконтакте Все записи гвидо ван россум граббер графика графы декоратор декораторы дескриптор дескрипторы документация заметки игра жизнь идея интересное киев Клиентам книги конференция личное математика метаклассы модели модули монады морфология мысли невозможное новости о облачные вычисления обо мне Обработка данных оптимизация оптимизация кода Основная лента основы парсинг парсинг сайтов перевод песочница Питон поебень поиск правила кодирования программирование Проектирование производительность работа рабочее размышлизмы Разное разработка разработка приложений разработки регулярные выражения сайт событие события ссылки статьи тестирование тесты Тюмень убунтариум фигня философия формы форум Хабрахабр хакинг хостинг шаблоны шаблоны проектирования эксперимент Эксперименты юмор я пиарюсь Яндекс