6 октября 2010 в 12:55

Сумбурные заметки про python и django

Накопилось несколько маленьких заметок/советов про python и django, которые на отдельные топики не тянут, поэтому публикую все сразу.

Под катом:
  • как упростить код вьюх ровно в 2 раза
  • легкий способ рисования графиков
  • почему Ian Bicking воскликнул «Cool!»
  • приложения для ВКонтакте на django за 5 минут
  • хорош ли pymorphy?
  • пара фишек насчет выкладки пакетов на pypi
  • что общего между декораторами и with-контекст-менеджерами
  • принимаем оплату на django-сайтах
  • показываем Яндекс.Карту для заданного адреса



Django: упрощаем код вьюх


В документации и обучающих примерах по django обычно пишут вьюхи вот так:

def contact(request):
    if request.method == 'POST': 
        form = ContactForm(request.POST)
        if form.is_valid():
            # обрабатываем данные. Например, делаем form.save()
            # ...
            return HttpResponseRedirect('/thanks/') # после POST-запроса делаем редирект
    else:
        form = ContactForm() 

    return render_to_response('contact.html', {
        'form': form,
    }, context_instance=RequestContext(request))    


Наверное, это правильно — так объяснять, чтобы человек лучше понимал, что происходит. Но в реальной жизни этот код пишется ровно в 2 раза короче:

def contact(request):
    form = ContactForm(request.POST or None)
    if form.is_valid():
        # обрабатываем данные. Например, делаем form.save()
        # ...
        return redirect('url_name', param1=value)
    return direct_to_template(request, 'contact.html', {'form': form})    


Фишки:
  • Для unbound-форм is_valid всегда возвращает False. Если после этого сразу непонятно, как работает код ContactForm(request.POST or None), то разберитесь в качестве упражнения, расписывать не буду. Это простая и полезная идиома.
  • Всегда используйте django.shortcuts.redirect для редиректов. Он умеет реверсить названия url'ов, вызывать get_absolute_url или просто перенаправлять по url'у
  • Используйте django.views.generic.simple.direct_to_template вместо render_to_response. Они делают почти одно и то же, но direct_to_template использует RequestContext вместо Context, который и так нужен в большинстве случаев. Вместо direct_to_template можно использовать декоратор render_to из django-annoying, но это уже дело вкуса, кому как нравится.


Django: рисуем графики


В статье про админку обещал рассказать про графики, но все никак руки не доходили, нехорошо получилось. Да и рассказывать-то там особо нечего, все слишком просто и «тупо» — графики рисуются через google charts. При этом можно обойтись без всяких библиотек: конструируем себе график по вкусу тут (это полуофициальный инструмент от гугла, на него есть ссылка со справки по api google charts), а потом вставляем полученную строку в шаблон и подставляем переменные вместо тестовых значений.

Есть очень тонкая обертка над google charts: django-chart-tools. Суть — та же: собрать график визуально и заменить переменные, просто с django-chart-tools такие графики удобнее поддерживать.

Выборку данных можно делать просто через django ORM, или, для удобства/скорости, через django-qsstats-magic, в зависимости от задачи.

В итоге (с использованием django-chart-tools и django-qsstats-magic) график пользователей по дням можно вывести примерно так:

# исходные данные
qs = User.objects.filter(is_active=True)
end = datetime.today()
start = end-timedelta(days=30)

# готовим данные для графика
data = QuerySetStats(qs, 'date_joined').time_series(start, end)
values = [t[1] for t in data]
captions = [t[0].day for t in data]


потом переменные values и captions передаем в шаблон, а там выводим график таким образом:

{% load chart_tags %}
{% bar_chart values captions «580x100» %}


Ограничений по количеству обращений у google charts image api нет, там просят только связаться с ними, если > 200тыс обращений в день будет, чтобы они нагрузку распределили. Так что такие графики можно не только в админке использовать.

Django: тесты


Используйте для написания тестов django-webtest. Я уже писал про это приложение, но с того времени произошло одно очень важное изменение: django-webtest теперь предоставляет доступ к контексту шаблонов (точно так же, как и стандартный джанговский тест-клиент). Спасибо Gregor Müllegger. Теперь можно писать в таком стиле:

    # ...
    response = page.forms['my-form-id'].submit().follow()
    assert response.context['user'] == self.user


работает также стандартный assertTemplateUsed.

django-webtest лучше любой интеграции с twill, т.к. в них нет доступа к контексту шаблонов и полной поддержки юникода, да и twill не развивается.

django-webtest лучше стандартного тест-клиента, т.к. предоставляет простой API (попробуйте-ка засабмитить форму со значениями по умолчанию через стандартный тест-клиент). Со стандартным тест-клиентом также нельзя протестировать отсутствие csrf-токена (или очень чер окольными пуями), а с django-webtest это делается тривиально (и даже автоматически). Используйте django-webtest)

Тут бы составить попсовую табличку с фичами: у django-webtest будут везде зеленые галочки, а у twill и стандартного тест-клиента — красные то тут, то там. Даже Ian Bicking считает, что django-webtest — это «Cool!».

Django: пишем приложение для Вконтакте


Это не просто просто, а очень просто. Отличие от обычных сайтов — только в способе регистрации и входа пользователей. Вместо django-registration ставим и настраиваем django-vkontakte-iframe. Все, теперь все посетители — это зарегистрированные и авторизованные django-пользователи, в остальном можно разрабатывать обычный сайт. Разве что еще позаботиться об js, чтобы подгонять размер iframe под размер страницы.

Python/Django: работа с русским языком


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

pymorphy вырос из статьи на хабре. Признаюсь, код сначала не был хорош, т.к. это был мой первый опыт общения как с python, так и с nlp (обработкой естественных языков). Но морфологический анализатор был-таки написан — и заброшен на год.

В начале этого года возобновил работу над pymorphy и переписал там кучу всего. А весной прошло «соревнование» морфологических анализаторов в рамках конференции Диалог-2010. Там участвовали очень серьезные ребята, результаты проверяли профессиональные лингвисты. pymorphy по дорожке «Морфология» справился лучше всех (скорее всего из-за того, что я как раз тогда выкатил работу с составными словами, записанными через дефис). Также pymorphy был единственным участником, приславшим разбор дорожки с «гразными текстами». Это все особо ни о чем не говорит, но приятно)

Python: пара трюков для выкладки пакетов на pypi


1. В setup.py в long_description можно использовать разметку ReST. Удобно положить рядом с setup.py файлик README.rst и потом просто указать
long_description = open('README.rst').read()
После этого на странице проекта на pypi сразу будет справка по нему — это просто, удобно, и в 90% устраняет необходимость в мороке с отдельной документацией (тут еще такое замечание — если все же кажется, что пакету нужна документация с навигацией и тд, то стоит задуматься — возможно, пакет делает слишком много?).

2. Есть сравнительно малоизвестный хак с setup.py. Если разметка выглядит не так, как хотелось, или исправили опечатку, или classifier, то делать новый релиз для исправления этих ошибок нет никакой необходимости: можно просто запустить ./setup.py register и данные обновятся.

Python: декораторы и with


Декораторы и оператор with в питоне часто применяются для одного и того же: выполнить какие-то дополнительные действия до или после определенного куска кода. А это означает, что можно написать такую штуку, которую можно использовать одновременно и как декоратор, и как контекст-менеджер для with (например, так: gist.github.com/573536).

Django: принимаем платежи на сайте


Если что, через django-robokassa и django-assist-ru сделаны в продакшне тысячи покупок, > млн рублей.

Пишите еще, кто чем пользуется, добавлю в список.

Python/Django: показываем Яндекс.карту на сайте


Чтобы не возиться с геокодированием и кешированием, можно воспользоваться приложением yandex-maps.

Ух, будем считать, что все.
Коробов Михаил @kmike
карма
338,7
рейтинг 0,0
Пользователь
Похожие публикации
Самое читаемое Разработка

Комментарии (37)

  • +1
    Красавэлла! На этой неделе нужна была django-робокасса и django-вконтакте, а тут такое… Приятно читать такое попивая кофеек )
  • +5
    Спасибо тебе добрый человек. Низкий поклон от изучающего Джанго! :)

    PS: Если есть еще чем полезным поделиться для новичков — будем только рады (думаю начинающие джангисты меня поддержат)
  • 0
    На этой неделе надо были графики %) Курил pyOFC2.
    Но спасибо большое, как новичек в джангопитоне узнал много нового.
  • +1
    добавил в избранное, позитив :)
  • +3
    > long_description = open('README.rst').read()

    Я обычно ещё вот так делаю:

    long_description = open('README.rst').read() + open('CHANGES.rst').read()
  • 0
    Еще больше сподвигли меня изучать Python) Завтра иду за книгой)
    • 0
      Не лучшее начало :-)
    • +2
      Начните с онлайн документации с оффсайта.
  • +3
    «request.POST or None» — отличная идея.

    Конечно, никуда без django-annoying. А еще полезны:
    https://bitbucket.org/jespern/django-piston/overview
    https://bitbucket.org/offline/django-publicauth/overview
    https://bitbucket.org/jezdez/django-robots/overview
    https://bitbucket.org/david/django-storages/overview
    https://bitbucket.org/zuber/django-newtagging/overview
    https://bitbucket.org/Kizlum/django-lightsearch/overview
    https://bitbucket.org/slav0nic/django-flickrstorage/overview
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        @render_to, @ajax_request
        Redirect exception
        AutoOneToOneField, JSONField
        HttpResponseReload

        вроде мелочи, а без них потом сложно, если привыкнуть
  • 0
    относительно request.POST or None — если вы захотите делать вручную редактирование модели (то есть автозаполнять форму, основываясь на данных модели) — то в вашем варианте придется переписывать всю логику (в противном случае форма берет все данные из модели и сразу считает себя валидной, до POST-запроса дело не доходит). а в первом варианте будет достаточно заменить ContactForm на ContactForm(model). думаю, здесь именно такая логика, почему джанго в доках дает именно первый, развернутый вариант.

    так что этот шорткат имеет смысл только в том случае, если вы совершенно точно хотите один раз отправить форму и навсегда про нее забыть (обращения с контактной формы на сайте, правда, наверное, подходят под это определение).
    • +5
      def project_edit(request, slug):
          project = get_object_or_404(Project, slug=slug)
      
          form = ProjectEditingForm(request.POST or None, instance=project)
          if form.is_valid():
              project = form.save()
              return redirect(project)
      
          return direct_to_template('projects/edit.html', {
              'project': project, 
              'form': form
          })
      
      

      Это рабочий код. С повторным показом формы, правильными начальными данными, ошибками валидации и редиректом на project.get_absolute_url в случае успеха. Никаких ограничений фишка с request.POST or None не накладывает. Поразбирайтесь повнимательнее, как фишка работает.
      • 0
        о, ну чудненько! отдельная передача модели как instance (кстати, туда любые данные ведь можно передать — или только модель? меня смущает синаксис model = form.save()) — совершенно верное решение в плане архитектуры.
        • +1
          Какие есть параметры конструктора у формы, такие и можно данные передать) Я тут пример привел для стандартной джанговской ModelForm:

          class ProjectEditingForm(forms.ModelForm):
              class Meta:
                  model = Project
          


          параметр конструктора instance для формы все равно ведь нужно дублировать при стандартном подходе в обеих ветках условия: unbound-форма берет из instance начальные значения, bound-форма узнает, куда все сохранять.

          В случае с обычной формой (forms.Form) начальные значения (initial) ни на что не влияют, когда форма — bound, поэтому их точно так же можно передавать. Да и вообще, в большинстве случаев по логике требуются одинаковые параметры в конструкторе и там, и там.

          Насчет ограничений — про «никаких ограничений» я погорячился) Правильней будет сказать, «можно почти всегда использовать» или «в большинстве случаев упрощает код».

          Фишку с request.POST or None не выйдет использовать, если почему-то нужно 2 совсем разные формы в bound и unbound варианте (не знаю, зачем и когда это может понадобиться).

          Еще 1 вариант есть, когда она не будет работать — если по совсем пустому POST-запросу нужно что-то делать (именно из-за этого случая проверяют request.method == 'POST', а не просто if request.POST).
          • 0
            Две разные формы могут понадобиться, если в них есть цепь последовательных <select>, а объектов в базе слишком много, чтобы их грузить все сразу. вот и получается, что в unbound ...field.queryset=SomeModel.objects.none()
            а в bound
            ...field.queryset=SomeModel.objects.all()

            Может, подскажете какой-нибудь другой подход? Этот не нравится, но ничего другого не придумалось
      • 0
        Блин, это не рабочий код, direct_to_template первым параметром request принимает) Копировал из кода, использующего @render_to, поэтому ошибся.
    • +1
      форма берет все данные из модели и сразу считает себя валидной

      Данные для предварительного заполнения скармливаются форме через параметр initial — тогда она не будет bound, и ещё не будет почём зря выполняться процедура валидации.

      form = MyForm(request.POST or None, initial = {'qwe': 'asd', 'foo': 'bar'})
      
      if form.is_valid():
          form.save()
          return redirect( ... )
      
      return direct_to_template( ... )
      
    • +1
      Если после этого сразу непонятно, как работает код ContactForm(request.POST or None), то разберитесь в качестве упражнения, расписывать не буду. Это простая и полезная идиома.

      я с вами согласен в том плане, что описание контроллеров для форм, своей однотипностью и многословностью вызывает уныние. но делать так, как вы предлагаете, тоже нельзя. :)

      если контоллерная логика такая же простая как в примере — используйте create-update-delete-generic-views — будет еще короче. :)

      ваш же код это грязный хак, а не «полезная идеома», т.к. в корне меняет семантику проверки. проверяя request.POST, вы делаете допущение, что при POST запросе (request.method == 'POST'), тело HTTP сообщения (request.POST) не может быть пустым. это так:

      допустим, форма предлагает выбрать один из вариантов контактов (список полей type=«radio»). если не выбрать ни одного и сделать POST, то тело POST запроса будет пустым и после перезагрузки страницы посетитель увидит снова оригинальную форму, вместо формы с сообщением об ошибке.

      т.е. для какой-то конкретной формы, при данных условиях это может работать. но в общем случае — нет. вот поэтому
      В документации и обучающих примерах по django обычно пишут вьюхи вот так
      • 0
        Если Вы все делаете «по науке» и используете защиту от CSRF, то в каждой форме есть еще CSRF-токен, и запрос POST не будет пустым никогда. Так что это не грязный хак и это будет работать в общем случае.

        Если же защита от CSRF не используется, то у сайта есть проблемы посерьезные, чем отсутствие ошибок валидации в тех (редких?) случаях, когда у кнопки submit не прописан name.
        • +1
          какие именно проблемы? я пока вижу только одну проблему — в пресловутом сниппете нужно учитывать массу плохо контролируемых нюансов внешней среды (csrf, содержание html-формы, ajax, возможности http-клиента). а это чревато появлением ошибок на этапе развития проекта (отключив csrf вы рискуете уронить бизнес-логику).

          учите писать правильно, а не экономить строчки кода. даже если «все под контролем». :)

          csrf же это не «наука», а инженерное решение, которое имеет вполне конкретную область применимости: работает только для html-форм, требует поддержку кук от браузера. (кстати, именно из-за привязанности к кукам, csrf не обеспечивает защиту на под-доменах). полезная штука, в своей нише, и не нужная во всех остальных случаях. не понимаю, ради чего его вытащили из contrib в core и переворотили четверть джанги. )

          «общий случай», где используются Forms (кстати, намеренно абстрагированные от request) — гораздо шире. forms часто используются для обработки данных, полученных через ajax — здесь csrf бесполезен, в принципе (кстати, и не обрабатывается django); с помощью forms удобно делать валидацию при создании api сайта. здесь csrf так же не нужен…
          • 0
            CSRF-защиту включили по умолчанию, чтобы не было ситуаций, как в mail.ru) Если защита от чего-то отключена по умолчанию, то это не защита — ей никто не будет пользоваться, а если и будет, то нет никаких гарантий, что пользоваться будут последовательно. Поэтому imho это правильное решение, так же, как и автоэкранирование в шаблонах, например. Автоэкранирование — это точно такое же инженерное решение, которое полезно в своей нише и не нужно в остальных случаях, но это большой плюс, что оно включено по умолчанию. Это снижает ментальную нагрузку с программиста — ему не нужно думать, как сделать приложение защищенным, ему нужно думать в тех случаях, когда защиту нужно по каким-то причинам отключать.

            Вы назвали csrf, ajax, содержание форм и возможности http-клиента «неконтролируемыми особенностями внешней среды». Я считаю вполне позволительным и допустимым то, что csrf (как и автоэкранирование в шаблонах) подразумевается включенным (а, значит, содержание форм и возможности http клиентов тоже сразу автоматически соответствуют условиям, при которых способ работает).

            Случаи, когда способ может не работать:

            — отключена защита от CSRF
            — обработчики ajax-запросов, которые должны выполнять какие-то действия при пустых запросах, и при этом занимаются обработкой как POST, так и GET
            — обработчики api-запросов, которые должны что-то делать при пустых запросах и при этом занимаются обработкой как POST, так и GET
            — у пользователя не работают cookie (а, значит, авторизация и тд у него тоже не работает)

            Насчет ajax api и стороннего API. В случае ajax и стороннего API unbound-формы ведь не используются обычно. Там во вьюхах и так нет 2 веток условий c созданием bound или unbound формы, поэтому там и фишка эта бессмысленна. unbound-формы нужны для показа пользователю HTML с начальными значениями. Могу представить, как формы можно использовать для отдачи начальных значений через тот же json, но это опять же, imho, очень маргинальный вариант.

            Я ничего не упускаю? Тут единственный интересный вариант — это использование одной и той же вьюхи как для ajax, так и для обычной работы в ситуации, когда на клиенте js собирает запрос для отправки «вручную», а не подхватывает просто данные из формы автоматически (так csrf-токен подхватится), и при этом вообще не шлет ничего для полей, у которых пустое значение (так кто-то пишет в реальной жизни? как это вообще написать, для каждого поля ставить проверку?)

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

            Конечная цель всего этого — упростить поддержку кода. И мы тут, очевидно, разошлись в оценках) Может, из-за того, что код разный имеем в виду. Мне кажется, что из-за полполпроцента случаев (которые, imho, довольно явно отличаются от обычных, и их нетрудно заметить) усложнять код почти всех вьюх (вводить дополнительный уровень вложенности и дублировать параметры конструктора) неправильно.
            • +1
              автоэкранирование в шаблонах, например.

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

              а view-функции из другой области. они предназначены для обработки http запросов. поэтому было бы просто замечательно, если бы django предоставляла аналогичные средства, так же упрощающие поддержку семантики этого протокола (в понимании rfc2616)…

              в реальности все совершенно иначе. view-фукнция является точкой входа для обработки любого запроса к данному URI. и вся мудреная логика формирования ответа целиком лежит на совести пользовательского кода (в том числе соответствие ответа типу сообщения GET, POST, PUT, ..). заморчки с диспетчеризацией данных для формы, по типу запроса, это прямое следствие джанговской архитектуры. вот решение этой задачи (на что сейчас все прикладники кладут, по понятным причинам) было бы приятно видеть на уровне фреймворка. сравните:

              # первая попавшаяся под руку хрень -)
              $ curl -svX PUT msdn.microsoft.com/ru-ru/default.aspx > /dev/null
              (HTTP/1.1 411 Length Required)

              # django
              $ curl -svX PUT docs.djangoproject.com/en/1.2/ > /dev/null
              (HTTP/1.0 200 OK)

              впрочем, не так давно, появилась определенная надежда на class-based views, ожидаемые в следующем релизе. они могли бы предоставлять нужный функционал по умолчанию… (правда это нехилый такой лисапед — классная реализация уже давно есть в django-piston).

              а csrf это вообще фича уровня протокола приложения. в contrib ей самое место. :)

              в принципе, я понял Вашу точку зрения, но принять не могу. :)
            • 0
              а что было с mail.ru?
  • +1
    Спасибо за разъяснение direct_to_template! а то меня эти контексты замучали)

    > form = ContactForm(request.POST or None)
    с формами я к такому же решению пришел
  • +4
    Могу посоветовать еще недавно появившийся полезный сайтик с расширениями django: djangopackages.com/. Особенно там нравится колонка «Using This»
  • +2
    я кстати для ещё большего сокращения пользую annoying pypi.python.org/pypi/django-annoying/0.7.4
    и получается что-то вроде
    @render_to('contact.html')
    def contact(request):
        form = ContactForm(request.POST or None)
        if form.is_valid():
            # обрабатываем данные. Например, делаем form.save()
            # ...
            return redirect('url_name', param1=value)
        return {'form': form} 
    

    • +2
      на @render_to Иван Сагалаев ругался сильно — по крайней мере я тут видел vorushin.ru/blog/26-decorators-python/#comment_132
      И в принципе я с ним согласен.
      • 0
        Я согласен с Иваном, в том плане, что @render_to скорее вреден, чем полезен. Хотя в django и есть аналогичная безделушка, в api шаблонизатора simple_tag

        Но в общем случае, декораторы часто используются в качестве адаптеров, поэтому отличие сигнатуры декорируемой функции это не аргумент. :)
  • +4
    По первому пункту, думаю, не стоит так делать.
    «Explicit is better than implicit.»
    «Readability counts.»
    • +5
      По-моему, в данном случае магии нет и код покороче более читабельный.
  • +2
    А direct_to_template разве не request первым аргументом принимает?
    • +1
      Тоже хотел спросить, без request первым параметром не работает :) Или это можно как-то обойти?
      • 0
        Конечно, request! Fixed.
  • 0
    Ограничений по количеству обращений у google charts image api нет, там просят только связаться с ними, если > 200тыс обращений в день будет, чтобы они нагрузку распределили. Так что такие графики можно не только в админке использовать.


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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.