Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Об организации кода в django-приложениях либо толстые модели – это восхитительно

Anna | 15.06.2014 | нет комментариев
От переводчика

Как неизменно свободный перевод увлекательной статьи о определенном подходе к организации кода в django-приложениях. Будет пригодна:

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

Большого числа кода не будет, статья по большей части дискуссионная. Энжой)

image
Толстые модели.

Интро

Множество django-туториалов и примеров по этому фреймворку подают дрянный пример новичкам в плане организации кода в их планах. В случае крупного/долгоиграющего приложения, конструкция кода, подчерпнутая из сходственных примеров, может стать поводом нетривиальных задач и трудных обстановок в процессе разработки. В данной статье я опишу альтернативный, довольно редко встречающийся подход к организации кода, тот, что, верю, покажется вам увлекательным.

MVC в django = MTV встроенное C

Пытались когда-нибудь объяснить как устроено MTV в django, скажем, RoR-девелоперу? Кто-то может подумать, что образцы – это представления, а представления – это контроллеры. Не вовсе так. Контроллер – это встроенный в django URL-маршрутизатор, тот, что обеспечивает логику запрос-результат. Представления необходимы для представления необходимых данных в необходимых образцах. Образцы и представления совокупно составляют «презентационный» слой фреймворка.

В сходственном MTV много плюсов – с его поддержкой дозволено легко и стремительно создавать типовые и не только приложения. Тем не менее, остаётся неясным где должна храниться логика по обработке и правке данных, куда отвлекать код в каких случаях. Давайте оценим несколько различных подходов и посмотрим на итоги их использования.

Логика в представлениях

Запихнуть всю либо огромную часть логики во вьюхи. Подход, особенно Зачастую встречающийся в разных туториалах и у новичков. Выглядит как-то так:

def accept_quote(request, quote_id, template_name="accept-quote.html"):

    quote = Quote.objects.get(id=quote_id)
    form = AcceptQuoteForm()

    if request.METHOD == 'POST':
        form = AcceptQuoteForm(request.POST)
        if form.is_valid():

            quote.accepted = True
            quote.commission_paid = False

            # назначаем комиссию
            provider_credit_card = CreditCard.objects.get(user=quote.provider)
            braintree_result = braintree.Transaction.sale({
                'customer_id': provider_credit_card.token,
                'amount': quote.commission_amount,
            })
            if braintree_result.is_success:
                quote.commission_paid = True
                transaction = Transaction(card=provider_credit_card,
                                          trans_id = result.transaction.id)
                transaction.save()
                quote.transaction = transaction
            elif result.transaction:
                # обрабатываем ошибку, позднее таск будет передан в celery
                logger.error(result.message)
            else:
                # обрабатываем ошибку, позднее таск будет передан в celery
                logger.error('; '.join(result.errors.deep_errors))

            quote.save()
            return redirect('accept-quote-success-page')

    data = {
        'quote': quote,
        'form': form,
    }
    return render(request, template_name, data)

Предельно легко, следственно на 1-й взор притягательно – каждый код в одном месте, не необходимо напрягать мозг и что-то там отвлекать. Но это только на 1-й взор. Сходственный подход нехорошо масштабируется и стремительно приводит к потере читаемости. Я имел блаженство лицезреть представления на полтысячи строк кода, от которых даже у матерых девелоперов сводило скулы и сжимались кулачки. Толстые вьюхи ведут к дублированию и усложнению кода, их трудно тестировать и дебажить и, как следствие, – легко сломать.

Логика в формах

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

def accept_quote(request, quote_id, template_name="accept-quote.html"):

    quote = Quote.objects.get(id=quote_id)
    form = AcceptQuoteForm()

    if request.METHOD == 'POST':
        form = AcceptQuoteForm(request.POST)
        if form.is_valid():

            # инкапсулируем логику в форме
            form.accept_quote()
            success = form.charge_commission()
            return redirect('accept-quote-success-page')

    data = {
        'quote': quote,
        'form': form,
    }
    return render(request, template_name, data)

