Pull to refresh

Comments 78

большинство таких задач решается через CBV.

обычно создаю базовый класс от TemplateView с методами:

* reverse
* redirect
* json_response
* get_object_or_404
* оберктой над django.contrib.messages вида self.messages.success('Hello, man!')
* get_model
и др.

для login_required так же делается класс-потомок от предыдущего с переопределенным методом dispatch.

все это избавляет от кучи импортов в каждом views.py и добавляет удобства в работе.
Кажется, Вы перемудрили :-)

get_object_or_404 — это как get_object() в DetailView. Что такое get_model не очень из контекста ясно, но наверняка в List/Detail не пригодится. А вместо json_response, я считаю, лучше пользовать микшин.
from django.db.models import get_model

entries = get_model('auth', 'User').objects.get(...)
а теперь сделайте DetailView с login_required :)

можно через urls.py, но это зачастую размазывает логику по urls.py и views.py
Ох, сколько раз мне это приходилось делать…

Ох уж эти новомодные горячие клавиши.

class LoginRequiredMixin(object):
    @ method_decorator(login_required) # пробел после собачки надо убрать.
    def dispatch(self, request, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)

class PostDetailView(LoginRequiredMixin, DetailView):
    model = Post
все круто и юзабельно, пока не нужно добавить свою логику в login_required. например, логгирование попыток входа.

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

class LoginRequiredMixin(object):
    @ method_decorator(login_required) # пробел после собачки надо убрать.
    def dispatch(self, request, *args, **kwargs):
         if self.test():
            return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
         else:
             return self.test_failed()

    def test_failed(self):
        "Response в случае ошибки"

    def test(self):
        return True


И уже потом от него наследовать LoginRequired ;)

class LoginRequiredMixin(UserPassesTestMixin):
    def test(self):
        return self.request.user.is_authenticated()


Главное — вовремя остановиться =)
Классы сложнее функций, а композиция проще, чем наследование. Я выбираю более простой способ писать вьюхи. Я допускаю, что возможна ситуация когда код на CBV будет проще, но сам с таким не сталкивался.

В любом случае, считать функциональные вьюхи устаревшими попросту неправильно.
С другой стороны необходимость кучи импортов раздражает. Я даже подумывал сделать спец. пакет, сложить туда всё дерьмо и делать:
from handy.shits import *
UFO just landed and posted this here
...
Although practicality beats purity.
...
UFO just landed and posted this here
Два экрана импортов — это одновременно непрактично и безобразно, а вот import * как раз стандартная практика в джанго.
Вот что думает один из core developerов Django по поводу CBV: lukeplant.me.uk/blog/posts/djangos-cbvs-were-a-mistake/.
И это не только его мнение, классы сложны в поддержке и понимании. А сила Pythonа в том, что код легко понимается с одного взгляда.
post = Post.objects.get(pk=request.GET['id'])


вы действительно используете такую конструкцию в реальных проектах? без валидации?
Думаю, там всё ок:

ajax.catch(Post.DoesNotExist)

Однако глаз коробит, согласен.
?id=ololotrololo';delete from auth_users;


во так например
Джанго экранирует подставляемые значения.
В данном случае нет: джанга попробует привести значение pk к целому (через int</code). У неё это, разумеется, не получится.
ok. но KeyError в определенных случаях здесь обеспечен
Не сообразил =)

Впрочем, удаления тут не произойдёт. Будет некрасивая «пятисотка», но да, это плохо. Согласен.
А разве django не использует preparedStements для запросов?
Нет, не использует, он использует клиенты для бд, которые следуют Python Database API, в котором экранирование параметров осуществляется на клиенте средствами этих библиотек.

В результате SQL-инъекции при запросе через ORM не будет.
Ясно, получается, что в случае MySQL и MySQLdb мы не сможем использовать server-side prepared statements
Ну через ORM нет, а так там есть расширения, только зачем?
Запросы будут бегать быстрее, ведь не надо будет каждый раз строить план выполнения запроса
Зато планы будут неоптимальны, потому как будут строиться без знания конкретных значений.
Откуда такая информация? Как по вашему происходит выполнения запроса в СУБД?
Примерно так — пришедший запрос парсится в какое-то внутреннее представление, потом для него готовится план выполнения. При составлении плана могут использоваться значения в условиях, например, если какое-то условие выберет небольшое количество рядов (по прогнозам), то будет использоваться соответствующий индекс, если много, то просто последовательное сканирование.
Я неверно выразился — под планом выполнения имел ввиду разобраное SQL выражения. Это еще не план. Но уже и не исходный SQL. Т.е. в случае server-side prepared statement второй раз не придется заниматься разбором sql-а, так как внутреннее представление уже есть
Хм, очень странно такое поведение Postgres-а. Ересь какая-то. Просто очень плотно работал с oracle, для которого неиспользование PreparedStatement-ов приводит к очень грустным результатам. Может просто не надо делать оптимизацию на уровне компилирования запроса, а уж если очень хочется — строить общий план, а потом корректировать на основании полученных данных. По крайней мере мы избегаем компиляции запроса, что для сложных запросов даст припрост.
Ну в 9.1, вроде, добавили перестройку плана при получении реальных параметров, но я не вижу где тут можно много времени выиграть — парсинг запроса не такая уж сложная вещь.
Ну у вас получается разобраный запрос во внутреннем представлении и план на основе синтаксиса запроса. Уже будет выигрыш. А если запрос большой? Интерпретация его каждый раз тоже небесплатна
Ну да, но это не так уж и медленно, а вот если план неэффективен, то можно замедлиться сразу в тысячи раз.

Да и большинство юзкейсов покрывают INSERT… VALUES… и UPDATE… VALUES…
Я так понял план неправильно считает постгре, и то как Вы написали-они поправили это дело. Mysql строит не весь план в случае prepared statements. Так что то, что preparedstatements медленные в постгре можно отнести к недальновидности разработчиков постгряшного оптимизатора.

Что касается юзкейсов — Вы почему то опустили селекты, которые в данном случае намного интереснее. Если у Вас селект в полстраницы — его разбирать уже не так быстро.
500-ку можно сделать красивой. Выдавать 500 в н. ештатной ситуации вполне адекватно. Более того, если возникнет KeyError, то это будет означать, что проблема не здесь, а где-то ещё, и замалчивать её неправильно. DoesNotExists наоборот надо ловить, потому как оно отражает штатную ситуацию — устаревшую ссылку
500-ка это внутренняя ошибка сервера. Ключевое слово — внутренняя. То есть если БД упала, например, или место на диске закончилось. Как-то нехорошо, когда внутренняя ошибка провоцируется внешним воздействием.
Вопрос восприятия, у нас концепция такова, что 500я должна выдаваться если произошло что-то неожиданное, что не было предусмотрено. И одновременно уходит письмо разработчикам. Такой подход позволяет быстро находить и чинить многие баги.
В этом случае любой кшольник-кулхацкер, запустивший сканер поиска ошибок, просто засыпет спамом :-) А если отчёты будут отправляться синхронно, ещё и отказ в обслуживании вызовет
Когда запустит тогда и будем смотреть, и адекватно отвечать. Решать проблему до её возникновения в корне неверно. А если просто ловить все такие ошибки, то это может привести к замалчиванию неправильного поведения, битых ссылок и т.п.

Опыт показывает, что при замалчивании ошибок искать такие проколы по жалобам пользователей довольно сложно и небыстро.
Решать проблему заранее, может, и не следует. А вот от канонических дыр лучше бы закрыться. Кстати, забыл спросить, а почему в том примере айдишник берётся из request.POST?
Бывало такое, что просыпаешься, а на почте 1500 писем с одной и той же ошибкой. Ну и что? Ошибка быстро исправлена, вреда эти 1500 писем не принесли (Gmail и миллион бы пережил).

А если замалчивать ошибку, то они и дальше бы оставались на сайте. В конце концов что важней — работающий продукт или чистота инбокса?
Работающий продукт важнее. Поэтому и надо ошибок не допускать. Понятно, что от них никто не застрахован, но самые основные лучше зарубать на корню.
Уверен, что у большинства есть своя bulletproof-библиотека с полезными функциями. Но досадно, что народ с завидным упорством плодит всё новые и новые пакеты: django-annoying, django-extensions, handy. А ведь функциональность зачастую перекрывается. Тот же render_to или json_response не реализовывал только ленивый.

И вместе с тем, очень многие из этих библиотек словно из прошлого века. В смысле, заточены под старые версии Джанги.

За упрощение жизни: многие в повседневной жизни используют CBV. Для них катастрофически не хватает микшинов с функциональностью, аналогичной декораторам. Или абстрактных моделей, в которых уже добавлены какие-то полезные колонки, которые приходится каждый раз добавлять руками (типа date_created).
> Для них катастрофически не хватает микшинов с функциональностью, аналогичной декораторам.
Эти иногда пользую github.com/brack3t/django-braces
Спасибо. Но даже если и использовать, всё равно как-то грустно, что придётся подключать django-braces для микшинов, django-annoying для get_object_or_None и django-extensions для чего-нибудь ещё.

Вот бы один метапакет, в котором это всё в одном месте было… Не совсем джанго-вэй, конечно, но его можно разбить на аппы и подключать только нужное:

INSTALLED_APPS = (
    'metapacket.views',
    'metapacket.shortcuts',
    'metapacket.decorators',
)


Вроде и красиво, и зависимостей не плодит.
Некрасиво, что декоратором render_to нельзя воспользоваться, не поставив пустые скобки. Посмотрите на login_required, например.
Декоратором render_to вообще пользоваться моветон, на мой взгляд. Ладно бы жили в каменные времена версии 1.2, когда это было вынужденной мерой: тогда шорткаты были длиннее собственно функций, на которые ссылались.

Начиная с 1.3 есть django.shortcuts.render. А в 1.2, кстати, можно было импортировать direct_to_template as render. Получалось вполне себе универсально.
Декоратор render_to принимает аргументы, поэтому нужны скобки.
login_required тоже. И хотя «Explicit is better than implicit», есть ещё и «Beautiful is better than ugly» (The Zen of Python). Кроме того мы находимся в контексте джанги, поэтому стоит соблюдать единообразие. И третий аргумент — такое поведение реализуется довольно просто: django/contrib/auth/decorators.py. Поэтому мне кажется, что такой подход будет удачнее.
Я думал об этом, но в конце концов не стал реализовывать, это усложнит реализацию и сделает поведение не таким очевидным, стоит ли оно того, чтобы не писать пары скобок?
Декораторы render_to и json_response есть в django-annoying. Полезная библиотека. Но насчет render_to — это уже действительно устарело. Сейчас можно написать просто:
return render(request, 'template/template.html', {'a':2,' b':3})

По мне это как то более… не знаю как сказать. Нагляднее что ли.
И если у тебя несколько выходов из вьюхи, то придётся имя шаблона повторять, либо вынести в переменную. Так что всё спорно.

Это просто другой способ смотреть на вещи — отдельно логика, отдельно рендеринг. Кроме того, @render_to() предлагает более высокий уровень факторизации, например похожие страницы для зарегистрированных и для анонимов могут использовать общую логику:

def _edit(request):
    # логика редактирования какой-нибудь штуки
    # будет использоваться повторно

# создаём вьюхи из логики и других аспектов
my_edit = login_required(render_to('my/edit.html')(_edit))
edit = render_to()(_edit)
Если у вьюхи несколько выходов, то почти наверняка лучше использовать CBV: отлаживать и писать тесты намного проще.
Если несколько выходов на один и тот же шаблон, значит у вас формируется несколько различных вариантов параметров контекста. Логично значит внутри условий добавлять в контекст параметры, а потом в конце функции отрендерить данный контекст при помощи шаблона.

Если же у вас посередине функции в рендомных местах стоят return-ы, то эта функция по определению нуждается в рефакторинге.
Ну структурном программировании можно долго спорить, я не считаю его такой уж безусловно правильной идеей. По мне, код должен отражать, то как удобно думать о решении, и если удобней думать «если так, то сразу выкидываем, если сяк, то выводим сообщение, а иначе — обычная обработка», то и код должен выглядеть именно так.

Каждый раз когда мы не выходим из функции сразу, мы вынуждены вводить новый элемент состояния (переменную/флажок), который занимает внимание, читающего такой код, программиста, а это весьма ограниченный ресурс.
Хотя шорткат render(), конечно, уменьшает надобность в @render_to().
Буду занудствовать.

