Внешний API


Binta обычно расширяется внутренне через модули, но многие из его функций и все его данные также доступны извне для внешнего анализа или интеграции с различными инструментами. Часть API моделей легко доступна через XML-RPC и доступна из различных языков.


Веб-сервисы

Модуль веб-сервиса предлагает общий интерфейс для всех веб-сервисов:

  • XML-RPC

  • JSON-RPC

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

Доступ к Binta осуществляется через интерфейсы XML-RPC/JSON-RPC, для которых существуют библиотеки на многих языках.

Библиотека XML-RPC

Следующий пример представляет собой программу Python 3, которая взаимодействует с сервером Binta с помощью библиотеки xmlrpc.client:

import xmlrpc.client

root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)

uid = xmlrpc.client.ServerProxy(root + 'common').login(DB, USER, PASS)
print("Logged in as %s (uid: %d)" % (USER, uid))

# Create a new note
sock = xmlrpc.client.ServerProxy(root + 'object')
args = {
    'color' : 8,
    'memo' : 'This is a note',
    'create_uid': uid,
}
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)


Библиотека JSON-RPC

Следующий пример — это программа Python 3, которая взаимодействует с сервером Binta со стандартными библиотеками Python urllib.request и json. В этом примере предполагается, что установлено приложение Productivity:

import json
import random
import urllib.request

HOST = 'localhost'
PORT = 8069
DB = 'openacademy'
USER = 'admin'
PASS = 'admin'

def json_rpc(url, method, params):
    data = {
        "jsonrpc": "2.0",
        "method": method,
        "params": params,
        "id": random.randint(0, 1000000000),
    }
    req = urllib.request.Request(url=url, data=json.dumps(data).encode(), headers={
        "Content-Type":"application/json",
    })
    reply = json.loads(urllib.request.urlopen(req).read().decode('UTF-8'))
    if reply.get("error"):
        raise Exception(reply["error"])
    return reply["result"]

def call(url, service, method, *args):
    return json_rpc(url, "call", {"service": service, "method": method, "args": args})

# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)

# create a new note
args = {
    'color': 8,
    'memo': 'This is another note',
    'create_uid': uid,
}
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)

​Примеры можно легко адаптировать из XML-RPC в JSON-RPC.


Соединение

Конфигурация

Если у вас уже установлен сервер Binta, вы можете просто использовать его параметры.

Для экземпляров Binta Online (<domain>.binta.by) пользователи создаются без локального пароля (как лицо, входящее в систему аутентификации Binta Online, а не сам экземпляр). Чтобы использовать XML-RPC на экземплярах Binta Online, вам необходимо установить пароль для учетной записи пользователя, которую вы хотите использовать:

  • Войдите в свой экземпляр, используя учетную запись администратора.

  • Перейдите в Настройки ‣ Пользователи и компании ‣ Пользователи .

  • Щелкните по имени пользователя, которого вы хотите использовать для доступа XML-RPC.

  • Нажмите «Действие» и выберите «Изменить пароль» .

  • Задайте значение нового пароля , затем нажмите «Изменить пароль» .

URL-адрес сервера — это домен экземпляра (например, https://mycompany.binta.by), имя базы данных — это имя экземпляра (например, mycompany ). Имя пользователя — это настроенный логин пользователя, как показано на экране «Изменить пароль» .

Python:

url = <insert server URL>
db = <insert database name>
username = 'admin'
password = <insert password for your admin user (default: admin)>


API-ключи

Binta поддерживает ключи API и (в зависимости от модулей или настроек) может потребовать эти ключи для выполнения операций веб-сервиса.

Способ использования API Keys в ваших скриптах заключается в простой замене пароля ключом . Логин остается в использовании. Вы должны хранить API Key так же тщательно, как и пароль, поскольку они по сути предоставляют тот же доступ к вашей учетной записи пользователя (хотя их нельзя использовать для входа через интерфейс).

Чтобы добавить ключ к своей учетной записи, просто нажмите на свой профиль в правом верхнем углу и нажмите на  «Парамерты»:

затем откройте вкладку «Параметры доступа» и нажмите «Новый ключ API» :

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

Нажмите Generate Key , затем скопируйте предоставленный ключ. Сохраните этот ключ бережно : он эквивалентен вашему паролю, и, как и ваш пароль, система не сможет извлечь или показать ключ снова позже. Если вы потеряете этот ключ, вам придется создать новый (и, вероятно, удалить тот, который вы потеряли).

После настройки ключей в вашей учетной записи они появятся над кнопкой «Новый ключ API» , и вы сможете их удалить:

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


Тестовая база данных

Чтобы упростить исследование, вы также можете запросить тестовую базу данных на https://binta.by/

import xmlrpc.client
info = xmlrpc.client.ServerProxy('https://binta.by').start()
url, db, username, password = info['host'], info['database'], info['user'], info['password']


Вход в систему

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

Конечная xmlrpc/2/common точка предоставляет метавызовы, которые не требуют аутентификации, такие как сама аутентификация или получение информации о версии. Чтобы проверить правильность информации о подключении перед попыткой аутентификации, простейшим вызовом является запрос версии сервера. Сама аутентификация выполняется через функцию authenticate и возвращает идентификатор пользователя (uid), используемый в аутентифицированных вызовах вместо имени пользователя.

Python:

common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
common.version()

Результат:

{
    "server_version": "13.0",
    "server_version_info": [13, 0, 0, "final", 0],
    "server_serie": "13.0",
    "protocol_version": 1,
}

Python:

uid = common.authenticate(db, username, password, {})



Вызов методов

Вторая конечная точка — xmlrpc/2/object. Она используется для вызова методов моделей Binta через execute_kw функцию RPC.

  • Каждый вызов execute_kwпринимает следующие параметры:
      • * используемая база данных, строковое значение
      • * идентификатор пользователя (полученный через authenticate), целочисленное значение
      • * пароль пользователя, строковое значение
      • * название модели, строковое значение
      • * имя метода, строковое значение
      • * массив/список параметров, переданных по позиции
      • * отображение/словарь параметров для передачи по ключевому слову (необязательно)

Пример

Например, чтобы проверить, можем ли мы прочитать res.partner модель, мы можем вызвать функцию check_access_rights с operation переданной позицией и raise_exception переданным по ключевому слову (чтобы получить результат true/false, а не true/error):

Python:

models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))
models.execute_kw(db, uid, password, 'res.partner', 'check_access_rights', ['read'], {'raise_exception': False})

Результат: 

true


Список записей

Записи можно просматривать и фильтровать с помощью search().

search() принимает обязательный фильтр домена (возможно, пустой) и возвращает идентификаторы базы данных всех записей, соответствующих фильтру.

Пример

Чтобы составить список компаний-клиентов, например:

models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]])

Результат:

[7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]


Пагинация

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

Пример

Python:

models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'offset': 10, 'limit': 5})

Результат:

[13, 20, 30, 22, 29]


Количество записей

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

Пример

Python:

models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', True]]])

Результат: 

19

Примечание:

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


Прочитать записи

Данные записи доступны через read() метод, который принимает список идентификаторов (возвращаемый search()), и опционально список полей для извлечения. По умолчанию он извлекает все поля, которые может прочитать текущий пользователь, что, как правило, составляет огромный объем.

Пример

Python:

ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'limit': 1})
[record] = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids])
# count the number of fields fetched by default
len(record)

Результат: 

121

И наоборот, выбор только трех интересующих областей.

Python:

models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {'fields': ['name', 'country_id', 'comment']})

Результат:

