Дисклеймер

Данный текст не имеет цели кого-то оскорбить или высмеять и несет исключительно развлекательный характер.

Я долго работал в коммерческом проекте в роли backend-разработчика с всемирно известным фреймворком Django, а также с его альтер эго - Django Rest Framework. Всегда кажется, что работать с чем-то многозвёздочном на GitHub - это кататься сыром в масле. На входе имеем: отличную документацию, отзывчивое сообщество, множество решений одной и той же проблемы, так как все уже (пред)решено...

Предыстория

Знакомство с фреймворком произошло достаточно давно. Казалось, что такой швейцарский нож доступен в Java, C++, Go (иногда конечно встречается, но нужно рыть). Везде такая разработка API: вьюхи, модельки, в чем проблема? Поначалу интересно писать по официальному туториалу, как итог - ты сразу видишь визуальный результат. Не скажу, что я тщательно вкраплялся в Джангу при старте, но одни из первых пет-проектов написал на ней. Это продолжалось ровно вплоть до того момента, как пришлось в n-м году искать работу хоть как-то похожую на программирование, и тут я нарываюсь на довольно неплохой оффер для уровня trainee и участие в проектах немалого масштаба. Тут были и Django, и Flask, и FastAPI. Более того, в каждом удалось поучаствовать, но проект на Django c Flask'ом отвалились в недра техподдержки, а зачаток нового проекта пал на благоговенный FastAPI. Да, я люблю гибкие фреймворки, но это не главное. Главное, что Джанго исчез из моего поля зрения, запомнившись мне как панацея ото всего, но почему-то разработчики на тот момент ушли от данного экспоната.

Готовая архитектура = легко разобраться в другом проекте?

Думаю есть плюс, понятно что и где лежит, никаких самописных штук. Но когда проект разрастается, каждый начинает изворачиваться как хочет и разработчики отклоняются от django-way. Поэтому тут две стороны медали, в том же DDD ты видишь бизнес логику, а также адаптеры к ней, и у тебя также не возникает проблем.

Лицом к лицу. Django

Вернуться к пресловутому симбиоту Django + DRF удалось лишь с переходом на новую работу. Не сказать, что я молился на FastAPI или другую технологию, в большинстве своем мне было все равно. Я был готов и в Devops (теперь хайпово знать k8s обычному работяге CRUDоделу), и в Go, и в Fullstack. Но как-то опять повезло, меня вновь вербует определенная корпорация на классический бэкендерский стэк. Какие проблемы могут быть?Спустя долгое время работы по многослойной архитектуре (без прибамбасов, а обычные controller, service, repository) с type hinting в кажд��м файле и различными примочками, которые следят, чтобы код предсказуемо себя вел, у меня сложилось совсем другое впечатление от разработки. Не буду распинаться про DDD, TDD, но я понял одно: в разработке чем проще, тем лучше. С одной стороны, чем быстрее завелось, тем лучше, но в долгосрочной перспективе - чем более предсказуемо ведет себя код, тем меньше ошибок в нем можно допустить. Команде нужно дорасти до TDD, но я также считаю, что работать с Django может только сильная команда, которая знает все тонкости фреймворка (а их настолько много, что грамотно стартануть не получится), а не выбирает его только потому, что это быстрое решение для бизнеса. А еще придется идти "против дзена Django" чтобы код стал действительно хорошим и масштабируемым. Это борьба вечная и не всегда заканчивается успехом.

№1 Шок от исходников

Рандомный класс из исходного код Django
Рандомный класс из исходного код Django

Да, типизации здесь не видать, как и датаклассов, как и других удобных штук, которые повышают качество разработки в x10 раз вместе с читабельностью. Это понятно, потому что фреймворку достаточно много лет и тогда в python не было никакой типизации. Но сегодня мир изменился, python избавляется от своих первозданных идиом в пользу предсказуемости. Современные разработчики начинают привыкать к общепринятой практике ставить везде type hints, а взамен получать подсказки от любимого IDE. Лучше снижать когнитивную нагрузку и подкладывать "подушки безопасности", где это возможно. Конечно низкий поклон тому, кто сделал отказоустойчивую систему в таких условиях, но было бы круто, если бы Django получил адекватную типизацию, это дало бы некий буст и безопасность.

