Работа с данными

Оглавление

Основные сведения

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

Все xml файлы - это файлы с данными. Т.е. представления, пункты меню, экшены, правила безопасности и много другое является данными, которые при установке либо при обновлении модуля записываются в базу.

Информация о каждой записи, созданной с помощью xml файла имеет уникальный идентификатор в системе, который назвается xml_id или external_id. Он состоит из двух частей - техническое_имя_модуля и уникально_имя_записи_внутри_модуля, которые разделяются точкой. И может выглядеть примерно вот так: first_module.record_001_first_model. И хранится информация о записях в модели ir.model.data.

Все это является частью платформы и используется повсеместно в системе. Вы можете ссылаться на уже созданные записи как внутри самих xml файлов так и с помощью кода:

record = self.env.ref("first_module.record_001_first_model")

в переменной record будет рекордсет из одной записи, которая была создана с помощью xml файла:

Данные о записях

Как вы можете видеть, данные о записях, которые имы видели:

Записи

Если мы внимательно изучим данные о записи в xml файле то увидим, что в теге <record> есть атрибут model который указывает на имя модели, в которой будет создана запись. Атрибут id и есть тот самый xml_id, который есть у каждой записи созданной с помощью xml файла.

Внутри тега <record> вы можете видеть теги field со значениями атрибута name. Это имена полей. Значением поля может быть:

  • текст внутри тега field
  • если поле имеет тип many2one, то значение можно указать как ссылку на другую запись с помощью атрибута ref. Разумеется ссылаться можно не любую запись любой модели а только на запись, которая создается для модели, указанной в параметре comodel_name текущего поля. Пример
  • с помощью атрибута eval. В этом случае текст внутри этого атрибута будет исполнен как код python. Пример

Особенность обновления данных

Когда вы разрабатываете модуль, вы будете часть менять xml файлы, а так же обновлять модуль(и) чтобы измененные данные попали в систему. В связи с этим есть один неочевидный нюанс, который может отнять у вас достаточно много нервов и я бы хотел сфокусироваться на нем. К примеру, вы создали вот такую запись:

<record id="record_001_first_model" model="first.model">
    <field name="name">record 001</field>
    <field name="field_one">10</field>
    <field name="field_two">0.1</field>
</record>

В этом случае система, если не найдет такого xml_id создаст новый и создаст запись, в модели first.model с прописанными значениями полей.

Если же мы поменяем какое либо значение одного из полей, то система обновит это поле новым значением.

А теперь вопрос, что произойдет с данными если вы удалите определение одного из полей, например удалите определение поля field_two?

<record id="record_001_first_model" model="first.model">
    <field name="name">record 001</field>
    <field name="field_one">10</field>
</record>

Чисто интуитивно, нам будет казаться, что система удалит данные из поля field_two. Так вот это не так. В поле останется последнее добавленное значение. Т.е. удалив определение поля из xml файла, вы не удаляете значение из него, а просто перестает записывать туда новое значение. Удалить данные из этого поля можно либо вручную, либо создав новую базу, где ваши файлы не будут создавать значение для этого поля, либо создавать запись с помощью функции, которая будет вызывать метод create если записи не существует, либо вызывать метод write если запись существует.

Вызов функций

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


<function
    model="first.model"
    name="start_function"
    eval="([ref('first_module.record_001_first_model')])"
/>

Это самый простой вариант - просто вызывает метод для рекордсета, который

 <function model="product.template" name="_create_variant_ids">
    <function
        model="product.template"
        name="search"
        eval="[
            [
                ('id', '=', ref('имя_модуля.искомый_id_записи')),
            ]
        ]"
    />
</function>

В этом примере вы можете видеть, что функция _create_variant_ids будет вызвана для рекордсета, который получится в результате вызова во вложенной функции метода search. В этом и предыдущем случае можно увидеть, что позиционный параметр функции можно передать с помощью списка внутри атрибута eval. При этом вы можете использовать ссылки на другие записи.

<function model="ir.model.data" name="_update_xmlids">
    <value
        model="base"
        eval="[{
        'xml_id': 'имя_вашего_модуля.id_записи_который_вы_хотите_присвоить',
        'record': obj().env['first.model'].search([('id', '=', 100)]),
        'noupdate': True,
    }]"
    />
</function>

Весьма комплексный пример, который показывает как можно создать xml_id для автоматически создаваемой записи.

Использование атрибута noupdate

Иногда у вас может возникать ситуация, чтобы данные из xml файлов загружались только один раз при установке модуля, а при дальнейших обновлениях игнорировались. Для этого вы можете к тегу <odoo> либо к тегу <data> добавить атрибут noupdate="1". Этот атрибут укажет системе, что записи с xml_id которые входят внутрь тега этим атрибутом, не будут обновляться при обновлении модуля. При его переустановке - будут.

Лично я при разработке стараюсь заполнять данные так, чтобы они правильно загружались даже при повторном обновлении, это очень сильно помогает избежать проблем при передаче модуля клиенту.

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

Обсуждение

Обсудить можно здесь