[{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]

ПримечаниеДаже если id не запрашивается, оно всегда возвращается.


Список полей записи

fields_get() может использоваться для проверки полей модели и выбора тех из них, которые представляют интерес.

Поскольку он возвращает большой объем метаинформации (она также используется клиентскими программами), его следует отфильтровать перед печатью. Наиболее интересными элементами для пользователя-человека являются string (метка поля), help (текст справки, если доступен) и type (чтобы узнать, какие значения ожидать или отправлять при обновлении записи).

Пример

Python:

models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [], {'attributes': ['string', 'help', 'type']})

Результат:

{
    "ean13": {
        "type": "char",
        "help": "BarCode",
        "string": "EAN13"
    },
    "property_account_position_id": {
        "type": "many2one",
        "help": "The fiscal position will determine taxes and accounts used for the partner.",
        "string": "Fiscal Position"
    },
    "signup_valid": {
        "type": "boolean",
        "help": "",
        "string": "Signup Token is Valid"
    },
    "date_localization": {
        "type": "date",
        "help": "",
        "string": "Geo Localization Date"
    },
    "ref_company_ids": {
        "type": "one2many",
        "help": "",
        "string": "Companies that refers to partner"
    },
    "sale_order_count": {
        "type": "integer",
        "help": "",
        "string": "# of Sales Order"
    },
    "purchase_order_count": {
        "type": "integer",
        "help": "",
        "string": "# of Purchase Order"
    },


Поиск и чтение

Поскольку это очень распространенная задача, Binta предоставляет search_read() сокращение, которое, как следует из названия, эквивалентно , search() за которым следует read(), но позволяет избежать необходимости выполнять два запроса и хранить идентификаторы.

Его аргументы аналогичны search() аргументам , но он также может принимать список полей (например read(), если этот список не указан, он извлечет все поля соответствующих записей).

Пример

Python:

models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', True]]], {'fields': ['name', 'country_id', 'comment'], 'limit': 5})

Результат:

[
    {
        "comment": false,
        "country_id": [ 21, "Belgium" ],
        "id": 7,
        "name": "Agrolait"
    },
    {
        "comment": false,
        "country_id": [ 76, "France" ],
        "id": 18,
        "name": "Axelor"
    },
    {
        "comment": false,
        "country_id": [ 233, "United Kingdom" ],
        "id": 12,
        "name": "Bank Wealthy and sons"
    },
    {
        "comment": false,
        "country_id": [ 105, "India" ],
        "id": 14,
        "name": "Best Designers"
    },
    {
        "comment": false,
        "country_id": [ 76, "France" ],
        "id": 17,
        "name": "Camptocamp"
    }
]


Создавать записи

Записи модели создаются с помощью create(). Метод создает одну запись и возвращает ее идентификатор базы данных.

create() принимает сопоставление полей со значениями, используемыми для инициализации записи. Для любого поля, имеющего значение по умолчанию и не заданного через аргумент сопоставления, будет использоваться значение по умолчанию.

Пример

Python:

id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{'name': "New Partner"}])

Результат:

78

Предупреждение!
Хотя большинство типов значений являются ожидаемыми (целое число для Integer, строка для Char или Text),

DateDatetime и Binary поля используют строковые значения

One2many и Many2many используют специальный командный протокол


Обновление записей

Записи можно обновить с помощью write(). Для этого требуется список записей для обновления и сопоставление обновленных полей со значениями, аналогичными create().

Несколько записей могут быть обновлены одновременно, но все они получат одинаковые значения для устанавливаемых полей. Невозможно выполнить «вычисляемые» обновления (где устанавливаемое значение зависит от существующего значения записи).

Пример

Python:

models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {'name': "Newer partner"}])
# get record name after having changed it
models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])

Результат:

[[78, "Newer partner"]]


Удалить записи

Записи можно удалять массово, указав их идентификаторы unlink().

models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
# check if the deleted record is still in the database
models.execute_kw(db, uid, password, 'res.partner', 'search', [[['id', '=', id]]])

Результат:

[]


Инспекция и самоанализ

Если ранее мы использовали fields_get() запрос модели и с самого начала использовали произвольную модель, то Binta хранит большую часть метаданных модели внутри нескольких метамоделей, которые позволяют как запрашивать систему, так и изменять модели и поля (с некоторыми ограничениями) «на лету» через XML-RPC.

ir.model

Предоставляет информацию о моделях Binta через различные поля.

name

понятное для человека описание модели

model

название каждой модели в системе

state

была ли модель сгенерирована в коде Python (base) или путем создания ir.model записи (manual)

field_id

список полей модели One2many через ir.model.fields

view_ids

One2many к представлениям, определенным для модели

access_ids

One2many отношение к правам доступа, установленным на модели

ir.model может быть использован для:

  • * Запросить у системы установленные модели (в качестве предварительного условия для операций с моделью или для изучения содержимого системы).

  • * Получить информацию о конкретной модели (обычно путем перечисления связанных с ней полей).

  • * Создавайте новые модели динамически через RPC.

Важно!

  • * Названия пользовательских моделей должны начинаться с x_.

  • * Необходимо указать state и установить значение manual, в противном случае модель не будет загружена.

  • * В пользовательскую модель невозможно добавлять новые методы , только поля.

Пример

Python:

models.execute_kw(db, uid, password, 'ir.model', 'create', [{
    'name': "Custom Model",
    'model': "x_custom_model",
    'state': 'manual',
}])
models.execute_kw(db, uid, password, 'x_custom_model', 'fields_get', [], {'attributes': ['string', 'help', 'type']})

Результат:

{
    "create_uid": {
        "type": "many2one",
        "string": "Created by"
    },
    "create_date": {
        "type": "datetime",
        "string": "Created on"
    },
    "__last_update": {
        "type": "datetime",
        "string": "Last Modified on"
    },
    "write_uid": {
        "type": "many2one",
        "string": "Last Updated by"
    },
    "write_date": {
        "type": "datetime",
        "string": "Last Updated on"
    },
    "display_name": {
        "type": "char",
        "string": "Display Name"
    },
    "id": {
        "type": "integer",
        "string": "Id"
    }
}


ir.model.fields

Предоставляет информацию о полях моделей Binta и позволяет добавлять пользовательские поля без использования кода Python.

model_id

Many2one к ir.model , к которой принадлежит поле

name

техническое название поля (используется в read или write)

field_description

читаемая пользователем метка поля (например, string в fields_get)

ttype

тип поля для создания

state

было ли поле создано с помощью кода Python (base) или с помощью ir.model.fields (manual)

required, readonly, translate

включает соответствующий флаг на поле

groups

контроль доступа на уровне поля, а Many2many к res.groups

selection, size, on_delete, relation, relation_field, domain

Свойства и настройки, зависящие от типа

Важно!

  • * Как и в пользовательских моделях, только новые поля, созданные с помощью, state="manual" активируются как фактические поля модели.

  • * Вычисляемые поля не могут быть добавлены через ir.model.fields, также не может быть установлена ​​некоторая метаинформация полей (значения по умолчанию, onchange).

Пример

Python:

id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{
    'name': "Custom Model",
    'model': "x_custom",
    'state': 'manual',
}])
models.execute_kw(db, uid, password, 'ir.model.fields', 'create', [{
    'model_id': id,
    'name': 'x_name',
    'ttype': 'char',
    'state': 'manual',
    'required': True,
}])
record_id = models.execute_kw(db, uid, password, 'x_custom', 'create', [{'x_name': "test record"}])
models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]])


Результат:

[
    {
        "create_uid": [1, "Administrator"],
        "x_name": "test record",
        "__last_update": "2014-11-12 16:32:13",
        "write_uid": [1, "Administrator"],
        "write_date": "2014-11-12 16:32:13",
        "create_date": "2014-11-12 16:32:13",
        "id": 1,
        "display_name": "test record"
    }
]