Полезные мелочи, скрашивающие жизнь джанговоду.

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

Адресация при импортировании модулей внутри проекта
При импортировании видов, моделей и прочих штук, я никогда не указываю модуль проекта, как это делается в примерах официальной документации. Этим я достигаю безболезненной переносимости приложений из одного проекта в другой т.к. модули проектов могут называться по разному. Например, этот блог хранится на сервере в каталоге /web/web-brains.com/web_site, где web_site - это модуль проекта. Так вот, при импортировании чего угодно я не пишу
from web_site.blog.models import Blog
from web_site.blog.models import Blog

, я пишу
from blog.models import Blog
from blog.models import Blog

Кстати, все проекты я называю web_site и кладу их в каталог с именем cовпадающим с названием домена проекта.

urls.py
В главном файле карты запросов (так я зову файл с urlpatterns), я лишь указываю откуда следует включать дополнительные карты. Если есть какие-то запросы, специфичные для данного проекта, я создаю для этого приложение project и помещаю эти запросы в project/urls.py. Вот пример карты запросов этого блога:


from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'', include('standalone_blog.urls')),
(r'', include('account.urls')),
(r'', include('comment.urls')),
(r'', include('score.urls')),
(r'', include('blog.urls')),
(r'', include('utils.debug_urls')),
(r'^admin/', include('django.contrib.admin.urls')),
)

from django.conf.urls.defaults import *

urlpatterns = patterns(”,
(r”, include(‘standalone_blog.urls’)),
(r”, include(‘account.urls’)),
(r”, include(‘comment.urls’)),
(r”, include(‘score.urls’)),
(r”, include(‘blog.urls’)),
(r”, include(‘utils.debug_urls’)),
(r’^admin/’, include(‘django.contrib.admin.urls’)),
)

Этим достигается свободная переносимость модулей приложений из одного проекта в другой.

settings.py
Обычно я завожу файл settings_local.py, куда записываю настройки специфичные для конкретного компьютера. Это удобно, когда проект лежит в репозитарии кода и нужно обеспечить различные настройки на продакшн-сервер и машине разработчика. То есть setttings_local.py я НЕ заношу в репозитарий. Ну, а в самом setttings.py я пишу в конце:
from settings_local import *
from settings_local import *


Повторное использование приложений в разных проектах
Я стараюсь писать каждое приложение так, чтобы его без труда можно было использовать в другом проекте. Все такие приоложения лежат у меня в отдельном каталоге на сервере. Когда мне нужно использовать какое-либо приложение, я просто делаю symlink на него в каталог проекта, добавляю его в settings.py и включаю его карту запросов в urls.py. Всё.

Повторное использование шаблонов
Шаблоны тоже можно использовать повторно :-) Для того, чтобы при подключении приложения в каталог проекта, автоматически подхватывались его шаблоны, нужно хранить их внутри каталога приложения. Например, шаблоны модуля blog у меня хранятся в blog/templates/blog/
Когда же мне нужно изменить какой-то шаблон, я просто создаю каталог blog в главном каталоге templates и добавляю туда изменённый файл. Чтобы файлы из главного каталога templates имели приоритет перед одноимёнными файлами в каталогах приложений, следует убедиться, что в файле настроек список TEMPLATE_LOADERS содержит как минимум два элемента в указанном порядке


TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)

TEMPLATE_LOADERS = (
‘django.template.loaders.filesystem.load_template_source’,
‘django.template.loaders.app_directories.load_template_source’,
)


Синхронизация изменений в Django моделях с таблицами в базе данных
В стандартной поставке Django нет (пока) средств для изменения структуры таблиц базы данных при изменении структуры моделей. Я пользуюсь сторонней разработкой: django-schemaevolution [code.google.com], которая решает эту проблему. Суть её действия в том, что она создаёт снимок моделей приложения, затем в случае изменения модели, этот инструмент генерирует файл evolution_script.py, при запуске которого вносятся необходимые поправки в соответствующие таблицы базы данных.

Проектирование видов
Читая маниакальный веблог [softwaremaniacs.org] я почерпнул для себя полезную методику выненесения всех действий по изменению данных из видов в формы. Допустим, у вас есть вид изменения какого-либо объекта. В этом виде, вы отображаете форму, затем в следующем запросе получаете POST данные и пытаетесь их сохранить в БД. Так вот, операции сохранения я выношу в метод save соотвествущей формы. Не буду углубляться в детали т.к. я скоро я напишу отдельную заметку, посвящённую вопросам использования библиотеки newforms в Django-проектах т.к. newforms на данный момент - это одна из самых загадочных фич Django в силу отсутствия полной документации.

