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

Миграция плагина на InfraVision v4.0

Этот документ служит руководством для разработчиков плагинов, написанных до выхода InfraVision v4.0. Он содержит все рекомендуемые изменения для обеспечения совместимости плагина с InfraVision v4.0 и более поздними версиями.

Общее

Поддержка Python

InfraVision v4.0 прекращает поддержку Python 3.8 и 3.9 и вводит поддержку Python 3.12. Вам может потребоваться обновить процессы CI/CD и/или упаковку для отражения этого.

Перемещение ресурсов плагинов

Все Python-ресурсы плагинов были перемещены из extras.plugins в netbox.plugins в InfraVision v3.7 (см. #14036), и поддержка импорта этих ресурсов из старых расположений была удалена.

Старый способ
from extras.plugins import PluginConfig
Новый способ
from netbox.plugins import PluginConfig

ContentType переименован в ObjectType

Прокси-модель InfraVision для модели ContentType Django была переименована в ObjectType для ясности. В общем случае плагины должны использовать прокси ObjectType при ссылках на типы контента, так как он включает несколько пользовательских методов менеджера. Единственное исключение — при определении обобщённых внешних ключей: поле ForeignKey, используемое для GFK, должно указывать на нативный ContentType Django.

Кроме того, разработчикам плагинов настоятельно рекомендуется принять терминологию "object type" для имён полей и фильтров, где это возможно, для согласованности с ядром InfraVision (однако это не является обязательным требованием для совместимости).

Старый способ
content_types = models.ManyToManyField(
    to='contenttypes.ContentType',
    related_name='event_rules'
)
Новый способ
object_types = models.ManyToManyField(
    to='core.ObjectType',
    related_name='event_rules'
)

Представления

Действия представлений должны быть словарями

Формат объявления действий и разрешений представлений был обновлён в InfraVision v3.7 (см. #13550), и InfraVision v4.0 прекращает поддержку старого формата. Представления, наследующие ActionsMixin, должны объявлять единый словарь actions.

Старый способ
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete')
action_perms = defaultdict(set, **{
    'add': {'add'},
    'import': {'add'},
    'bulk_edit': {'change'},
    'bulk_delete': {'delete'},
})
Новый способ
actions = {
    'add': {'add'},
    'import': {'add'},
    'export': set(),
    'bulk_edit': {'change'},
    'bulk_delete': {'delete'},
}

Формы

Удаление BootstrapMixin

Класс BootstrapMixin больше не доступен и не нужен, его можно удалить из всех форм.

Старый способ
from django import forms
from utilities.forms import BootstrapMixin

class MyForm(BootstrapMixin, forms.Form):
Новый способ
from django import forms

class MyForm(forms.Form):

Обновление определений Fieldset

InfraVision v4.0 вводит несколько новых классов для расширенной отрисовки форм, включая FieldSet. Определения fieldset в формах должны использовать этот новый класс вместо кортежа или списка.

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

Старый способ
from django.utils.translation import gettext_lazy as _
from netbox.forms import NetBoxModelForm

class CircuitForm(NetBoxModelForm):
    ...
    fieldsets = (
        (_('Circuit'), ('cid', 'type', 'status', 'description', 'tags')),
        (_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
        (_('Tenancy'), ('tenant_group', 'tenant')),
    )
Новый способ
from django.utils.translation import gettext_lazy as _
from netbox.forms import NetBoxModelForm
from utilities.forms.rendering import FieldSet

class CircuitForm(NetBoxModelForm):
    ...
    fieldsets = (
        FieldSet('cid', 'type', 'status', 'description', 'tags', name=_('Circuit')),
        FieldSet('install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')),
        FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
    )

Навигация

Удаление цветов кнопок

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

Старый способ
PluginMenuButton(
    link='myplugin:foo_add',
    title='Add a new Foo',
    icon_class='mdi mdi-plus-thick',
    color=ButtonColorChoices.GREEN
)
Новый способ
PluginMenuButton(
    link='myplugin:foo_add',
    title='Add a new Foo',
    icon_class='mdi mdi-plus-thick'
)

Макет интерфейса

Переименованные блоки шаблонов

Следующие блоки шаблонов были переименованы или удалены:

Шаблон Старое имя Новое имя
generic/object.html header page-header
generic/object.html controls control-buttons
base/layout.html content-wrapper Удалён (используйте content)

Использование flex-элементов управления

Откажитесь от устаревших элементов управления "float" (например, float-end) в пользу новых flex-поведений Bootstrap для управления расположением и размером элементов по горизонтали. Например, следующий код выровняет два элемента по левому и правому краям родительского элемента:

<div class="d-flex justify-content-between">
    <h3>Title text</h3>
    <i class="mdi mdi-close"></i>
</div>

Проверка смещений колонок

При использовании смещений колонок (например, class="col-offset-3") обязательно также устанавливайте ширину колонки (например, class="col-9 col-offset-3"), чтобы избежать горизонтальной прокрутки.

Таблицы внутри карточек

Таблицы внутри карточек должны быть встроены напрямую, а не вложены в элемент card-body.

Старый способ
<div class="card">
    <div class="card-body">
        <table class="table table-hover attr-table">
            ...
        </table>
    </div>
</div>
Новый способ
<div class="card">
    <table class="table table-hover attr-table">
        ...
    </table>
</div>

Удаление класса btn-sm с кнопок

Класс btn-sm (маленький) обычно больше не нужен для кнопок общего назначения.

Старый способ
<a href="#" class="btn btn-sm btn-primary">Text</a>
Новый способ
<a href="#" class="btn btn-primary">Text</a>

Обновление классов bg-$color

Цвет переднего плана (текста) больше не настраивается автоматически классами bg-$color. Чтобы обеспечить достаточный контраст с цветом фона, используйте форму класса text-bg-$color или задайте цвет текста отдельно с помощью text-$color.

Старый способ
<span class="badge bg-primary">Text</span>
Новый способ
<span class="badge text-bg-primary">Text</span>

Устаревшие пользовательские CSS-классы

Следующие пользовательские CSS-классы были удалены:

  • object-subtitle (используйте вместо него text-secondary)

REST API

Расширение сериализатора для краткого режима

InfraVision теперь использует единый сериализатор API как для обычного, так и для "краткого" режимов (т.е. GET /api/dcim/sites/?brief=true); вложенные классы сериализаторов больше не требуются. Для поддержки краткого режима необходимы два изменения в сериализаторах API:

  1. Определите brief_fields в классе Meta. Это поля, которые будут включены при использовании краткого режима.
  2. Для любых вложенных объектов переключитесь на использование основного сериализатора и передайте nested=True.

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

Старый способ
class SiteSerializer(NetBoxModelSerializer):
    region = NestedRegionSerializer(required=False, allow_null=True)

    class Meta:
        model = Site
        fields = ('id', 'url', 'display', 'name', 'slug', 'status', 'region', 'time_zone', ...)
Новый способ
class SiteSerializer(NetBoxModelSerializer):
    region = RegionSerializer(nested=True, required=False, allow_null=True)

    class Meta:
        model = Site
        fields = ('id', 'url', 'display', 'name', 'slug', 'status', 'region', 'time_zone', ...)
        brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug')

Включение полей description в краткий режим

InfraVision теперь включает поле description в "кратком" режиме для всех моделей, которые его имеют. Это не является обязательным для плагинов, но вы можете сделать то же самое для согласованности.

GraphQL

InfraVision заменил Graphene-Django на Strawberry, что требует обновления любого кода GraphQL.

Изменение schema.py

Strawberry использует типизацию Python и в целом требует лишь небольшого рефакторинга определения схемы для обновления:

Старый способ
import graphene
from netbox.graphql.fields import ObjectField, ObjectListField
from utilities.graphql_optimizer import gql_query_optimizer

class CircuitsQuery(graphene.ObjectType):
    circuit = ObjectField(CircuitType)
    circuit_list = ObjectListField(CircuitType)

    def resolve_circuit_list(root, info, **kwargs):
        return gql_query_optimizer(models.Circuit.objects.all(), info)
Новый способ
import strawberry
import strawberry_django

@strawberry.type
class CircuitsQuery:
    @strawberry.field
    def circuit(self, id: int) -> CircuitType:
        return models.Circuit.objects.get(pk=id)
    circuit_list: list[CircuitType] = strawberry_django.field()

Изменение types.py

Преобразование типов также довольно простое, но Strawberry требует явного определения ссылок FK и M2M для правильной типизации.

  1. Параметры class Meta нужно переместить в декоратор Strawberry
  2. Добавьте определения @strawberry_django.field для любых ссылок FK и M2M в модели
Старый способ
import graphene

class CircuitType(NetBoxObjectType, ContactsMixin):
    class Meta:
        model = models.Circuit
        fields = '__all__'
        filterset_class = filtersets.CircuitFilterSet
Новый способ
from typing import Annotated

import strawberry
import strawberry_django

@strawberry_django.type(
    models.CircuitType,
    fields='__all__',
    filters=CircuitTypeFilter
)
class CircuitTypeType(OrganizationalObjectType):
    color: str

    @strawberry_django.field
    def circuits(self) -> list[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
        return self.circuits.all()

Изменение filters.py

Strawberry в настоящее время не поддерживает django-filter напрямую, поэтому необходимо создать явный файл filters.py. InfraVision включает новый autotype_decorator, используемый для автоматического обёртывания FilterSets и минимизации необходимого кода.

Новый способ
import strawberry
import strawberry_django
from circuits import filtersets, models

from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin

__all__ = (
    'CircuitFilter',
)


@strawberry_django.filter(models.Circuit, lookups=True)
@autotype_decorator(filtersets.CircuitFilterSet)
class CircuitFilter(BaseFilterMixin):
    pass