Теснее отменнее. Задача в том, что сейчас форма для приёма оплаты также занимается обработкой комиссий по крединым картам. Некомильфо. Что если мы захотим применять данную функцию в каком-то ином месте? Мы, разумеется, разумны и могли бы закодить нужные примеси, но вновь-таки, что если данная логика потребуется нам в консоли, в celery либо ином внешнем приложении? Решение инстанцировать форму для работы с моделью не выглядит верным.

Код в представлениях на основе классов

Подход дюже схож на предшествующий – те же превосходства, те же недочеты. У нас нет доступа к логике из консоли и из внешних приложений. Больше того, усложняется схема наследования вьюх в плане.

utils.py

Еще один примитивный и пленительный подход – отвлекать из представлений каждый второстепенный код и перенести его в виде utility-функций в обособленный файл. Казалось бы, стремительное решение всех задач (которое многие в результате и выбирают), но давайте немножко поразмыслим.

def accept_quote(request, quote_id, template_name="accept-quote.html"):

    quote = Quote.objects.get(id=quote_id)
    form = AcceptQuoteForm()

    if request.METHOD == 'POST':
        form = AcceptQuoteForm(request.POST)
        if form.is_valid():

            # инкапсулируем логику в utility-функции
            accept_quote_and_charge(quote)
            return redirect('accept-quote-success-page')

    data = {
        'quote': quote,
        'form': form,
    }
    return render(request, template_name, data)

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

Решение: толстые модели и толстые администраторы

Модели и их администраторы являются безукоризненным местом для инкапсуляции кода, предуготовленного для обработки и обновления данных, исключительно, когда такой код логически либо функционально завязан на вероятности ORM. По сути, мы расширяем API модели собственными способами.

def accept_quote(request, quote_id, template_name="accept-quote.html"):

    quote = Quote.objects.get(id=quote_id)
    form = AcceptQuoteForm()

    if request.METHOD == 'POST':
        form = AcceptQuoteForm(request.POST)
        if form.is_valid():

            # инкапсулируем логику в способе модели
            quote.accept()
            return redirect('accept-quote-success-page')

    data = {
        'quote': quote,
        'form': form,
    }
    return render(request, template_name, data)

На мой вкус, сходственное решение является самым верным. Код по обработке кредитных карт изысканно инкапсулирован, логика находится в релевантном для неё месте, надобную функциональность легко обнаружить и (пере)применять.

Резюме: всеобщий алгорифм

Куда писать код бле@ть? Если ваша логика завязана на объект request, то ей, возможно, самое место в представлении. В отвратном случае, разглядите дальнейший порядок вариантов:

  • Код в способе модели
  • Код в способе администратора
  • Код в способе формы
  • Код в способе CBV

Если ни один из вариантов не подошел, допустимо стоит разглядеть абстрагирование в отдельную utility-функцию.

TL;DR

Логика в моделях улучшает django-приложения не говоря теснее о ваших волосах.

Бонус

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

github.com/kmmbvnr/django-fsm – помощь конечного автомата для django-моделей (из изложения). Устанавливаете на модель поле FSMField и отслеживаете метаморфоза предварительно предопределенных состояний с поддержкой декоратора в духе receiver.

github.com/adamhaney/django-ondelta – примесь для django-моделей, разрешающая обрабатывать метаморфозы в полях модели. Предоставляет API в жанре собственных clean_*-способов модели. Делает именно то, что указано в изложении.

Там же был предложен еще один подход – отвлекать каждый код, относящийся к бизнес-логике в обособленный модуль. Скажем, в приложении prices выделяем каждый код, ответственный за обработку цен, в модуль processing. Сходно с подходом utils.py, отличается тем, что абстрагируем бизнес-логику, а не всё подряд.

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

  • Код в способе модели – если код относится к определенному инстансу модели
  • Код в способе администратора – если код затрагивает всю соответствующую таблицу
  • Код в способе формы – если код валидирует и/или предобрабатывает данные из запроса
  • Код в способе CBV – то, что относится к request и по остаточному тезису
  • В utils.py – код, не относящийся напрямую к плану

Обсудим?

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB