Перейти к содержанию

Работа с пользовательскими полями

Yandex Tracker позволяет работать с нестандартным набором полей. Мы учли это и добавили возможность передавать в запросах ожидаемую вами модель.

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

О том, как настроить локальное поле, вы можете узнать из соответствующего раздела документации: https://cloud.yandex.ru/docs/tracker/local-fields

В нашем примере мы рассмотрим добавление двух полей: - userUsername - userId

Создаём модель задачи

Создайте дочерний класс задачи из yatracker, чтобы насытить родительский класс нужными полями:

from yatracker.types import FullIssue


class HelpIssue(FullIssue):
    user_username: str | None = None
    user_id: int | None = None

Обратите внимание, что модели нашей библиотеки автоматически используют со стороны python имена в стиле snake_case, а при работе с Tracker, они конвертируются в camelCase – как это принято в самом трекере.

Дополнительные поля лучше объявлять необязательными (со значением по умолчанию None): локальное поле может отсутствовать в ответе трекера, и тогда задача без него всё равно корректно разберётся.

Локальные поля очереди

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

64a51c6d866ea82411abe756--userId

Работать с таким именем неудобно, не говоря о неудобствах передачи его в kwargs. Авторы стандартной библиотеки предлагают делать это так:

issue.update(**{"64a51c6d866ea82411abe756--userId": 42})

Такой ключ уходит в Tracker как есть: в camelCase преобразуются только имена, которые являются корректными идентификаторами Python (например, attachment_ids превратится в attachmentIds), а идентификаторы локальных полей вроде <id>--userId передаются без изменений.

Мы же предлагаем использовать удобные вам названия, а наша библиотека позаботится о правильном преобразовании:

from yatracker.types import FullIssue, field


class HelpIssue(FullIssue):
    user_id: int | None = field(
        default=None,
        alias="64a51c6d866ea82411abe756--userId",
    )

Устаревшее написание field(name="...") тоже продолжает работать – это синоним alias=, оставленный для совместимости:

class HelpIssue(FullIssue):
    user_id: int | None = field(
        default=None,
        name="64a51c6d866ea82411abe756--userId",
    )

Используем модель задачи

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

issue = await tracker.create_issue(
    summary="New Issue",
    queue="HELP",
    user_id=42,
    _type=HelpIssue,
)

print(issue.user_id)

Поле url и ключ self

Ключ self в ответах трекера содержит ссылку на объект. В моделях библиотеки он читается в поле url. Трекер выставляет self сам, записать его нельзя, поэтому именованный аргумент url= в create_issue, edit_issue, move_issue и других методах уходит в API как есть – ключом url, а не self. Модели, вложенные в тело запроса (например, parent=Issue(...)), сериализуются в том же виде, в каком пришли из API, – с ключом self. Если в вашей очереди есть пользовательское поле url (например, ссылка на задачу в старой системе при миграции), достаточно передать его напрямую:

issue = await tracker.create_issue(
    summary="New Issue",
    queue="HELP",
    url="https://old-tracker/ticket/42",
)

Чтобы читать такое поле из ответа, объявите его в дочернем классе под другим python-именем – url уже занято ссылкой на объект:

from yatracker.types import FullIssue, field


class MigratedIssue(FullIssue):
    source_url: str | None = field(default=None, alias="url")


issue = await tracker.get_issue("HELP-1", _type=MigratedIssue)
print(issue.url)  # ссылка на задачу в API (ключ self)
print(issue.source_url)  # пользовательское поле url

Если передать такую модель в _type при записи, source_url= тоже преобразуется в ключ url, а одновременная передача url= и source_url= завершится ValueError, чтобы одно из значений не потерялось молча:

issue = await tracker.edit_issue(
    "HELP-1",
    source_url="https://old-tracker/ticket/42",
    _type=MigratedIssue,
)

Без _type библиотека ничего не знает о source_url и отправит его как обычное поле sourceUrl.

Модели в теле запроса

Модель, попавшая в тело запроса, отправляется в своей запросной форме, а не дословным model_dump. По умолчанию это одно и то же, но модели, у которых запрос и ответ выглядят по-разному, переопределяют её: EntityChecklistItem отдаёт assignee идентификатором и выбрасывает read-only поля, WidgetBucket переименовывает type в unit, FilterSort заменяет объект поля его ключом. Благодаря этому объект, прочитанный из API, можно передать обратно как есть — и он отрисуется одинаково, каким бы методом вы его ни отправили.

Собственная модель ничего для этого делать не обязана: базовый класс уже умеет выгружать себя в JSON. Переопределять _to_request() имеет смысл, только если ваш эндпоинт ждёт форму, отличную от ответа; учтите, что хук не рекурсивный — вложенные модели выгружает pydantic, поэтому переопределение, которое несёт вложенную модель, должно вызвать её хук само.