Удобное использование RequestContext
Часто бывает так, что практически в любом виде проекта нужно создавать context из класса RequestContext
Cтандартный способ этого добиться:


def show_foo(request):
context = {'foo': 'bar', 'bar': 'baz'}
return render_to_response('foo_template.html', context, context_instance=RequestContext(request))

def show_foo(request):
context = {‘foo’: ‘bar’, ‘bar’: ‘baz’}
return render_to_response(‘foo_template.html’, context, context_instance=RequestContext(request))

Когда в проекте с десяток приложений и в каждом приложении с десяток видов стократное повторение этого заклинания сведёт в могилу даже самого терпеливого джанговода. Есть более удобное решение - хитрый декоратор (спасибо piranha [blog.piranha.org.ua] за подсказку). Вот его код:


def render_to(template_path):
def decorator(func):
def wrapper(request, *args, **kw):
output = func(request, *args, **kw)
if not isinstance(output, dict):
return output
return render_to_response(template_path, output,
context_instance=RequestContext(request))
return wrapper
return decorator

def render_to(template_path):
def decorator(func):
def wrapper(request, *args, **kw):
output = func(request, *args, **kw)
if not isinstance(output, dict):
return output
return render_to_response(template_path, output,
context_instance=RequestContext(request))
return wrapper
return decorator


Перепишем прошлый пример с использованием декоратора:


@render_to('foo_template.html'):
def show_foo(request):
return {
'foo': bar, 'bar': 'baz'}

@render_to(‘foo_template.html’):
def show_foo(request):
return {
‘foo’: bar, ‘bar’: ‘baz’}

Простота второго примера видна с первого взгляда.

Постраничное разбиение
Для постраничного разбиения множества объектов в Django есть ObjectPaginator [djangoproject.com].
Пример его использования:



@render_to('index.html')
def index(request):
try:
page = int(request.GET.get('page',0))
except ValueError:
page = 0
paginator = ObjectPaginator(
News.objects.all(),2)
records = paginator.get_page(page - 1)
return {
'page': page,
'pages': paginator.pages,
'records': records}


@render_to(‘index.html’)
def index(request):
try:
page = int(request.GET.get(‘page’,0))
except ValueError:
page = 0
paginator = ObjectPaginator(
News.objects.all(),2)
records = paginator.get_page(page - 1)
return {
‘page’: page,
‘pages’: paginator.pages,
‘records’: records}


Мне не понравилось повторение куска вычленения номера страницы из request при каждом использовании пагинатора.
Решение этой проблемы я нашёл на форуме маньяка [softwaremaniacs.org], где он посоветовал задействовать декоратор.


def paged(func):
def wrapper(request, *args, **kwargs):
try:
page = int(request.GET.get('page',1))
except ValueError:
page = 1
return func(request, page=page, *args, **kwargs)
return wrapper

def paged(func):
def wrapper(request, *args, **kwargs):
try:
page = int(request.GET.get(‘page’,1))
except ValueError:
page = 1
return func(request, page=page, *args, **kwargs)
return wrapper


А вот переписанный пример с использованием декоратора:



@render_to('index.html')
@paged
def index(request,page):
paginator = ObjectPaginator(
News.objects.all(),2)
records = paginator.get_page(page - 1)
return {
'page': page,
'pages': paginator.pages,
'records': records}


@render_to(‘index.html’)
@paged
def index(request,page):
paginator = ObjectPaginator(
News.objects.all(),2)
records = paginator.get_page(page - 1)
return {
‘page’: page,
‘pages’: paginator.pages,
‘records’: records}


На этой пагинированной ноте закончу, пожалуй, повествование. Я не успел исчерпать запас полезных фишек, поэтому на днях дополню эту заметку )
Add post to:   Google Slashdot Yahoo Digg Technorati Delicious Bobrdobr.ru Newsland.ru Smi2.ru Rumarkz.ru Vaau.ru Memori.ru Rucity.com Moemesto.ru News2.ru Mister-Wong.ru Yandex.ru Myscoop.ru 100zakladok.ru
Make comment

Comments

  • Привет !

    Для синхронизации моделей и структуры БД я пользуюсь Django Evolution, описал на своём блоге тут:http://spanasik.blogspot.com/2007/12/django_20.html

    С уважением, Стас

  • Я сейчас пользуюсь deseb: http://code.google.com/p/deseb/ Обновления модели (в иделе) проходит так: ./manage.py sqlevolve foobar | mysql dbname На практике deseb подглючивает, но обычно grep -v [херня] спасает дело )

comment submission form

Required. 30 chars of fewer.

Required.

captcha image Please, enter symbols, which you see on the image