From dc0f6df740bff66363fa63ef82a658b1edffcd46 Mon Sep 17 00:00:00 2001 From: Anton M Date: Sat, 9 May 2026 16:23:52 +0200 Subject: [PATCH 1/4] fix imports --- validity/__init__.py | 4 +-- validity/choices.py | 5 ++-- validity/netbox_changes/__init__.py | 4 +-- validity/netbox_changes/current.py | 8 ++---- validity/netbox_changes/old.py | 23 +++------------- validity/netbox_changes/oldest.py | 27 ++----------------- validity/scripts/keeper.py | 3 +-- validity/scripts/runtests/combine.py | 8 +++++- .../test_scripts/runtests/test_combine.py | 5 ++-- validity/tests/test_scripts/test_backup.py | 3 +-- validity/tests/test_scripts/test_keeper.py | 7 +++-- validity/views/script.py | 4 +-- 12 files changed, 31 insertions(+), 70 deletions(-) diff --git a/validity/__init__.py b/validity/__init__.py index 4db1f6e..9a137c7 100644 --- a/validity/__init__.py +++ b/validity/__init__.py @@ -19,8 +19,8 @@ class NetBoxValidityConfig(PluginConfig): version = "3.4.3" base_url = "validity" django_apps = ["django_bootstrap5"] - min_version = "4.3.0" - max_version = "4.5.99" + min_version = "4.4.0" + max_version = "4.6.99" graphql_schema = "graphql.schema" # custom field diff --git a/validity/choices.py b/validity/choices.py index ddecf22..926eae2 100644 --- a/validity/choices.py +++ b/validity/choices.py @@ -1,11 +1,12 @@ from typing import Optional, TypeVar from django.db.models import IntegerChoices, TextChoices -from django.db.models.enums import ChoicesMeta from django.utils.translation import gettext_lazy as _ +from validity.netbox_changes import ChoicesType -class ColoredChoiceMeta(ChoicesMeta): + +class ColoredChoiceMeta(ChoicesType): """ Allows to write choice fields with a color like that: option1 = 'red' diff --git a/validity/netbox_changes/__init__.py b/validity/netbox_changes/__init__.py index 75b55a4..b24bc2b 100644 --- a/validity/netbox_changes/__init__.py +++ b/validity/netbox_changes/__init__.py @@ -16,9 +16,9 @@ def get_base_table_kwargs(self): StrFilterLookup = locate("strawberry_django.StrFilterLookup") if config.netbox_version >= "4.5.5" else FilterLookup[str] -if config.netbox_version >= "4.5.0": +if config.netbox_version >= "4.6.0": from .current import * -elif config.netbox_version >= "4.4.0": +elif config.netbox_version >= "4.5.0": from .old import * else: from .oldest import * diff --git a/validity/netbox_changes/current.py b/validity/netbox_changes/current.py index ac5eb85..148179a 100644 --- a/validity/netbox_changes/current.py +++ b/validity/netbox_changes/current.py @@ -1,8 +1,4 @@ -# NetBox 4.5 -from pydoc import locate - -from .old import * +# NetBox 4.6 -BaseModelFilter = locate("netbox.graphql.filters.BaseModelFilter") -NetBoxModelFilter = locate("netbox.graphql.filters.NetBoxModelFilter") +from .old import * diff --git a/validity/netbox_changes/old.py b/validity/netbox_changes/old.py index 137210c..0e0b170 100644 --- a/validity/netbox_changes/old.py +++ b/validity/netbox_changes/old.py @@ -1,23 +1,8 @@ -# NetBox 4.4 -from typing import TYPE_CHECKING +# NetBox 4.5 +from pydoc import locate from .oldest import * -if TYPE_CHECKING: - from validity.models import ComplianceReport - from validity.scripts.data_models import RequestInfo - - -def get_logs(job): - return job.log_entries - - -def set_logs(job, logs): - job.log_entries = logs - - -def enqueue(report: "ComplianceReport", request: "RequestInfo"): - queue = {} - enqueue_event(queue, report, request, "object_created") - flush_events(queue.values()) +BaseModelFilter = locate("netbox.graphql.filters.BaseModelFilter") +NetBoxModelFilter = locate("netbox.graphql.filters.NetBoxModelFilter") diff --git a/validity/netbox_changes/oldest.py b/validity/netbox_changes/oldest.py index b4bb7d6..37eb854 100644 --- a/validity/netbox_changes/oldest.py +++ b/validity/netbox_changes/oldest.py @@ -1,30 +1,7 @@ -# NetBox 4.3 +# NetBox 4.4 from pydoc import locate -from typing import TYPE_CHECKING - -from extras.events import enqueue_event, flush_events - - -if TYPE_CHECKING: - from validity.models import ComplianceReport - from validity.scripts.data_models import RequestInfo BaseModelFilter = locate("netbox.graphql.filter_mixins.BaseFilterMixin") NetBoxModelFilter = locate("netbox.graphql.filter_mixins.NetBoxModelFilterMixin") - - -def get_logs(job): - return job.data["log"] - - -def set_logs(job, logs): - if not isinstance(job.data, dict): - job.data = {} - job.data["log"] = logs - - -def enqueue(report: "ComplianceReport", request: "RequestInfo"): - queue = {} - enqueue_event(queue, report, request.get_user(), request.id, "object_created") - flush_events(queue.values()) +ChoicesType = locate("django.db.models.enums.ChoicesMeta") diff --git a/validity/scripts/keeper.py b/validity/scripts/keeper.py index 03dfce1..8c0d7fb 100644 --- a/validity/scripts/keeper.py +++ b/validity/scripts/keeper.py @@ -5,7 +5,6 @@ from core.models import Job from validity import di -from validity.netbox_changes import set_logs from validity.utils.logger import Logger from .exceptions import AbortScript @@ -48,7 +47,7 @@ def terminate_job( self, status: str = JobStatusChoices.STATUS_COMPLETED, error: str | None = None, output=None ) -> None: self.job.data = self.job.data or {} - set_logs(self.job, [log.serialized for log in self.logger.messages]) + self.job.log_entries = [log.serialized for log in self.logger.messages] output = output or self.job.data.get("output") self.job.data["output"] = output self.job.terminate(status, error) diff --git a/validity/scripts/runtests/combine.py b/validity/scripts/runtests/combine.py index bb82c47..c647087 100644 --- a/validity/scripts/runtests/combine.py +++ b/validity/scripts/runtests/combine.py @@ -10,10 +10,10 @@ from dimi import Singleton from django.db.models import QuerySet from django.urls import reverse +from extras.events import enqueue_event, flush_events from validity import di from validity.models import ComplianceReport, ComplianceTestResult -from validity.netbox_changes import enqueue from validity.utils.logger import Logger, Message from ..data_models import FullRunTestsParams, RequestInfo, TestResultRatio from ..exceptions import AbortScript @@ -22,6 +22,12 @@ from ..parent_jobs import JobExtractor +def enqueue(report: "ComplianceReport", request: "RequestInfo"): + queue = {} + enqueue_event(queue, report, request, "object_created") + flush_events(queue.values()) + + @di.dependency(scope=Singleton) @dataclass(repr=False, kw_only=True) class CombineWorker: diff --git a/validity/tests/test_scripts/runtests/test_combine.py b/validity/tests/test_scripts/runtests/test_combine.py index 6638eff..93de6b4 100644 --- a/validity/tests/test_scripts/runtests/test_combine.py +++ b/validity/tests/test_scripts/runtests/test_combine.py @@ -5,7 +5,6 @@ import pytest from django.utils import timezone -from validity.netbox_changes import get_logs from validity.scripts.data_models import ExecutionResult, Message from validity.scripts.data_models import TestResultRatio as ResultRatio from validity.scripts.runtests.combine import CombineWorker @@ -59,7 +58,7 @@ def test_call_abort(worker, full_runtests_params, job_extractor, monkeypatch): worker(full_runtests_params) job = full_runtests_params.get_job() assert job.status == "errored" - assert get_logs(job) == [ + assert job.log_entries == [ {"message": "m-3", "status": "info", "time": "2000-01-01T00:00:00"}, {"message": "m-4", "status": "info", "time": "2000-01-01T00:00:00"}, {"message": "ApplyWorkerError", "status": "failure", "time": "2020-01-01T00:00:00"}, @@ -78,7 +77,7 @@ def test_successful_call(worker, full_runtests_params, job_extractor, monkeypatc job.refresh_from_db() assert job.status == "completed" assert job.data["output"] == {"statistics": {"total": 7, "passed": 3}} - assert get_logs(job) == [ + assert job.log_entries == [ *[m.serialized for m in messages], { "time": "2020-01-01T00:00:00", diff --git a/validity/tests/test_scripts/test_backup.py b/validity/tests/test_scripts/test_backup.py index 7583d2a..fbc5ebd 100644 --- a/validity/tests/test_scripts/test_backup.py +++ b/validity/tests/test_scripts/test_backup.py @@ -7,7 +7,6 @@ from validity.data_backup import BackupBackend from validity.integrations.errors import IntegrationError -from validity.netbox_changes import get_logs from validity.scripts.backup import perform_backup from validity.scripts.data_models import FullBackUpParams @@ -35,7 +34,7 @@ def test_backup_success(di, params): assert bp.last_status == "completed" assert bp.last_uploaded < timezone.now() assert job.status == "completed" - assert get_logs(job) + assert job.log_entries @pytest.mark.django_db diff --git a/validity/tests/test_scripts/test_keeper.py b/validity/tests/test_scripts/test_keeper.py index f770ca5..aa8fac3 100644 --- a/validity/tests/test_scripts/test_keeper.py +++ b/validity/tests/test_scripts/test_keeper.py @@ -3,7 +3,6 @@ import pytest from factories import DSBackupJobFactory -from validity.netbox_changes import get_logs from validity.scripts.exceptions import AbortScript from validity.scripts.keeper import JobKeeper from validity.utils.logger import Logger, Message @@ -17,7 +16,7 @@ def test_keeper_noerror(timezone_now): keeper.logger.info("msg") keeper.job.refresh_from_db() assert keeper.job.status == "completed" - assert get_logs(keeper.job) == [{"time": "2000-01-01T01:00:00+00:00", "status": "info", "message": "msg"}] + assert keeper.job.log_entries == [{"time": "2000-01-01T01:00:00+00:00", "status": "info", "message": "msg"}] assert keeper.logger.messages == [] @@ -29,7 +28,7 @@ def test_keeper_abort(timezone_now): raise AbortScript("abort_msg", logs=[Message("warning", "extra_msg")]) keeper.job.refresh_from_db() assert keeper.job.status == "failed" - assert get_logs(keeper.job) == [ + assert keeper.job.log_entries == [ {"time": "2000-01-01T01:00:00+00:00", "status": "info", "message": "msg1"}, {"time": "2000-01-01T01:00:00+00:00", "status": "warning", "message": "extra_msg"}, {"time": "2000-01-01T01:00:00+00:00", "status": "failure", "message": "abort_msg"}, @@ -46,7 +45,7 @@ def test_keeper_exception(timezone_now): raise ValueError("unexpected") keeper.job.refresh_from_db() assert keeper.job.status == "errored" - logs = get_logs(keeper.job) + logs = keeper.job.log_entries assert len(logs) == 2 assert logs[0] == {"time": "2000-01-01T01:00:00+00:00", "status": "info", "message": "msg1"} assert logs[1]["status"] == "failure" diff --git a/validity/views/script.py b/validity/views/script.py index 84e8f12..31d61f8 100644 --- a/validity/views/script.py +++ b/validity/views/script.py @@ -12,7 +12,7 @@ from validity import di from validity.forms import RunTestsForm -from validity.netbox_changes import get_base_table_kwargs, get_logs +from validity.netbox_changes import get_base_table_kwargs from validity.scripts import Launcher, RunTestsParams, ScriptParams from validity.tables import ScriptResultTable from .base import LauncherMixin @@ -51,7 +51,7 @@ class ScriptResultView(PermissionRequiredMixin, TableMixin, ObjectView): permission_required = "core.view_job" def get_table(self, job, request, bulk_actions=False): - logs = [entry | {"index": i} for i, entry in enumerate(get_logs(job), start=1)] + logs = [entry | {"index": i} for i, entry in enumerate(job.log_entries, start=1)] table = self.table_class(logs, **self.get_table_kwargs()) table.configure(request) return table From 017cd6a246817dadc8eb14c22fa3861aa664caf6 Mon Sep 17 00:00:00 2001 From: Anton M Date: Sun, 10 May 2026 01:04:47 +0200 Subject: [PATCH 2/4] fix tests --- validity/api/helpers.py | 3 +-- validity/netbox_changes/current.py | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/validity/api/helpers.py b/validity/api/helpers.py index 0e166a9..033dce6 100644 --- a/validity/api/helpers.py +++ b/validity/api/helpers.py @@ -1,5 +1,4 @@ from django.core.exceptions import ValidationError -from django.db.models import ManyToManyField from rest_framework.permissions import BasePermission from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.serializers import HyperlinkedIdentityField, JSONField, ModelSerializer @@ -82,7 +81,7 @@ class SubformValidationMixin: def _validate(self, attrs): instance = self.instance or self.Meta.model() for field, field_value in attrs.items(): - if not isinstance(instance._meta.get_field(field), ManyToManyField): + if not instance._meta.get_field(field).many_to_many: setattr(instance, field, field_value) if not instance.subform_type: return diff --git a/validity/netbox_changes/current.py b/validity/netbox_changes/current.py index 148179a..8be04bb 100644 --- a/validity/netbox_changes/current.py +++ b/validity/netbox_changes/current.py @@ -1,4 +1,8 @@ # NetBox 4.6 +from pydoc import locate from .old import * + + +ChoicesType = locate("django.db.models.enums.ChoicesType") From d721e9b593e901d11ecc3e08f78adea4d67e097f Mon Sep 17 00:00:00 2001 From: Anton M Date: Sun, 10 May 2026 01:09:19 +0200 Subject: [PATCH 3/4] change ci nb versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72f27c5..3d1ff57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - netbox_version: &nb_versions [v4.3.7, v4.4.10, v4.5.5] + netbox_version: &nb_versions [v4.4.10, v4.5.8, v4.6.0] steps: - name: Checkout uses: actions/checkout@v3 From 0bf5ee344607bcd1409d335b676293d3a74db08b Mon Sep 17 00:00:00 2001 From: Anton M Date: Sun, 10 May 2026 01:50:17 +0200 Subject: [PATCH 4/4] update deps --- requirements/dev.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 661e844..a4c4bfa 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,9 +2,9 @@ debugpy==1.8.19 factory_boy==3.3.3 ipython pre-commit==4.5.1 -pytest==9.0.2 -pytest-cov==7.0.0 -pytest-django==4.11.1 +pytest==9.0.3 +pytest-cov==7.1.0 +pytest-django==4.12.0 pytest-subtests==0.15.0 ruff==0.14.11 tomli==2.4.0