Перейти к содержимому

Разработка Odoo: от оценки до боевого сервера

18 сентября 2026 г. от
Разработка Odoo: от оценки до боевого сервера

Оценка разработки Odoo - это обычно одно число: часы. О том, что происходит внутри этих часов, оно не говорит ничего. А решается там ровно одно: будет ли модуль Odoo работать после следующего подъёма версии.

Поэтому - порядок работы на моих проектах. Почти всё в нём - шрамы, оставшиеся от версий с 15 по 19.

До модуля Odoo появляются два документа

Первый - замысел: что система должна делать иначе в тот день, когда работа закончена, и написано это языком бизнеса. «Кладовщик сканирует код, и строка закрывается» - замысел. «Добавить поле в stock.move.line» - нет, это уже ответ, и обычно неверный. В девяти случаях из десяти сканированию нужно правило штрихкодовой номенклатуры и переопределение существующего перемещения, а не новое поле где бы то ни было. Замысел должен держаться на языке кладовщика достаточно долго, чтобы это было ещё видно.

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

Какие существующие модели затронуты и хватит ли наследования. Что добавляется: поле, метод, представление, маршрут. И что остаётся нетронутым. Какое штатное поведение переопределяется - и та ли это вообще форма переопределения: _inherit расширяет модель на месте, а _inherits тихо добавляет внешний ключ и делегированную запись, и вот так в системе заводится лишняя строка на каждый товар, а почему - уже никто не помнит.

На какой редакции работает система, потому что замысел, построенный вокруг модели из Enterprise, на Community - не замысел. Сколько компаний, сайтов и языков должна тянуть одна инсталляция: сорок сайтов в одной инсталляции Odoo - это другая архитектура, чем один.

И вопрос, который пропускают надёжнее всего: откуда берутся данные и что делает интеграция Odoo в три часа ночи, когда вторая сторона перестаёт отвечать. «Повторяет два раза, потом откладывает заказ и пишет мне на почту» - это архитектура. Тишина - тоже архитектура, просто её никто не выбирал.

Архитектуру читают до того, как её построили

Когда эта страница есть, вы её видите. Решения, не код: вот эти три модели трогаем, эту - нет, интеграция Odoo идёт через очередь заданий, поэтому неудачный вызов к поставщику превращается в запись, которую можно повторить и на которую можно посмотреть, а всё по расписанию работает как ir.cron, который отдаёт долгую работу в очередь, - потому что процесс крона убивают, когда он упирается в лимит времени.

Разбор занимает около часа, и это последний дешёвый момент передумать. Передумали после реализации - платите за неделю. Передумали после запуска - платите ещё и за две недели, которые склад вводил всё по два раза.

Здесь же я говорю то, что стоит мне денег: иногда честный ответ - Studio, или модуль из магазина приложений, который поддерживает кто-то другой, или вообще ничего. Odoo это уже умеет - плохо, но сносно, - а свой модуль добавит год поддержки ради десяти сэкономленных минут в месяц. Свой модуль Odoo - самый дорогой из этих вариантов, и выигрывать он должен по существу.

Реализация: наследовать модель Odoo, а не копировать

Правило скучное: расширять, никогда не копировать. Скопированный штатный файл прекрасно работает до обновления, а после - он ваш навсегда.

Честное исключение одно. У некоторых штатных методов нет шва: нечего обернуть в super(), не за что зацепиться. Когда такой метод приходится заменить целиком, в копии стоит комментарий с именем исходного файла, веткой и версией, откуда она взята, - чтобы на следующем обновлении было что с чем сравнить, а не загадка.

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

Две ловушки стоит назвать, потому что обе находятся ценой выкладки. Наследование представления не примет произвольный атрибут как селектор: xpath по aria-label отвергается сразу. А hasclass(...) не находит ничего, когда родительский шаблон собирает этот класс через t-attf-class, - потому что атрибута class в исходнике просто нет. Обе ошибки вылезают только при установке модуля, то есть после того, как статические проверки были зелёными. Я читаю сырой XML того шаблона, который расширяю, и никогда - отрисованный HTML, где классы уже склеены и сломанный селектор выглядит правильным.

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

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

Тесты и что здесь на самом деле делает ИИ

Каждое нетривиальное изменение получает тесты: TransactionCase на логику ORM, тур HttpCase, когда меняется экран, и оба с меткой post_install, чтобы они шли против тех модулей, которые реально будут стоять рядом с моим.

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

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

Два правила вокруг этого. Сгенерированный тест должен сначала упасть и только потом пройти: если я не могу сломать код и увидеть красное, тест не проверяет ничего, а сгенерированные тесты очень убедительно не проверяют ничего. И фикстуры - это выдуманные записи, а не выгрузка ваших клиентов.

Стенд на реальных данных, потом боевой сервер

Изменение сначала едет на стенд, на копию настоящей базы. Демоданные вежливы. В них нет клиентов с двумя пробелами в имени, нет товаров, загруженных годы назад по правилам, которых никто не записал, нет счетов в валюте, которую вывели из обращения. И объёма в них нет: поиск, отвечающий на демоданных мгновенно, на ста десяти тысячах товаров занимал восемь секунд.

Копия вашей базы - всё ещё ваша база, с вашими клиентами внутри, поэтому перед работой её обезвреживают: исходящая почта выключена, задания по расписанию выключены, платёжные провайдеры - на тестовых ключах. Иначе первый же прогон процесса на стенде напишет тремстам живым людям с сервера, которого вообще-то не существует.

На стенде работу проверяет тот, кто её просил, на своих собственных записях. Потом боевой сервер, по написанному порядку: какой модуль обновляем, какой скрипт миграции идёт до обновления, а какой после, и что происходит с данными, которые уже лежат в базе. Опубликованные материалы - обычный сюрприз: запись, загруженная с noupdate, не перезаливается, что с ней ни делай, поэтому текст, который уже живёт на сайте, меняют записью в саму запись из миграции - или никак. И откат, который я хотя бы раз действительно прогонял. Предполагаемый откат - не откат.

Дефекты, которые вылезают только на живом сайте

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

Баннер согласия записывал ответ посетителя в неверном формате. «Только необходимые» записывала корректный JSON, «Принять» - строку true - потому что полное согласие перезапускает взаимодействия страницы, и обработчик закрытия окна отработал на свежем экземпляре, всё ещё державшем значение по умолчанию. На сервере проверка, читающая эту куку, ждёт словарь и удаляет всё остальное: ветка совместимости делает ровно то, для чего её написали. В итоге каждого, кто нажал «принять», спрашивали снова на следующей странице, а аналитика за этим стояла на нуле, выглядя при этом прекрасно установленной.

Второй прячется в экране настроек. Можно ли грузить счётчик, решает метод, который начинается со слов «если плашка согласия выключена, согласие дано»: логика в том, что владелец, выключивший плашку, собрал согласие как-то иначе. Снимите эту галочку - и каждый посетитель молча считается согласившимся, и так час подряд, потому что отрисованная страница закеширована. Ничто в этом экране настроек не выглядит опасным.

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

Все три нашлись одинаково: открыть живой сайт, нажать обе кнопки, прочитать куку и вкладку сети вместо кода. Это пятнадцать минут, и это единственное место, где такие ошибки существуют. Я считаю их частью работы и выставляю в счёт.

Во что обходится следующая версия

Статья была бы нечестной, если бы советовала спрашивать про обновления, а сама от вопроса ушла. Обновление базы - работа Odoo, и делают они её хорошо. Обновление вашего кода - моя, и цена зависит от одного: насколько модуль стоит на штатном поведении, которое сдвинулось.

Модуль, который добавляет свою модель, несколько полей и пару представлений, обычно переживает большую версию за полдня правок. Модуль, который переопределяет create, правит отчёт и наследует четыре штатных шаблона, - это неделя. Стоит знать заранее: create идёт как @api.model_create_multi и принимает список записей, а не одну, поэтому переопределение, написанное под один набор значений, не падает громко - оно тихо обрабатывает первую строку пакетного импорта и не замечает остальных.

И миграция Odoo идёт версия за версией. Odoo ничего не перепрыгивает, поэтому свой код переносят на каждом шаге, а не один раз в конце.

Права - часть задачи

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

Сильнее всего в чужом коде - и в своём - я ищу sudo(). Он выключает все правила разом, это самый быстрый способ заставить ошибку прав исчезнуть, а в публичном контроллере это утечка данных с удобным адресом.

Доводка после запуска

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

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

Один вопрос, который стоит задать

Ничего из этого вы не вспомните, сидя напротив разработчика. Поэтому вот вопрос, который я задал бы на вашем месте:

Что станет с этим модулем, когда Odoo выпустит следующую версию?

Если в ответе назван конкретный риск - вот это переопределение стоит на методе, который Odoo трогает часто, поэтому его перепроверяют на каждом обновлении, - человек уже делал миграцию Odoo. «Всё будет нормально» тоже бывает правдой: для модуля, который добавляет два поля и список. Слушать стоит одно: может ли собеседник сказать, какой из двух случаев ваш.

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

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

Партнёр Odoo или независимый разработчик: в чём разница на самом деле
Из чего складываются уровни Ready, Silver и Gold и кому платят, когда вы покупаете больше мест.