№2 Startproject - пошла магия

django-admin startproject my_project

Django создает такую структуру-матрёшку:

  • Внешняя папка my_project с файлом управленцем manage.py

  • Внутренняя папка my_project, она же хранит все общие настройки и конфиги. Я еще не создал ни одного приложения, а уже должен осознать, что "приложений" почему-то будет много, хотя для новичка не совсем понятно, что за "приложения" такие, ведь мы создаем буквально веб-приложение (в единственном количестве).

  • my_project/my_project/settings.py что здесь храним?

    • магические словари DATABASES, CACHES, которые в недрах фреймворка превращаются в коннекторы к базам данных. Если движок базы данных не входит в стандартный набор Django, вы обречены на поиск сторонних библиотек, которые пытаются приклеиться к джанговской интерпретации, но не всегда обеспечивают ту же дефолтную магию.

    • магические переменные, которые, например, по одному писку меняют поведение транзакций во всем проекте. На днях я общался со знакомым разработчиком на Rust, и он спросил: "Что будет если в транзакции БД какой-то код вызовет таймаут?" Неважно какой здесь дать ответ, когда вовсе не работаешь с транзакциями, потому что весь UoW - это одна строчка в настройках, зато на собеседованиях спрашивают про ACID и уровни изоляции транзакций.

    • SECRET_KEY, который потребуется не забыть перенести в .env. Я частенько об этом забывал, но мешать секреты и конфиги в одном месте - плохая практика.

№3 Startapp

python manage.py startapp app

Вроде бы ничего, появляется папочка заготовок, некоторые из которых разработчик сразу же удаляет, потому что не будет пользоваться основной спецификой фреймворка.
views.py tests.py models.py скорее всего превратятся в директорию, admin.py не будет использоваться вовсе, если пишется стандартный API, а вот urls.py наоборот не хватает в таком случае. Известна же плохая практика написания логики во View, получается нужно еще докручивать какие-нибудь services и в итоге многое захочется переименовать.
Ну, наверное, это мелочи, перейдем к конструктиву во время самой разработки

№4 kwargs.get('key1'), request.GET.get('key2') красиво?

Фреймворк Django предлагает море разных способов получить один и тот же параметр, заставляя нас гадать, какой из них "более ликвидный". А еще обязательно ожидает, что в функции вы укажете request, path_parameter, не говоря уже об обязательном нейминге get, post, patch и т.д., иначе все сломается.

class UserView(View): 
	def get(self, request, user_id, *args, **kwargs):
		# user_id - path параметр
		user_id_1 = user_id
		user_id_2 = kwargs.get('user_id')
		user_id_3 = self.kwargs.get('user_id')
		
		# А так вытаскивается query параметр
		group_id = request.GET.get('group_id')
		
		return JSONResponse(
			{
				"user_id": user_id_1,
				"group_id": group_id,
			}
		)

№5 Готовая админка с сессиями для Hello World

Если вас не пугает то, что сейчас будет мгновением ниже, наверняка вы сейчас где-то на туториале или разрабатываете сайт-визитку. Первичная миграция в базу данных заканчивается дефолтной высадкой целого десанта таблиц в вашей новоиспеченной базе данных:

  • django_migrations

  • auth_user

  • auth_group

  • auth_permission

  • auth_user_groups

  • auth_user_user_permissions

  • django_admin_log

  • django_session

  • django_content_type

Безусловно, это можно отключить:

# settings.py
INSTALLED_APPS = []

Надо кстати не забыть про MIDDLEWARE в settings.py и некоторый косметический рефакторинг, иначе приложение не заработает. Но оно же включено по умолчанию, лучше же ничего не трогать? Когда ты подходишь к бэкенду осознанно, такая "магия" из коробки скорее пугает, чем радует. В голову лезут вопросы: "Откуда это притянулось?", "Сколько там данных есть и будет, и как их чистить?", "Как это безопасно выпилить, чтобы всё не рухнуло?" Контроль над проектом потерян, ты начинаешь искать, кто тебе всучил это и убежал - для этого требуется лишние исследования.

№6 User призрак

Однажды приходит осознание, что пользователя за тебя уже кто-то создал (случай, когда из INSTALLED_APPS ничего не убиралось). За тебя не только сделали User, но и накидали своих полей. Выходит, чтобы писать на фреймворке, нужно заранее убрать медвежьи капканы, которые будут присутствовать примерно всегда. Так переопределим User и не забудем про settings.py, причем много переменных скрыто от пользователя, а за этим пожалуйста в документацию, хотя User для бэкенда - святая святых.

# users.py
from django.contrib.auth.models import AbstractUser 
from django.db import models 


class User(AbstractUser):
	# Полностью удаляем поле из модели
	username = None
	
	email = models.EmailField(unique=True)
	bio = models.TextField(blank=True)

	# Говорим Django, что теперь email — это логин 
	USERNAME_FIELD = 'email'
	
	# Говорим Django, что больше ничего не требовать от меня 
	REQUIRED_FIELDS = []
	
# settings.py
AUTH_USER_MODEL = 'users.User'

Лично для меня поля не совсем предсказуемы, то есть приходится снова брать готовое решение, и убирать лишнюю скорлупу, ведь нам не нужен "username" в качестве логина в 2026? А еще требуется почему-то сменить поле USERNAME_FIELD, а не LOGIN_FIELD или что-либо подобное.

№7 Явное лучше неявного?

Посмотрим внимательно на код. Есть тут название таблицы? А поле id в качестве первичного ключа?

import uuid 
from django.db import models
 
class Product(models.Model): 
	uid = models.UUIDField(default=uuid.uuid4, editable=False) 
	name = models.CharField(max_length=255)

В model.Model уже присутствует скрытый id, а если ваше приложение называлось app, то в базе данных появится таблица app_product, что в общем-то не совсем красиво, и хотелось бы получить некое наказание, а не поощрение за неявность.

Ведь с самого начала тебя не учат строить, тебя учат тому, что все итак работать будет.

№8 Шаблонизаторы - грех фуллстэка

Model - View - Template - такая структура Django до сих пор.
Стандарт, когда вьюха отдает HTML.
Однажды по молодости мне не засчитали тестовое задание потому, что я сделал не API, а нечто подобное:

class PollsDetailView(View):
	def detail(request, question_id):
	    question = get_object_or_404(Question, pk=question_id)
	    return render(request, "polls/detail.html", {"question": question})
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>

Не это хотелось увидеть от бэкендера, не так ли?
Думаю, совсем нет смысла комментировать. Для справки: в новой Django 6 разработчики и дальше прокачивают шаблоны.

Эпилог

К счастью или сожалению не буду раздувать дальше для компактности статьи. Я обязательно вспомню что-нибудь еще, если увижу отклик.

Django - это неподъемный монолит, который в основном кидается HTML. Да, моя ошибка, не понял истинное предназначение фреймворка 2005 года. В нем конечно есть JSONResponse и другие приколы, но как оказалось, фреймворк не предназначен для построения API. То есть вся документация, на которую я опирался будучи новичком и услышав что "Django топ для бэкендеров", оказалась старейшим решением с шаблонизаторами и формами.

Конечно, не беда. Ведь есть Django Rest Framework! Однако чтобы с ним работать, нам потребуется вся родословная Django с ее кучей инструментов, которые лягут мертвым грузом при разработке API. Выглядит уже как-то не исчерпывающе, ведь так?

Продолжение следует за DRF...