1. Вот такое:
<hh user=render_to>()
def foo(request):
   return {
   }


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

2. last_modified — а если в вьюха уже установила Last-Modified?

3. class StripWhitespace(object):
компиляцию регеспов вынести в модуль.

4. render_to_email; if settings.DEBUG:
Зачем? есть же console.EmailBackend

В общем, еще пилить и пилить :)

Ну а в целом — кнопка watch уже нажата, удачного развития!
1. Один из вариантов декораторов — это композиция, и естественно они будут менять семантику декорируемой функции. foo здесь уже не вьюха, а только её часть и если думать о ней так, то вполне логично, что и выглядит она по другому.

2. Такой проблемы не возникало, а следовательно и решать её преждевременно.

4. console.EmailBackend выводит на консоль уже кодированное письмо, т.е. русский текст не прочитаешь. Решение конечно ad-hoc, правильнее было бы свой EmailBackend написать.
В целом спасибо за конструктив )
Честно говоря, я против того, чтобы декоратор менял тип и логику выходных данных функций. Декоратор — как приправа, должен делать функцию «вкуснее», однако в его отсутствии блюдо все равно должно быть съедомным.

Например,
ajax
def enable_post(request):
...
raise ajax.error('permission_denied')


легко может быть заменено на:
def enable_post(request):
...
return ErrorAjaxResponse('permission_denied')


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

P.S. Можно использовать <source lang="python">...</source> для подстветки кода и &#x40; для создания собак.
Разделение логики и представления это хорошо. Тут все верно. Но причем тут декораторы?

Декор — (от лат. decorare — украшать) — дополнительные элементы в живописных композиционных сюжетах.


Т.е. когда у Вас есть здание и вы его декорируете — это значит что вы делаете красивым готовое решение. Вы же не поручаете декоративным гипсовым колоннам поддерживать потолок. Без декора здание будет некрасивым — однако оно будет, и оно будет работать.

А для разделения логики и представления есть модули, инкапсуляция, наследование, функциональные паттерны и другие вещи. Да и return SomeCostomResponse() даже короче в написании чем raise SomeCustomError в функции с дополнительной оберткой.
Кстати для шаблонов есть вполне красивое решение от самой джанги:
class MyCustomView(TemplateView):

    template_name = 'my_custom_template.html'
    def get_context_data(self, **kwargs):
        return {
            'bar': Bar.objects.all()
        }


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

Simple is better than complex.
Flat is better than nested.

Другими словами если функция делает дело, то создавать класс, который| содержит несколько методов, которые сделают дело просто излишне.
Ну может декораторы и не совсем правильное название, но они делают дело, да и не я их называл. Так что аргумент не принимается.

Что же качается return и raise, то где вы увидели SomeCustomError()? А использование исключений позволяет выносить отдельные вещи в подфункции, декораторы и возвращать {«success»: true} просто по успешному завершению функции. В остальном же дело вкуса и спорить об этом глупо.
Зачем все это в одном пакете? Почему не в нескольких? Вообще же никакой связности.
Связность есть — мелкие нужные штуки, слабая, конечно, но поддерживать кучу пакетов, а соответственно привносить кучу сущностей — тоже сомнительное решение.
Хм, а в чем именно сомнительность? Всегда был уверен, что куча маленьких узкоспециализированных аппов лучше одного монструозного.

Еще, по поводу StripWhitespace middleware. Чем {% spaceless %} не угодил?
Он решает несколько другую задачу, да и засорять свои шаблоны не хочется. Использование middleware чище.
Этот тег отлично наследуется, так что достаточно одного раза в базовом шаблоне. Но даже если вы уверены, что middleware чище, почему бы не использовать django.utils.html.strip_spaces_between_tags, зачем писать эту логику самостоятельно?
У меня пробелы срезаются иначе. Во-первых, не только между тегами, а во-вторых один всегда оставляется (чтобы ненароком не поломать вёрстку в IE).
> DAYS = zip(range(7), 'Sun Mon Tue Wed Thu Fri Sat'.split())

можно заменить на

DAYS = tuple(enumerate('Sun Mon Tue Wed Thu Fri Sat'.split()))
Sign up to leave a comment.

Articles