nautobot 2.4.3__py3-none-any.whl → 2.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/__init__.py +19 -3
- nautobot/apps/filters.py +2 -0
- nautobot/circuits/filters.py +1 -1
- nautobot/circuits/tests/test_models.py +5 -3
- nautobot/cloud/filters.py +3 -6
- nautobot/cloud/tests/test_filters.py +21 -0
- nautobot/core/admin.py +2 -0
- nautobot/core/celery/__init__.py +5 -3
- nautobot/core/jobs/__init__.py +5 -3
- nautobot/core/management/commands/generate_performance_test_endpoints.py +9 -6
- nautobot/core/models/utils.py +6 -1
- nautobot/core/templates/inc/javascript.html +1 -0
- nautobot/core/templatetags/ui_framework.py +20 -4
- nautobot/core/testing/__init__.py +2 -0
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/testing/mixins.py +9 -0
- nautobot/core/tests/test_api.py +1 -1
- nautobot/core/tests/test_graphql.py +3 -3
- nautobot/core/tests/test_jobs.py +30 -28
- nautobot/core/ui/object_detail.py +1 -1
- nautobot/dcim/api/serializers.py +36 -0
- nautobot/dcim/api/views.py +1 -1
- nautobot/dcim/elevations.py +17 -4
- nautobot/dcim/factory.py +9 -1
- nautobot/dcim/filters/__init__.py +27 -1
- nautobot/dcim/forms.py +13 -1
- nautobot/dcim/models/devices.py +11 -5
- nautobot/dcim/signals.py +26 -0
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -62
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +6 -0
- nautobot/dcim/tests/test_api.py +176 -0
- nautobot/dcim/tests/test_filters.py +56 -3
- nautobot/dcim/tests/test_jobs.py +4 -6
- nautobot/dcim/tests/test_models.py +40 -0
- nautobot/dcim/views.py +24 -14
- nautobot/extras/api/mixins.py +1 -1
- nautobot/extras/api/views.py +2 -2
- nautobot/extras/choices.py +8 -3
- nautobot/extras/filters/__init__.py +4 -0
- nautobot/extras/jobs.py +181 -103
- nautobot/extras/management/utils.py +13 -2
- nautobot/extras/models/datasources.py +11 -4
- nautobot/extras/models/jobs.py +20 -17
- nautobot/extras/plugins/__init__.py +26 -1
- nautobot/extras/tables.py +25 -29
- nautobot/extras/templates/extras/inc/jobresult.html +12 -13
- nautobot/extras/templates/extras/objectchange.html +28 -12
- nautobot/extras/test_jobs/atomic_transaction.py +6 -6
- nautobot/extras/test_jobs/fail.py +75 -1
- nautobot/extras/tests/test_api.py +17 -16
- nautobot/extras/tests/test_datasources.py +64 -54
- nautobot/extras/tests/test_filters.py +2 -0
- nautobot/extras/tests/test_jobs.py +69 -62
- nautobot/extras/tests/test_models.py +1 -1
- nautobot/extras/tests/test_plugins.py +32 -1
- nautobot/extras/tests/test_relationships.py +5 -5
- nautobot/extras/tests/test_views.py +12 -2
- nautobot/extras/views.py +10 -1
- nautobot/ipam/api/serializers.py +7 -8
- nautobot/ipam/api/views.py +2 -2
- nautobot/ipam/factory.py +27 -8
- nautobot/ipam/filters.py +67 -29
- nautobot/ipam/formfields.py +51 -0
- nautobot/ipam/forms.py +28 -1
- nautobot/ipam/migrations/0051_added_optional_vrf_relationship_to_vdc.py +41 -0
- nautobot/ipam/models.py +63 -5
- nautobot/ipam/querysets.py +6 -0
- nautobot/ipam/tables.py +21 -7
- nautobot/ipam/templates/ipam/rir.html +1 -43
- nautobot/ipam/tests/test_api.py +107 -66
- nautobot/ipam/tests/test_filters.py +145 -5
- nautobot/ipam/tests/test_models.py +16 -0
- nautobot/ipam/tests/test_views.py +15 -2
- nautobot/ipam/urls.py +1 -21
- nautobot/ipam/views.py +24 -41
- nautobot/project-static/css/base.css +11 -0
- nautobot/project-static/css/dark.css +2 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +62 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +43 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +35 -0
- nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +94 -1
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -3
- nautobot/project-static/docs/development/apps/api/prometheus.html +0 -3
- nautobot/project-static/docs/development/apps/api/testing.html +0 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +0 -3
- nautobot/project-static/docs/development/apps/index.html +2 -35
- nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
- nautobot/project-static/docs/development/core/application-registry.html +0 -6
- nautobot/project-static/docs/development/core/best-practices.html +0 -27
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +58 -4
- nautobot/project-static/docs/development/core/getting-started.html +12 -16
- nautobot/project-static/docs/development/core/homepage.html +0 -3
- nautobot/project-static/docs/development/core/style-guide.html +0 -5
- nautobot/project-static/docs/development/core/templates.html +0 -3
- nautobot/project-static/docs/development/core/testing.html +0 -9
- nautobot/project-static/docs/development/jobs/index.html +30 -43
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +0 -18
- nautobot/project-static/docs/release-notes/version-2.4.html +374 -0
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +0 -10
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -15
- nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -16
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +5 -35
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -17
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -7
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -15
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -26
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -19
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -9
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -13
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -5
- nautobot/project-static/js/editor.js +292 -0
- nautobot/project-static/monaco-editor-0.52.2/README.md +81 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/worker/workerMain.js +31 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/xml/xml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/yaml/yaml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.css +8 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.js +798 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonMode.js +19 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonWorker.js +42 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/loader.js +11 -0
- nautobot/tenancy/filters/__init__.py +3 -5
- nautobot/tenancy/tests/test_filters.py +10 -0
- nautobot/virtualization/views.py +0 -1
- nautobot/wireless/tables.py +9 -4
- nautobot/wireless/tests/test_api.py +0 -9
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/METADATA +4 -4
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/RECORD +198 -186
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/NOTICE +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/WHEEL +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/entry_points.txt +0 -0
nautobot/__init__.py
CHANGED
|
@@ -14,18 +14,34 @@ __initialized = False
|
|
|
14
14
|
|
|
15
15
|
def add_success_logger():
|
|
16
16
|
"""Add a custom log level for success messages."""
|
|
17
|
-
SUCCESS = 25
|
|
17
|
+
SUCCESS = 25 # between INFO and WARNING
|
|
18
18
|
logging.addLevelName(SUCCESS, "SUCCESS")
|
|
19
19
|
|
|
20
|
-
def success(self, message, *args, **
|
|
20
|
+
def success(self, message, *args, **kwargs):
|
|
21
|
+
kwargs["stacklevel"] = kwargs.get("stacklevel", 1) + 1 # so that funcName is the caller function, not "success"
|
|
21
22
|
if self.isEnabledFor(SUCCESS):
|
|
22
|
-
self._log(SUCCESS, message, args, **
|
|
23
|
+
self._log(SUCCESS, message, args, **kwargs)
|
|
23
24
|
|
|
24
25
|
logging.Logger.success = success
|
|
25
26
|
return success
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def add_failure_logger():
|
|
30
|
+
"""Add a custom log level for failure messages less severe than an ERROR."""
|
|
31
|
+
FAILURE = 35 # between WARNING and ERROR
|
|
32
|
+
logging.addLevelName(FAILURE, "FAILURE")
|
|
33
|
+
|
|
34
|
+
def failure(self, message, *args, **kwargs):
|
|
35
|
+
kwargs["stacklevel"] = kwargs.get("stacklevel", 1) + 1 # so that funcName is the caller function, not "failure"
|
|
36
|
+
if self.isEnabledFor(FAILURE):
|
|
37
|
+
self._log(FAILURE, message, args, **kwargs)
|
|
38
|
+
|
|
39
|
+
logging.Logger.failure = failure
|
|
40
|
+
return failure
|
|
41
|
+
|
|
42
|
+
|
|
28
43
|
add_success_logger()
|
|
44
|
+
add_failure_logger()
|
|
29
45
|
logger = logging.getLogger(__name__)
|
|
30
46
|
|
|
31
47
|
|
nautobot/apps/filters.py
CHANGED
|
@@ -43,6 +43,7 @@ from nautobot.extras.filters.mixins import (
|
|
|
43
43
|
StatusFilter,
|
|
44
44
|
)
|
|
45
45
|
from nautobot.extras.plugins import FilterExtension
|
|
46
|
+
from nautobot.ipam.filters import PrefixFilter
|
|
46
47
|
from nautobot.tenancy.filters import TenancyModelFilterSetMixin
|
|
47
48
|
|
|
48
49
|
__all__ = (
|
|
@@ -72,6 +73,7 @@ __all__ = (
|
|
|
72
73
|
"NaturalKeyOrPKMultipleChoiceFilter",
|
|
73
74
|
"NautobotFilterSet",
|
|
74
75
|
"NumericArrayFilter",
|
|
76
|
+
"PrefixFilter",
|
|
75
77
|
"RelatedMembershipBooleanFilter",
|
|
76
78
|
"RelationshipFilter",
|
|
77
79
|
"RelationshipModelFilterSetMixin",
|
nautobot/circuits/filters.py
CHANGED
|
@@ -16,7 +16,7 @@ from nautobot.dcim.filters import (
|
|
|
16
16
|
)
|
|
17
17
|
from nautobot.dcim.models import Location
|
|
18
18
|
from nautobot.extras.filters import NautobotFilterSet, StatusModelFilterSetMixin
|
|
19
|
-
from nautobot.tenancy.filters import TenancyModelFilterSetMixin
|
|
19
|
+
from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin
|
|
20
20
|
|
|
21
21
|
from .models import Circuit, CircuitTermination, CircuitType, Provider, ProviderNetwork
|
|
22
22
|
|
|
@@ -17,8 +17,7 @@ class CircuitTerminationModelTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
17
17
|
provider = Provider.objects.first()
|
|
18
18
|
circuit_type = CircuitType.objects.first()
|
|
19
19
|
|
|
20
|
-
location_type_1 = LocationType.objects.
|
|
21
|
-
location_type_1.content_types.set([])
|
|
20
|
+
location_type_1 = LocationType.objects.create(name="University")
|
|
22
21
|
location_type_2 = LocationType.objects.get(name="Building")
|
|
23
22
|
location_type_2.content_types.add(ContentType.objects.get_for_model(CircuitTermination))
|
|
24
23
|
status = Status.objects.get_for_model(Circuit).first()
|
|
@@ -26,7 +25,10 @@ class CircuitTerminationModelTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
26
25
|
cid="Circuit 1", provider=provider, circuit_type=circuit_type, status=status
|
|
27
26
|
)
|
|
28
27
|
cls.provider_network = ProviderNetwork.objects.create(name="Provider Network 1", provider=provider)
|
|
29
|
-
|
|
28
|
+
location_status = Status.objects.get_for_model(Location).first()
|
|
29
|
+
cls.location_1 = Location.objects.create(
|
|
30
|
+
name="Department", location_type=location_type_1, status=location_status
|
|
31
|
+
)
|
|
30
32
|
cls.location_2 = Location.objects.filter(location_type=location_type_2)[0]
|
|
31
33
|
|
|
32
34
|
cloud_resource_type = CloudResourceType.objects.get_for_model(CloudNetwork).first()
|
nautobot/cloud/filters.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import django_filters
|
|
2
|
-
|
|
3
1
|
from nautobot.cloud import models
|
|
4
2
|
from nautobot.core.filters import (
|
|
5
3
|
BaseFilterSet,
|
|
@@ -11,7 +9,7 @@ from nautobot.dcim.models import Manufacturer
|
|
|
11
9
|
from nautobot.extras.filters import NautobotFilterSet
|
|
12
10
|
from nautobot.extras.models import SecretsGroup
|
|
13
11
|
from nautobot.extras.utils import FeatureQuery
|
|
14
|
-
from nautobot.ipam.
|
|
12
|
+
from nautobot.ipam.filters import PrefixFilter
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
class CloudAccountFilterSet(NautobotFilterSet):
|
|
@@ -98,7 +96,7 @@ class CloudNetworkFilterSet(NautobotFilterSet):
|
|
|
98
96
|
queryset=models.CloudNetwork.objects.all(),
|
|
99
97
|
label="Parent cloud network (name or ID)",
|
|
100
98
|
)
|
|
101
|
-
prefixes =
|
|
99
|
+
prefixes = PrefixFilter()
|
|
102
100
|
|
|
103
101
|
class Meta:
|
|
104
102
|
model = models.CloudNetwork
|
|
@@ -117,8 +115,7 @@ class CloudNetworkPrefixAssignmentFilterSet(BaseFilterSet):
|
|
|
117
115
|
queryset=models.CloudNetwork.objects.all(),
|
|
118
116
|
label="Cloud network (name or ID)",
|
|
119
117
|
)
|
|
120
|
-
|
|
121
|
-
prefix = django_filters.ModelMultipleChoiceFilter(queryset=Prefix.objects.all())
|
|
118
|
+
prefix = PrefixFilter()
|
|
122
119
|
|
|
123
120
|
class Meta:
|
|
124
121
|
model = models.CloudNetworkPrefixAssignment
|
|
@@ -65,6 +65,7 @@ class CloudNetworkTestCase(FilterTestCases.FilterTestCase):
|
|
|
65
65
|
("name",),
|
|
66
66
|
("parent", "parent__id"),
|
|
67
67
|
("parent", "parent__name"),
|
|
68
|
+
("prefixes", "prefixes__id"),
|
|
68
69
|
]
|
|
69
70
|
exclude_q_filter_predicates = [
|
|
70
71
|
"parent__name",
|
|
@@ -79,6 +80,16 @@ class CloudNetworkTestCase(FilterTestCases.FilterTestCase):
|
|
|
79
80
|
queryset = queryset.filter(children__isnull=True)
|
|
80
81
|
return queryset
|
|
81
82
|
|
|
83
|
+
def test_prefixes_filter_by_string(self):
|
|
84
|
+
"""Test filtering by prefix strings as an alternative to pk."""
|
|
85
|
+
prefix = self.queryset.filter(prefixes__isnull=False).first().prefixes.first()
|
|
86
|
+
params = {"prefixes": [prefix.prefix]}
|
|
87
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
88
|
+
self.filterset(params, self.queryset).qs,
|
|
89
|
+
self.queryset.filter(prefixes__network=prefix.network, prefixes__prefix_length=prefix.prefix_length),
|
|
90
|
+
ordered=False,
|
|
91
|
+
)
|
|
92
|
+
|
|
82
93
|
|
|
83
94
|
class CloudNetworkPrefixAssignmentTestCase(FilterTestCases.FilterTestCase):
|
|
84
95
|
queryset = models.CloudNetworkPrefixAssignment.objects.all()
|
|
@@ -89,6 +100,16 @@ class CloudNetworkPrefixAssignmentTestCase(FilterTestCases.FilterTestCase):
|
|
|
89
100
|
("prefix", "prefix__id"),
|
|
90
101
|
]
|
|
91
102
|
|
|
103
|
+
def test_prefix_filter_by_string(self):
|
|
104
|
+
"""Test filtering by prefix strings as an alternative to pk."""
|
|
105
|
+
prefix = self.queryset.first().prefix
|
|
106
|
+
params = {"prefix": [prefix.prefix]}
|
|
107
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
108
|
+
self.filterset(params, self.queryset).qs,
|
|
109
|
+
self.queryset.filter(prefix__network=prefix.network, prefix__prefix_length=prefix.prefix_length),
|
|
110
|
+
ordered=False,
|
|
111
|
+
)
|
|
112
|
+
|
|
92
113
|
|
|
93
114
|
class CloudServiceNetworkAssignmentTestCase(FilterTestCases.FilterTestCase):
|
|
94
115
|
queryset = models.CloudServiceNetworkAssignment.objects.all()
|
nautobot/core/admin.py
CHANGED
|
@@ -9,7 +9,9 @@ from django_celery_beat.models import (
|
|
|
9
9
|
PeriodicTask,
|
|
10
10
|
SolarSchedule,
|
|
11
11
|
)
|
|
12
|
+
import social_django.admin # noqa: F401 # unused-import -- but this import installs the social_django admin
|
|
12
13
|
from social_django.models import Association, Nonce, UserSocialAuth
|
|
14
|
+
import taggit.admin # noqa: F401 # unused-import -- but this import installs the taggit admin
|
|
13
15
|
from taggit.models import Tag
|
|
14
16
|
|
|
15
17
|
from nautobot.core.forms import BootstrapMixin
|
nautobot/core/celery/__init__.py
CHANGED
|
@@ -14,7 +14,7 @@ from django.utils.module_loading import import_string
|
|
|
14
14
|
from kombu.serialization import register
|
|
15
15
|
from prometheus_client import CollectorRegistry, multiprocess, start_http_server
|
|
16
16
|
|
|
17
|
-
from nautobot import add_success_logger
|
|
17
|
+
from nautobot import add_failure_logger, add_success_logger
|
|
18
18
|
from nautobot.core.celery.control import discard_git_repository, refresh_git_repository # noqa: F401 # unused-import
|
|
19
19
|
from nautobot.core.celery.encoders import NautobotKombuJSONEncoder
|
|
20
20
|
from nautobot.core.celery.log import NautobotDatabaseHandler
|
|
@@ -138,14 +138,16 @@ def add_nautobot_log_handler(logger_instance, log_format=None):
|
|
|
138
138
|
|
|
139
139
|
@signals.after_setup_logger.connect
|
|
140
140
|
def setup_nautobot_global_logging(logger, **kwargs): # pylint: disable=redefined-outer-name
|
|
141
|
-
"""Add SUCCESS
|
|
141
|
+
"""Add SUCCESS and FAILURE logs to celery global logger."""
|
|
142
142
|
logger.success = add_success_logger()
|
|
143
|
+
logger.failure = add_failure_logger()
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
@signals.after_setup_task_logger.connect
|
|
146
147
|
def setup_nautobot_task_logging(logger, **kwargs): # pylint: disable=redefined-outer-name
|
|
147
|
-
"""Add SUCCESS
|
|
148
|
+
"""Add SUCCESS and FAILURE logs to celery task logger."""
|
|
148
149
|
logger.success = add_success_logger()
|
|
150
|
+
logger.failure = add_failure_logger()
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
@signals.celeryd_after_setup.connect
|
nautobot/core/jobs/__init__.py
CHANGED
|
@@ -229,7 +229,8 @@ class ExportObjectList(Job):
|
|
|
229
229
|
# The force_csv=True attribute is a hack, but much easier than trying to construct a valid HttpRequest
|
|
230
230
|
# object from scratch that passes all implicit and explicit assumptions in Django and DRF.
|
|
231
231
|
serializer = serializer_class(queryset, many=True, context={"request": None}, force_csv=True)
|
|
232
|
-
|
|
232
|
+
# Explicitly add UTF-8 BOM to the data so that Excel will understand non-ASCII characters correctly...
|
|
233
|
+
csv_data = codecs.BOM_UTF8 + renderer.render(serializer.data).encode("utf-8")
|
|
233
234
|
self.create_file(filename + ".csv", csv_data)
|
|
234
235
|
|
|
235
236
|
|
|
@@ -294,8 +295,9 @@ class ImportObjects(Job):
|
|
|
294
295
|
validation_failed = True
|
|
295
296
|
else:
|
|
296
297
|
validation_failed = True
|
|
297
|
-
for field,
|
|
298
|
-
|
|
298
|
+
for field, errs in serializer.errors.items():
|
|
299
|
+
for err in errs:
|
|
300
|
+
self.logger.error("Row %d: `%s`: `%s`", row, field, err)
|
|
299
301
|
return new_objs, validation_failed
|
|
300
302
|
|
|
301
303
|
def run(self, *, content_type, csv_data=None, csv_file=None, roll_back_if_error=False): # pylint:disable=arguments-differ
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from django.apps import apps
|
|
3
4
|
from django.conf import settings
|
|
4
5
|
from django.core.management.base import BaseCommand
|
|
5
6
|
from django.urls import get_resolver
|
|
@@ -162,6 +163,10 @@ class Command(BaseCommand):
|
|
|
162
163
|
if model:
|
|
163
164
|
app_name = model._meta.app_label
|
|
164
165
|
|
|
166
|
+
# Retrieve the base URL for the app to be used in the URL pattern
|
|
167
|
+
app_config = apps.get_app_config(app_name)
|
|
168
|
+
base_url = app_config.base_url if hasattr(app_config, "base_url") else app_name
|
|
169
|
+
|
|
165
170
|
if app_name == "users" and pattern.name in ["login", "logout"]:
|
|
166
171
|
# No need to test the login and logout endpoints for performance testing
|
|
167
172
|
url_pattern = f"/{pattern.pattern}" # /login, /logout
|
|
@@ -199,7 +204,7 @@ class Command(BaseCommand):
|
|
|
199
204
|
elif is_api_endpoint:
|
|
200
205
|
if not is_app:
|
|
201
206
|
# One of the nautobot apps: nautobot.circuits, nautobot.dcim, and etc.
|
|
202
|
-
url_pattern = f"/api/{
|
|
207
|
+
url_pattern = f"/api/{base_url}/{pattern.pattern}" # /api/dcim/devices/
|
|
203
208
|
app_name = f"{app_name}-api" # dcim-api
|
|
204
209
|
view_name = f"{app_name}:{pattern.name}" # dcim-api:device-list
|
|
205
210
|
else:
|
|
@@ -207,16 +212,14 @@ class Command(BaseCommand):
|
|
|
207
212
|
view_name = (
|
|
208
213
|
f"plugins-api:{api_app_name}:{pattern.name}" # plugins-api:example_app-api:examplemodel-list
|
|
209
214
|
)
|
|
210
|
-
|
|
211
|
-
url_pattern = f"/api/plugins/{app_name}/{pattern.pattern}" # /api/plugins/example-app/models/
|
|
215
|
+
url_pattern = f"/api/plugins/{base_url}/{pattern.pattern}" # /api/plugins/example-app/models/
|
|
212
216
|
else:
|
|
213
217
|
if not is_app:
|
|
214
|
-
url_pattern = f"/{
|
|
218
|
+
url_pattern = f"/{base_url}/{pattern.pattern}" # /dcim/devices/
|
|
215
219
|
view_name = f"{app_name}:{pattern.name}" # dcim:device_list
|
|
216
220
|
else:
|
|
217
221
|
view_name = f"plugins:{app_name}:{pattern.name}" # plugins:example_app:examplemodel_list
|
|
218
|
-
|
|
219
|
-
url_pattern = f"/plugins/{app_name}/{pattern.pattern}" # /plugins/example-app/models/
|
|
222
|
+
url_pattern = f"/plugins/{base_url}/{pattern.pattern}" # /plugins/example-app/models/
|
|
220
223
|
|
|
221
224
|
return url_pattern, view_name, is_api_endpoint
|
|
222
225
|
|
nautobot/core/models/utils.py
CHANGED
|
@@ -112,7 +112,12 @@ def serialize_object(obj, extra=None, exclude=None):
|
|
|
112
112
|
|
|
113
113
|
# Include any tags. Check for tags cached on the instance; fall back to using the manager.
|
|
114
114
|
if is_taggable(obj):
|
|
115
|
-
|
|
115
|
+
# Note that when upgrading from Nautobot 1.x to 2.0, this method may be called during data migrations,
|
|
116
|
+
# specifically ipam_0022 and dcim_0034, to create ObjectChange records.
|
|
117
|
+
# This can be problematic (see issue #6952) as the Tag records in the DB still have `created` as a `DateField`,
|
|
118
|
+
# but the 2.x code expects this to be a `DateTimeField` (as it will be after the upgrade completes in full).
|
|
119
|
+
# We "cleverly" bypass that issue by using `.only("name")` since that's the only actual Tag field we need here.
|
|
120
|
+
tags = getattr(obj, "_tags", []) or obj.tags.only("name")
|
|
116
121
|
data["tags"] = [tag.name for tag in tags]
|
|
117
122
|
|
|
118
123
|
# Append any extra data
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<script src="{% versioned_static 'js/dropdown.js' %}"
|
|
28
28
|
onerror="window.location='{% url 'media_failure' %}?filename=js/dropdown.js'"></script>
|
|
29
29
|
<script type="text/javascript">
|
|
30
|
+
var nautobot_static_url = "{% static '' %}";
|
|
30
31
|
var nautobot_api_path = "{% url 'api-root' %}";
|
|
31
32
|
var nautobot_csrf_token = "{{ csrf_token }}";
|
|
32
33
|
var loading = $(".loading");
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from django import template
|
|
2
4
|
from django.utils.html import format_html_join
|
|
3
5
|
|
|
4
6
|
from nautobot.core.utils.lookup import get_view_for_model
|
|
5
7
|
from nautobot.core.views.utils import get_obj_from_context
|
|
6
8
|
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
7
11
|
register = template.Library()
|
|
8
12
|
|
|
9
13
|
|
|
@@ -26,15 +30,27 @@ def render_components(context, components):
|
|
|
26
30
|
@register.simple_tag(takes_context=True)
|
|
27
31
|
def render_detail_view_extra_buttons(context):
|
|
28
32
|
"""
|
|
29
|
-
Render the "extra_buttons"
|
|
33
|
+
Render the "extra_buttons" from the context's object_detail_content, or as fallback, from the base detail view.
|
|
30
34
|
|
|
31
35
|
This makes it possible for "extra" tabs (such as Changelog and Notes, and any added by App TemplateExtensions)
|
|
32
36
|
to automatically still render any `extra_buttons` defined by the base detail view, without the tab-specific views
|
|
33
37
|
needing to explicitly inherit from the base view.
|
|
34
38
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
object_detail_content = context.get("object_detail_content")
|
|
40
|
+
if object_detail_content is None:
|
|
41
|
+
obj = get_obj_from_context(context)
|
|
42
|
+
if obj is None:
|
|
43
|
+
logger.error("No 'obj' or 'object' found in the render context!")
|
|
44
|
+
return ""
|
|
45
|
+
base_detail_view = get_view_for_model(obj)
|
|
46
|
+
if base_detail_view is None:
|
|
47
|
+
logger.warning(
|
|
48
|
+
"Unable to identify the base detail view - check that it has a valid name, i.e. %sUIViewSet or %sView",
|
|
49
|
+
type(obj).__name__,
|
|
50
|
+
type(obj).__name__,
|
|
51
|
+
)
|
|
52
|
+
return ""
|
|
53
|
+
object_detail_content = getattr(base_detail_view, "object_detail_content", None)
|
|
38
54
|
if object_detail_content is not None and object_detail_content.extra_buttons:
|
|
39
55
|
return render_components(context, object_detail_content.extra_buttons)
|
|
40
56
|
return ""
|
|
@@ -68,6 +68,8 @@ def run_job_for_testing(job, username="test-user", profile=False, **kwargs):
|
|
|
68
68
|
username=username, defaults={"is_superuser": True, "password": "password"}
|
|
69
69
|
)
|
|
70
70
|
# Run the job synchronously in the current thread as if it were being executed by a worker
|
|
71
|
+
# TODO: in Nautobot core testing, we set `CELERY_TASK_ALWAYS_EAGER = True`, so we *could* use enqueue_job() instead,
|
|
72
|
+
# but switching now would be a potentially breaking change for apps...
|
|
71
73
|
job_result = JobResult.execute_job(
|
|
72
74
|
job_model=job,
|
|
73
75
|
user=user_instance,
|
nautobot/core/testing/forms.py
CHANGED
|
@@ -24,7 +24,7 @@ class FormTestCases:
|
|
|
24
24
|
self.skipTest(f"{self.form_class.__name__}.{field_name} has no query_params")
|
|
25
25
|
field_model = field_class.queryset.model
|
|
26
26
|
filterset_class = get_filterset_for_model(field_model)
|
|
27
|
-
filterset_fields = set(filterset_class.
|
|
27
|
+
filterset_fields = set(filterset_class.get_filters().keys())
|
|
28
28
|
invalid_query_params = query_params_fields - filterset_fields
|
|
29
29
|
self.assertFalse(
|
|
30
30
|
invalid_query_params,
|
nautobot/core/testing/mixins.py
CHANGED
|
@@ -18,6 +18,7 @@ from nautobot.core.models import fields as core_fields
|
|
|
18
18
|
from nautobot.core.testing import utils
|
|
19
19
|
from nautobot.core.utils import permissions
|
|
20
20
|
from nautobot.extras import management, models as extras_models
|
|
21
|
+
from nautobot.extras.choices import JobResultStatusChoices
|
|
21
22
|
from nautobot.users import models as users_models
|
|
22
23
|
|
|
23
24
|
# Use the proper swappable User model
|
|
@@ -188,6 +189,14 @@ class NautobotTestCaseMixin:
|
|
|
188
189
|
err_message = f"{msg}\n{err_message}"
|
|
189
190
|
self.assertIn(response.status_code, expected_status, err_message)
|
|
190
191
|
|
|
192
|
+
def assertJobResultStatus(self, job_result, expected_status=JobResultStatusChoices.STATUS_SUCCESS):
|
|
193
|
+
"""Assert that the given job_result has the expected_status, or print the job logs to aid in debugging."""
|
|
194
|
+
self.assertEqual(
|
|
195
|
+
job_result.status,
|
|
196
|
+
expected_status,
|
|
197
|
+
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
198
|
+
)
|
|
199
|
+
|
|
191
200
|
def assertInstanceEqual(self, instance, data, exclude=None, api=False):
|
|
192
201
|
"""
|
|
193
202
|
Compare a model instance to a dictionary, checking that its attribute values match those specified
|
nautobot/core/tests/test_api.py
CHANGED
|
@@ -609,7 +609,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
|
|
|
609
609
|
dcim_models.LocationType.objects.get(name="Building"),
|
|
610
610
|
]
|
|
611
611
|
for location_type in self.locations_types:
|
|
612
|
-
location_type.content_types.
|
|
612
|
+
location_type.content_types.add(vlan_group_ct, vlan_ct)
|
|
613
613
|
|
|
614
614
|
self.statuses = extras_models.Status.objects.get_for_model(dcim_models.Location)
|
|
615
615
|
self.location1 = dcim_models.Location.objects.create(
|
|
@@ -8,7 +8,7 @@ from django.apps import apps
|
|
|
8
8
|
from django.contrib.auth import get_user_model
|
|
9
9
|
from django.contrib.auth.models import Group
|
|
10
10
|
from django.contrib.contenttypes.models import ContentType
|
|
11
|
-
from django.db.models import Q
|
|
11
|
+
from django.db.models import Count, Q
|
|
12
12
|
from django.test import override_settings, TestCase
|
|
13
13
|
from django.test.client import RequestFactory
|
|
14
14
|
from django.urls import reverse
|
|
@@ -919,8 +919,8 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
919
919
|
priority=789,
|
|
920
920
|
),
|
|
921
921
|
)
|
|
922
|
-
|
|
923
|
-
|
|
922
|
+
cls.namespace = Namespace.objects.annotate(prefix_count=Count("prefixes")).filter(prefix_count__gt=2).first()
|
|
923
|
+
prefixes = Prefix.objects.filter(namespace=cls.namespace)
|
|
924
924
|
vrfs = (
|
|
925
925
|
VRF.objects.create(name="VRF 1", rd="65000:100", namespace=cls.namespace),
|
|
926
926
|
VRF.objects.create(name="VRF 2", rd="65000:200", namespace=cls.namespace),
|