Compare commits

..

2 Commits

Author SHA1 Message Date
IluaAir
b715019845 reorder file paths 2026-02-03 16:43:23 +03:00
IluaAir
3268274f69 reorder file paths 2026-02-01 15:56:37 +03:00
7 changed files with 30 additions and 324 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
/.venv/
/.idea/
/.netbox/netbox
/.vscode/

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

0
README.md Normal file
View File

View File

@@ -1,324 +0,0 @@
import re
from tenancy.models import Tenant, TenantGroup, Contact, ContactRole, ContactAssignment
from ipam.models import Prefix, VRF, Role
from dcim.models import DeviceType, DeviceRole, Device
from extras.scripts import Script, StringVar, IPNetworkVar, ChoiceVar, ObjectVar
from django.core.exceptions import ValidationError
from extras.models import CustomField
from utilities.exceptions import AbortScript
def slugify(text: str):
CYRILLIC_TO_LATIN = {
"а": "a",
"б": "b",
"в": "v",
"г": "g",
"д": "d",
"е": "e",
"ё": "e",
"ж": "zh",
"з": "z",
"и": "i",
"й": "i",
"к": "k",
"л": "l",
"м": "m",
"н": "n",
"о": "o",
"п": "p",
"р": "r",
"с": "s",
"т": "t",
"у": "u",
"ф": "f",
"х": "h",
"ц": "c",
"ч": "ch",
"ш": "sh",
"щ": "shch",
"ъ": "",
"ы": "y",
"ь": "",
"э": "e",
"ю": "yu",
"я": "ya",
"А": "A",
"Б": "B",
"В": "V",
"Г": "G",
"Д": "D",
"Е": "E",
"Ё": "E",
"Ж": "ZH",
"З": "Z",
"И": "I",
"Й": "I",
"К": "K",
"Л": "L",
"М": "M",
"Н": "N",
"О": "O",
"П": "P",
"Р": "R",
"С": "S",
"Т": "T",
"У": "U",
"Ф": "F",
"Х": "H",
"Ц": "C",
"Ч": "CH",
"Ш": "SH",
"Щ": "SHCH",
"Ъ": "",
"Ы": "Y",
"Ь": "",
"Э": "E",
"Ю": "YU",
"Я": "YA",
}
text = text.replace("ООО", "").lstrip()
text = "".join(CYRILLIC_TO_LATIN.get(c, c) for c in text)
text = text.lower()
text = re.sub(r"\s+", "_", text)
text = re.sub(r"[^a-z0-9_-]", "", text)
text = re.sub(r"[-_]{2,}", "_", text).strip("_-")
return text
class CreateTenant(Script):
class Meta:
name = "Создание Оператора"
description = "Создание оператора и связанного префикса"
scheduling_enabled = False
fieldsets = (
(
"Оператор",
(
"tenant_name",
"tenant_group",
"tenant_connection_type",
"tenant_description",
),
),
(
"Префикс",
("prefix_cidr", "prefix_vrf", "prefix_role", "prefix_description"),
),
("Документы", ("tenant_contract", "tenant_prefix_task", "tenant_tr")),
("Контакты", ("contacts_fio", "contacts_phone", "contacts_email")),
)
connection_types = CustomField.objects.get(name="connection_type_tenant")
tenant_name = StringVar(label="Имя оператора")
tenant_group = ObjectVar(
label="Группа оператора", model=TenantGroup, required=False
)
tenant_connection_type = ChoiceVar(
label="Тип подключения", choices=connection_types.choices, required=True
)
tenant_description = StringVar(label="Описание оператора", required=False)
tenant_contract = StringVar(
label="Договор",
description="Номер договора на разработку комплекса",
required=False,
)
tenant_tr = StringVar(
label="Проект/ТР", description="Пример: DOCXXXXX", required=False
)
tenant_prefix_task = StringVar(
label="Задача",
description="URL или номер задачи из redmine, jira, CRM",
required=False,
)
prefix_cidr = IPNetworkVar(
label="Префикс для оператора (CIDR)",
description="Указывается с маской подсети /xx",
)
prefix_vrf = ObjectVar(label="VRF проекта", model=VRF, required=True)
prefix_role = ObjectVar(
label="Роль",
description="Роль для малых операторов IPMI-SSH-MGMT",
model=Role,
required=True,
)
prefix_description = StringVar(label="Описание префикса", required=False)
contacts_fio = StringVar(
label="ФИО",
description="Указывается в формате Иванов Иван Иванович, остальные значения игнорируются при отсутсвии ФИО",
required=False,
)
contacts_phone = StringVar(label="Номер тел.", required=False)
contacts_email = StringVar(label="E-mail", required=False)
contacts_role = ObjectVar(label="Роль контакта", model=ContactRole, required=False)
def check_tenant_exists(self, data):
if Tenant.objects.filter(name=data["tenant_name"]).exists():
raise ValidationError(
f"Оператор с именем '{data['tenant_name']}' уже существует"
)
def check_prefix_exists(self, data):
if Prefix.objects.filter(prefix=data["prefix_cidr"]).exists():
self.log_warning(
f"Префикс {data['prefix_cidr']} уже существует, оператор будет создан без префикса"
)
return False
return True
def run(self, data, commit):
try:
self.check_tenant_exists(data)
validate_prefix = self.check_prefix_exists(data)
tenant_slug = slugify(data["tenant_name"])
tenant_data = {
"name": data["tenant_name"],
"slug": tenant_slug,
"group": data["tenant_group"],
}
if data.get("tenant_description"):
tenant_data["description"] = data["tenant_description"]
tenant = Tenant(**tenant_data)
tenant.custom_field_data["dogovor"] = data["tenant_contract"]
tenant.custom_field_data["connection_type_tenant"] = data[
"tenant_connection_type"
]
tenant.custom_field_data["link_on_docs"] = data["tenant_tr"]
tenant.full_clean()
if commit:
tenant.save()
self.log_success(
f"Создан новый оператор: {tenant.name} - {tenant.slug}"
)
if validate_prefix:
prefix = Prefix(
prefix=data["prefix_cidr"],
tenant=tenant,
description=data.get("prefix_description") or "",
vrf=data["prefix_vrf"],
role=data["prefix_role"],
)
prefix.custom_field_data["Project"] = data["tenant_tr"]
prefix.custom_field_data["zadacha"] = data["tenant_prefix_task"]
prefix.full_clean()
prefix.save()
self.log_success(
f"Создан префикс {prefix.prefix} для оператора: {tenant.name}"
)
if data.get("contacts_fio"):
contact = Contact(
name=data["contacts_fio"],
phone=data["contacts_phone"],
email=data["contacts_email"],
)
try:
contact.full_clean()
contact.save()
self.log_success(f"Создан контакт: {contact.name}")
except Exception as e:
self.log_failure(f"Ошибка при создании контакта: {e}")
try:
role = data.get("contacts_role") or ContactRole.objects.first()
assignment = ContactAssignment(
contact=contact,
role=role,
object=tenant,
)
assignment.full_clean()
assignment.save()
self.log_success(
f"Связан контакт {contact.name} с оператором: {tenant.name}"
)
except Exception as e:
self.log_failure(
f"Ошибка при связывании контакта с оператором: {e}"
)
else:
self.log_info(
f"Тестовый режим: Будет создан оператор: {tenant.name} (slug: {tenant.slug})"
)
self.log_info(
f"Тестовый режим: Будет создан префикс: {data['prefix_cidr']}"
)
try:
tenant.full_clean()
prefix = Prefix(
prefix=data["prefix_cidr"],
tenant=tenant,
description=data.get("prefix_description") or "",
)
prefix.full_clean()
contact = Contact(
name=data["contacts_fio"],
phone=data["contacts_phone"],
email=data["contacts_email"],
)
contact.full_clean()
role = data.get("contacts_role") or ContactRole.objects.first()
assignment = ContactAssignment(
contact=contact,
role=role,
object=tenant,
)
assignment.full_clean()
self.log_success("Все данные валидны")
except ValidationError as e:
self.log_failure(f"Ошибка валидации: {e}")
except ValidationError as e:
raise AbortScript(f"Ошибка валидации: {e}")
except Exception as e:
self.log_failure(f"Ошибка при выполнении скрипта: {e}")
if commit:
self.log_failure("Изменения отменены из-за ошибки")
raise
class CreateDevice(Script):
class Meta:
name = "Создание устройства"
description = "Создание устройства и привязка к оператору"
scheduling_enabled = False
fieldsets = (
(
"Устройство",
(
"device_name",
"device_type",
"device_role",
"device_description",
"tenant_group",
"device_tenant",
),
),
)
device_name = StringVar(label="Имя устройства")
device_role = ObjectVar(label="Роль устройства", model=DeviceRole, required=True)
device_type = ObjectVar(label="Тип устройства", model=DeviceType, required=True)
device_description = StringVar(label="Описание устройства", required=False)
tenant_group = ObjectVar(
label="Группа оператора", model=TenantGroup, required=False
)
device_tenant = ObjectVar(
label="Оператор",
model=Tenant,
required=True,
query_params={
"group_id": "$tenant_group",
},
)
def run(self, data, commit):
pass
script_order = (CreateTenant, CreateDevice)

7
pyproject.toml Normal file
View File

@@ -0,0 +1,7 @@
[project]
name = "netbox-scripts"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

12
pyrightconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extraPaths": ["netbox/netbox"],
"typeCheckingMode": "off",
"reportMissingImports": "warning",
"reportMissingTypeStubs": "none",
"reportGeneralTypeIssues": "none",
"reportOptionalMemberAccess": "none",
"reportOptionalSubscript": "none",
"reportArgumentType": "none",
"reportAssignmentType": "none",
"reportReturnType": "none"
}

8
uv.lock generated Normal file
View File

@@ -0,0 +1,8 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "netbox-scripts"
version = "0.1.0"
source = { virtual = "." }