Мне кажется, каждый специалист должен иногда ругаться на средства, которыми пользуется. Даже если средство все из себя хорошее и замечательное, надо постараться накопать там что-нибудь вонюченкое :-).
Я покопался в Django и нашел N вещей, которые не нравятся в нем лично мне. Не претендую ни на объективную оценку их относительной важности, ни на полноту этого списка. Мне вообще кажется, что это больше всего повеселит поклонников каких-нибудь других фреймворков :-).
- ORM не поддерживает агрегацию
-
Очень часто хочется сделать вещи типа "список всех статей с количеством комментариев к ним", "список юзеров с количеством сообщений от них" — то, что делается через "group by" и "count()". В джанговском языке запросов нет прямого средства для этого, и это очень плохо, потому что очень легко побуждает писать в шаблонах дико неэффективный код:
{% for article in articles %} {{ article.subject }}, {{ article.comment_set.count }} {% endfor %}
Он будет делать отдельный запрос с count на каждую строчку.
- HTML escape не включен по умолчанию.
-
Идеология "не додумывать лишнего за пользователя" вышла боком в том, что в шаблонах все переменные по умолчанию выводятся без экранирования HTML. Писать в каждом месте
{{ var|escape }}
очень легко забывается, и приводит к появлению XSS-уязвимостей. Но самая главная проблема в том, что это сейчас невозможно починить просто, потому что уже написана куча кода, который работает в предположении, что этого нет, а значит будет делать двойное экранирование. Придется чинить сложно. - Авторизация завязана на модель User
-
Модель User всегда была предназначена для использования в админке. И только потом стало понятно, что сам процесс авторизации — это отдельная удобная штука, которая пригодится и за ее пределами. Авторизацию худо-бедно выдрали, и она даже подходит для общего использования, но она слишком многими вещами завязана на модель User, которую нельзя по-человечески расширить, и приходится пользоваться разными ухищрениями с профилями.
- Conditional get бесполезен
-
Есть такая штука — ConditionalGetMiddleware. Она умеет обрабатывать клиентские запросы с заголовком If-Modified-Since и отдавать короткий ответ "Not Modified", если содержимое не менялось. Это очень эффективная техника кеширования динамических сайтов, которые нельзя кешировать на жесткие промежутки времени. Эффективна она, причем, не столько для клиента, сколько для сервера, который может вообще не генерировать тяжелый динамический контент, если известно, что он не менялся. Но эта middleware как раз этого и не дает сделать: проверка времени последнего изменения делается после безусловной генерации ответа. То есть клиента от перекачивания ненужных данных спасает, а сервер от их генерации — нет.
P.S. Хотя у меня есть идеи для Cicero, как сделать conditional get более интеллектуальным и полезным.
- Байтовые строки по умолчанию
-
Родовая травма софта, написанного в ASCII-мире, не минула и Django. Он изначально был написан с использованием обычных байтовых строк вместо unicode, что означает появление периодических проблем с разными старыми кодировкам, upcase'ами, отсечением символов и подсчетом длины строки.
Однако! Некоторое время назад таки был создан UnicodeBranch, в котором Малколм Трединник переводит Django на внутренне использование юникода. Я там тоже периодически участвую. Бранч очень близок к завершению, поэтому я призываю всех интересующихся скачать его и потестировать на своих проектах.
- Context в render_to_response
-
Render_to_response задумывался как удобный способ сократить пять строк в одну в очень частом случае возврата ответа из view. Случай действительно очень частый, и все им пользуются. И надо ж так было случиться, что эта удобная функция не использует RequestContext, который передает в шаблоны данные по умолчанию, и тоже используется практически всегда. В итоге, практически любой вызов render_to_response сопровождается либо дописыванием одного и того же лишнего параметра, либо порождает разные автоматизирующие решения. А хотелось бы, чтобы это было просто сделано правильно.
- Create/Update_object принимают модель
-
Generic views — замечательная штука, которая позволяет не писать одинаковый код (вывод списка объектов, вывод одного объекта, вывод архива за год и т.п.) Create_object и update_object призваны автоматизировать обработку форм: вывод формы с начальными значениями, прием данных, валидацию, повторный вывод при ошибках, сохранение данных в базу. Все это они делают, достаточно только указать модель данных.
Однако на практике получается, что редко какая форма работает только с одной моделью. Она может принимать данные для нескольких моделей, может вообще не связываться с базой, а например отсылать письмо о начале регистрации... В итоге лично у меня во view полно этой самой обработки форм, практически одинаковой, только формы разные.
А вот если бы вместо модели эти generic view принимали класс формы, все бы было гораздо более шоколадно.
- Upload через память
-
Ну и напоследок. С самых давних времен весь upload файлов в Django происходит с полной загрузкой этого файла в память. Для аватарок работает, для архивов с музыкой — не работает. Я в свое время правил это в своих проектах, но тот патч в Django не попал. Вместо него разрабатывается очень долго и трудно новое мега-решение, которое, к сожалению, пытается решать много проблем сразу вместо одной. Но его наверное все таки включат, потому что в итоге это нужно.
Вот. N получилось равным 8.
Комментарии: 31
Хотел написать, что 7 - мне пофиг. А потом понял, что как раз не пофиг - я ж им потому и не пользуюсь. ;) А вообще да, всё поддерживаю, остальные все - у меня в моём списке недостатков есть. :)
А "N вещей, которые я люблю в Django" будут? ;) или уже были?
Вот они: http://softwaremaniacs.org/blog/category/django/
Их там будет еще много :-).
С вашего позволения посмотрю на этот пост с точки зрения Java.
Hibernate имеет судя по всему лучший язык запросов (по крайней мере в мире Java). И агрегацию в том числе.
JSPX.
Аналогично, обрабатывать ручками.
Ни на что не завязана, можно делать любую.
Внутри везде UTF, в некоторых местах при чтении извне по умолчанию используется системная кодировка, приходится явно задавать UTF.
Вот это очень странное решение. Первый раз вижу что такое вообще используется.
Я бы добавил еще то что джанго не умеет делать это - http://code.djangoproject.com/wiki/SchemaEvolution.
Это просто глупо. Какой-такой Java? Статья про Python или что? Она про фреймворк, а не про ЯП.
О чём тут вообще речь? О Java'е, что ли? А почему было не подумать, что на Python'е можно тоже что угодно сделать, и это проблема только лишь Джанги?
А в примера с комментариями для статьи select_related() разве не помогает?
Нет, select_related выбирает только вверх по родительскому дереву. То есть если выбирать комментарии, то с помощью select_related можно выбрать в одном запросе и их статьи, с которыми они связаны. Принципиально здесь то, что эта выборка не увеличит размер выборки комментариев, потому что у одного комментария не может быть много статей.
В обратную же сторону, когда нужно выбрать статьи, если их просто помножить на таблицу комментариев, то в запросе статей станет больше, они будут повторяться (столько раз каждая, сколько у ней комментариев). Чтобы этого не происходило, надо делать группировку по статьям, а этого джанговский db-api делать не умеет.
Давайте без крови :-). Мнение человека из Java-мира всегда ценно, потому что мнение питоновского мира мы и сами знаем.
Но по сути, да. Я описывал проблемы, не вдаваясь в тонкости, поэтому некоторые сравнения с Java в том комментарии немного не о том.
Я б так жить не смог :)
А как тогда выходить из ситуаций с агрегацией, писать самому SQL запрос?
Дело в том, что мне как раз пример с комментариями надо будет сделать и я вот призадумался, точнее, для этого конкретного все же надо кол-во комментариев хранить в таблице самих статей, как это рекомендует MySQL cookbook, но общий вопрос интересен.
Я обычно после получения списка объектов из queryset'а делаю вручную еще один запрос с "group by", который отдает IDшки и count'ы. Потом каждому объекту из первого запроса приписываю в цикле эти count'ы. Получается два запроса вместо одного, но это все равно O(1) вместо O(n).
Вообще, ORM в django очень примитивный, предназначенный для какого-то узкого класса задач. Мне почти всегда проще написать прямо sql-запрос; организовать результаты запроса в объекты - дело пары строк.
2crash: да, брать и писать SQL-запрос. SQL - это ведь язык (запросов), и я вообще с трудом понимаю, зачем на язык громоздить ещё один язык (API моделей).
SQLAlchemy в этом смысле, по крайней мере, честно говорит: внутре - SQL.
А единственная причина использовать API моделей - это использование готовых частей django, которые используют API моделей (и, как заметил Иван в (3) и (7), делают это не совсем хорошо и правильно).
К сожалению не имею опыта работы с Django, но новые технологии всегда интересны. Меня смутили два пункта из Вашего перечня:
Минус ли это? Попробую описать ситуацию.
Предположим, что в шаблон передается строка со следующим содержанием:
Обратите внимание, что текст многострочный. По вашему убеждению внутри шаблона, где бы она не встречалась эта последовательность символов должна быть автоматически заменена на:
"Click Me
Click Me"
На первый взгляд все правильно. Но давайте представим, что этот текст должен выводится внутри alert() и что получится?
alert("Click Me
Click Me")
Этот код, естественно, вызовет ошибку JavaScript. На самом деле строка должна была быть экранирована так:
Таким образом оказывается, что обработчик шаблонов должен еще и отслеживать контекст, в который будут подставляться значения переменных. Не окажится ли такое решение слишком трудоемким?
Но и такой "умный" обработчик не справится с ситуацией, когда переменная содержит HTML, который должен быть отображен "как есть" (например, новость, набранная в WYSIWYG-редакторе).
Вы намекнули, что у вас есть решение этой проблемы. Было бы интересно узнать подробности.
Про render_to_response очень жаль, что ему надо RequestContext постоянно подсовывать, ведь HttpResponse это и есть ответ на HttpRequest.
И про агрегацию тоже не понятно. Ведь даже в газетных сайтах наверняка нужно что-нибудь считать.
2 undebugger: ORM позволяет абстрагироваться от какой-то конкретной версии хранилища данных. Если ты пишешь, скажем, только для MySQL, то можно и вручную запросы писать, а если собираешься поддерживать несколько СУБД?
Спасибо за отличную статью.
Чтобы отделить мух от котлет — питон-код от кода SQL. Не знаю как вам, а мне значительно приятнее писать на чистом языке, без примесей.
Да и Django тоже не заставляет делать всё через свой ORM. Менеджеры моделей — очень хорошая штука.
P.S. кстати, может быть, после братания Django и SQLAlchemy возможности работы с БД станут гибче?
Я веду речь только об экранировании по умолчанию. Оно должно быть HTML'ным, потому что это самый частый случай, и потому что не делать этого небезопасно. Но средства отключить экранирование или сделать другое экранирование быть, безусловно, тоже должны.
Я напишу подробно, когда реализую, но вкратце — это должен быть декоратор для view, принимающий отдельную функцию для быстрого взятия времени обновления или ETag'а.
Думаю, самое слабое место в Django на сегодняшний день - это всё-таки schema migrations.
Учитывая нынешнюю моду на динамические страницы, у разработчика остается возможность "забыть" о правильном экранировании данных, передаваемых в JavaScript и получить тот-же самый XSS, только через скриптовый язык. Если же добавить экранирование, то это будет только способствовать "привыканию" к тому, что "умный фреймворк делает все за нас" и заботиться о безопасности нет никакой потребности.
Очень интересно будет почитать.
Интересно, со многим согласен.
По поводу #7, думаю, это стделать не сложно, generic views всо равно нужно переписать под newforms, но судя по тому, что разработчикам не нравится backwards- compatible изменения, этого не будет.
Спасибо за статью. Со всем изложенным в ней полностью согласен. Про schema evolution не сказал ;-)
Вот послушайте еще одно мнение Java-разработчика, правда бывшего, а теперь пересевшего на Python. Всякие там хибернейты приходят и уходят, а реляционные базы остаются. Мне, например, было удобнее работать с iBatis, чем с Hibernate как раз именно потому, что там можно было просто написать какой нужно SQL, вместо прыжков с бубном вокруг шибко умного API.
Djangoвский ORM мне нравится за простоту и близость к БД. Проблему с агрегацией я решаю так: (в общем случае) создаю агрегирующий View, затем делаю запрос через ORM с такой добавкой:
Вариации: материализованный view, сабселект в параметре tables, ну.. много что еще можно придумать.
Schema evolution - в бобруйск. Если у вас фреймворк принимает решения о структуре базы, то я вашей базе глубоко соболезную. Триггеры там всякие с хранимыми процедурами, куда и как они должны эволюционировать?
А вот насчет HTML escape - это правда. Как хорошо в Zope это было сделано!
А в формах для поля CharField нет возможности автоматически обрезать пробелы. Хорошо бы иметь параметр strip_spaes = True/False, который бы регулировал обработку поля. У меня в проекте, например, куча форм и нет ни одной где такая обработка не нужна. Задача настолько распространенная, что просто удивляет отсутствие подобного атрибута.
Ну собственно я думаю вам теперь надо идти на сайт django, сделать патч и создать тикет, реализующий такую фичу. Его я думаю примут. Ну а мы, остальные, будем пользоваться.
Это же не нечто глобально вроде escape или schema evolution.
Да, везде свои "тонкости" :)
Вообще все эти запросы с GROUP BY, по моему, не слабо сервак загружают.
Честно говоря, давно не занимался программированием, но сейчас "приспичило".
Наверное, я буду делать так (даже если данный недостаток исправят):
В основной таблице заведу еще одно поле - счетчик. Его и буду показывать в различных списках/формах просмотра.
Обновлять содержимое этого поля буду при добавлении/удалении записей в подчиненной таблице (с комментами к статье, например). И только тут, для получения актуального количества комментариев (например), буду делать запрос.
По моему плюсы: