Как стать автором
Обновить

Комментарии 6

"Ничего ты не знаешь, Джон Сноу". Джордж Мартин, Буря мечей.

Вроде бы автор и выполнил доскональный разбор кода Django GCBV, но вот так ничего и не понял. А понимание приходит, когда Видами-Базирующимися-На-Классах пользуешься ежедневно несколько лет.

Я уважаю и ценю эту технологию Django из-за ее элегантности пользования и удобства работы с данными в процессе формирования тела response.

Теперь к статье:

Диспетчер URL Django всегда вызывает функцию которая должна вернуть response. В случае найденного совпадения - это будет функция добавляющая в response что-то, иначе это будет функция создающая 404 response, если не сказано другого.

Эти ВСЕ функции называются видами (Views).

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

func(*args, **kwargs); func1(*args, **kwargs); ....

func(*args, **kwargs): return func1(*args, **kwargs)....

Чехарда с локальными переменными в случае нескольких функций приводит к идее использования контейнера для хранения всех необходимых данных. Эту роль в GCBV выполняет видовый класс. Он прокидывается по всем вызовам функций первым аргументом, если не сказано иное.

По главам.

Глава 0. URL dispatcher

Обьявляем класс

class MyFrmView(FormView):
  pass # it should be my class body

myfrmview = MyFrmView.as_view()  # add alias

Как бы выглядел urlpatterns на основе привычного Function-based View (FBV) и на Class-based View (CBV):

urlpatterns = [
		# Function-based View
    path('aboutFunc/', myfuncview),

    # Class-based View
    path('aboutView/', myfrmview)
]

Упс. Я что то делаю не так? Увы все так, реальный пример, когда urlpatterns формируется автоматически только для тех классов у которых в файле прописан превдоним для .as_view()

Глава 1. Class View.

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

согласно django.core.handlers.base для

path('myurl/', view_func, {**initkwargs}, name='updmodel')

вызов диспетчером URL выглядит так:

if url.dispatch == True: 
	ready_view_func = view_function_configurator(**initkwargs)
	return ready_view_func(request, *args, **kwargs).

В итоге, зная как работает Python, пишем следующее:

class myfrmview(FormView):
  
def __call__(self, request, *args, **kwargs):
    self.setup(request, *args, **kwargs)
    return self.dispatch(request, *args, **kwargs)

И тогда в диспетчере URL больше не надо этих отвратительных .as_view():

path('myurl/', myfrmview, name='updmodel')

диспетчер инициирует класс, и вызовет метод __call __.

Увы эта полезная доработка ждет своего часа на полках bugtracing django core, поскольку она ломает в легаси проектах, где декорирование функции указано прямо в url. Но это стало поправимо с появлением method_decorator в Django.

# раньше
path('myurl/', csrf_exempt(myOldfrmview.as_view()), name='updmodel')
# теперь
path('myurl/', method_decorator(csrf_exempt, name='call')(myfrmview), name='myname')

в итоге родной as_view не будет вызываться никогда, вызов будет работать быстрее. и это хорошо.

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

Если автор и начал рассказывать про GCBV, то совсем не сказал зачем они, об их пользе. Cложность и простота GCBV скрываются в осознанном их применении, до которого еще надо дойти.

Идеология GCBV - это совсем не про код модулей django.views.generic, это, в первую очередь, про скорость разработки c неполным следованием в коде принципам SOLID/DRY.

Скорость выигрывается за счет того, что имея набор модулей из django.views.generic я могу создать 99% всех необходимых обработчиков запросов для проекта без написания кода и только обьявлением и конфигурацией видовых классов.

Скорость выигрывается еще за счет того, что это все уже протестировано.

Скорость выигрывается еще и за счет того, что архитектура цепочки вызовов более-менее оптимизирована.

В итоге с GCBV кодовая база проекта получается микроскопической, понятной и простой. И это хорошо!

Автору успехов, видно что он только в самом начале своего творческого пути.

Вы абсолютно правы! У меня нет вашего опыта и я сейчас на самом старте изучения Django, эта статья как раз и есть результат той боли новичка, который сталкивается с куцыми инструкциями в курсах и мануалах, но не понимает, как они работают и что под собой несут.

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

Увы эта полезная доработка ждет своего часа на полках bugtracing django core, поскольку она ломает в легаси проектах,

Я знаю иную историю. Если view сохраняет какое-то промежуточное состояние в свои свойства, то создание единственного экземпляра такого объекта непосредственно в urls.py может быть чревато появлением не особо тривиальных в отладке багов, из-за того, что состояние из одного запроса будет протекать в следующий. Другой причиной было соглашение избегать использования магических методов python в публичных интерфейсах фреймворка. Поэтому вместо варианта __call__ сделали фабричный метод as_view, который на каждый запрос создает новый экземпляр объекта.

Вариант, что не хотят __call __ использовать, подходит больше всего, как объяснение. Это объясняет появление setup вместо __init __.

Разумеется, что:

def __call__(self, request, *args, **kwargs):
  	# забыл инстанс создать
  	self = type('MyCurrentCall', (type(self),), vars(self))()
    self.setup(request, *args, **kwargs)
    return self.dispatch(request, *args, **kwargs)

Иначе получится админ панель Django, где именно синглтоны использованы.

А ещё as_view(**initargs) принимает параметры для кастомизации эндпоинта. Получается что-то в роде partial application для view-функции. Просто в то время в трендах было ООП, наследование и все такое.

в предложенном варианте это учтено.

  1. при старте проекта класс инициализируется MyCls(**initkwargs) из urls.

  2. при вызове __call __ копируется в новый инстанс. self = type(MyCls)(vars(self)).

    В предыдущем ответе перемудрил с созданием нового класса.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации