nautobot 2.4.5__py3-none-any.whl → 2.4.6__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.
- nautobot/core/api/mixins.py +10 -0
- nautobot/core/celery/encoders.py +2 -2
- nautobot/core/forms/fields.py +21 -5
- nautobot/core/forms/utils.py +1 -0
- nautobot/core/jobs/bulk_actions.py +1 -1
- nautobot/core/management/commands/generate_test_data.py +1 -1
- nautobot/core/models/name_color_content_types.py +9 -0
- nautobot/core/models/validators.py +7 -0
- nautobot/core/settings.py +0 -14
- nautobot/core/settings.yaml +0 -28
- nautobot/core/tables.py +6 -1
- nautobot/core/templates/generic/object_retrieve.html +1 -1
- nautobot/core/testing/api.py +18 -0
- nautobot/core/tests/nautobot_config.py +0 -2
- nautobot/core/tests/runner.py +17 -140
- nautobot/core/tests/test_api.py +4 -4
- nautobot/core/tests/test_authentication.py +83 -4
- nautobot/core/tests/test_forms.py +11 -8
- nautobot/core/tests/test_graphql.py +9 -0
- nautobot/core/tests/test_jobs.py +7 -0
- nautobot/core/ui/object_detail.py +31 -0
- nautobot/dcim/factory.py +2 -0
- nautobot/dcim/filters/__init__.py +5 -0
- nautobot/dcim/forms.py +17 -1
- nautobot/dcim/migrations/0068_alter_softwareimagefile_download_url.py +19 -0
- nautobot/dcim/migrations/0069_softwareimagefile_external_integration.py +25 -0
- nautobot/dcim/models/devices.py +9 -2
- nautobot/dcim/tables/devices.py +1 -0
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +4 -0
- nautobot/dcim/tests/test_api.py +74 -31
- nautobot/dcim/tests/test_filters.py +2 -0
- nautobot/dcim/tests/test_models.py +65 -0
- nautobot/dcim/tests/test_views.py +3 -0
- nautobot/extras/forms/forms.py +7 -3
- nautobot/extras/plugins/marketplace_manifest.yml +18 -0
- nautobot/extras/tables.py +4 -5
- nautobot/extras/templates/extras/inc/panel_changelog.html +1 -1
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -37
- nautobot/extras/tests/integration/test_notes.py +1 -1
- nautobot/extras/tests/test_api.py +22 -7
- nautobot/extras/tests/test_changelog.py +4 -4
- nautobot/extras/tests/test_customfields.py +3 -0
- nautobot/extras/tests/test_plugins.py +19 -13
- nautobot/extras/tests/test_relationships.py +9 -0
- nautobot/extras/tests/test_tags.py +2 -2
- nautobot/extras/tests/test_views.py +15 -6
- nautobot/extras/urls.py +1 -30
- nautobot/extras/views.py +10 -54
- nautobot/ipam/tables.py +6 -2
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -41
- nautobot/ipam/templates/ipam/service.html +2 -46
- nautobot/ipam/templates/ipam/service_edit.html +1 -17
- nautobot/ipam/templates/ipam/service_retrieve.html +7 -0
- nautobot/ipam/tests/migration/__init__.py +0 -0
- nautobot/ipam/tests/migration/test_migrations.py +510 -0
- nautobot/ipam/tests/test_api.py +66 -36
- nautobot/ipam/tests/test_filters.py +0 -10
- nautobot/ipam/tests/test_views.py +44 -2
- nautobot/ipam/urls.py +2 -47
- nautobot/ipam/utils/migrations.py +185 -152
- nautobot/ipam/utils/testing.py +177 -0
- nautobot/ipam/views.py +95 -157
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +47 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +63 -0
- nautobot/project-static/docs/development/apps/api/testing.html +0 -87
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
- nautobot/project-static/docs/development/core/best-practices.html +3 -3
- nautobot/project-static/docs/development/core/getting-started.html +78 -107
- nautobot/project-static/docs/development/core/release-checklist.html +1 -1
- nautobot/project-static/docs/development/core/style-guide.html +1 -1
- nautobot/project-static/docs/development/core/testing.html +24 -198
- nautobot/project-static/docs/media/user-guide/administration/getting-started/nautobot-cloud.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.4.html +226 -1
- 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 +2 -48
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +71 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -1
- nautobot/project-static/docs/user-guide/administration/installation/index.html +257 -16
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -0
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +8 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +40 -25
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +77 -5
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -1
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
- nautobot/project-static/docs/user-guide/index.html +89 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +207 -122
- nautobot/virtualization/forms.py +20 -0
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -39
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -0
- nautobot/virtualization/tests/test_api.py +14 -3
- nautobot/virtualization/tests/test_views.py +10 -2
- nautobot/virtualization/urls.py +10 -93
- nautobot/virtualization/views.py +33 -72
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/METADATA +6 -5
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/RECORD +113 -108
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/WHEEL +1 -1
- nautobot/core/tests/performance_baselines.yml +0 -8900
- nautobot/ipam/tests/test_migrations.py +0 -462
- /nautobot/ipam/templates/ipam/{namespace_ipaddresses.html → namespace_ip_addresses.html} +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/NOTICE +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/entry_points.txt +0 -0
|
@@ -292,7 +292,7 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
|
|
292
292
|
schema = ConfigContextSchema.objects.create(
|
|
293
293
|
name="Schema 1", data_schema={"type": "object", "properties": {"foo": {"type": "string"}}}
|
|
294
294
|
)
|
|
295
|
-
self.add_permissions("extras.add_configcontext")
|
|
295
|
+
self.add_permissions("extras.add_configcontext", "extras.view_configcontextschema")
|
|
296
296
|
|
|
297
297
|
data = {
|
|
298
298
|
"name": "Config Context with schema",
|
|
@@ -2248,7 +2248,7 @@ class JobHookTest(APIViewTestCases.APIViewTestCase):
|
|
|
2248
2248
|
"type_delete": True,
|
|
2249
2249
|
}
|
|
2250
2250
|
|
|
2251
|
-
self.add_permissions("extras.add_jobhook")
|
|
2251
|
+
self.add_permissions("extras.add_jobhook", "extras.view_job")
|
|
2252
2252
|
response = self.client.post(self._get_list_url(), data, format="json", **self.header)
|
|
2253
2253
|
self.assertContains(
|
|
2254
2254
|
response,
|
|
@@ -2264,7 +2264,7 @@ class JobHookTest(APIViewTestCases.APIViewTestCase):
|
|
|
2264
2264
|
"type_delete": True,
|
|
2265
2265
|
}
|
|
2266
2266
|
|
|
2267
|
-
self.add_permissions("extras.change_jobhook")
|
|
2267
|
+
self.add_permissions("extras.change_jobhook", "extras.view_job")
|
|
2268
2268
|
job_hook2 = JobHook.objects.get(name="JobHook2")
|
|
2269
2269
|
response = self.client.patch(self._get_detail_url(job_hook2), data, format="json", **self.header)
|
|
2270
2270
|
self.assertContains(
|
|
@@ -2553,7 +2553,7 @@ class UserSavedViewAssociationTest(APIViewTestCases.APIViewTestCase):
|
|
|
2553
2553
|
"saved_view": saved_view.pk,
|
|
2554
2554
|
"view_name": duplicate_view_name,
|
|
2555
2555
|
}
|
|
2556
|
-
self.add_permissions("extras.add_usersavedviewassociation")
|
|
2556
|
+
self.add_permissions("extras.add_usersavedviewassociation", "users.view_user", "extras.view_savedview")
|
|
2557
2557
|
response = self.client.post(
|
|
2558
2558
|
self._get_list_url(), duplicate_user_to_savedview_create_data, format="json", **self.header
|
|
2559
2559
|
)
|
|
@@ -3240,7 +3240,15 @@ class RelationshipTest(APIViewTestCases.APIViewTestCase, RequiredRelationshipTes
|
|
|
3240
3240
|
location=existing_location_2,
|
|
3241
3241
|
)
|
|
3242
3242
|
|
|
3243
|
-
self.add_permissions(
|
|
3243
|
+
self.add_permissions(
|
|
3244
|
+
"dcim.view_location",
|
|
3245
|
+
"dcim.view_locationtype",
|
|
3246
|
+
"dcim.view_device",
|
|
3247
|
+
"dcim.add_location",
|
|
3248
|
+
"extras.view_relationship",
|
|
3249
|
+
"extras.add_relationshipassociation",
|
|
3250
|
+
"extras.view_status",
|
|
3251
|
+
)
|
|
3244
3252
|
response = self.client.post(
|
|
3245
3253
|
reverse("dcim-api:location-list"),
|
|
3246
3254
|
data={
|
|
@@ -3564,7 +3572,9 @@ class RelationshipAssociationTest(APIViewTestCases.APIViewTestCase):
|
|
|
3564
3572
|
),
|
|
3565
3573
|
]
|
|
3566
3574
|
|
|
3567
|
-
self.add_permissions(
|
|
3575
|
+
self.add_permissions(
|
|
3576
|
+
"extras.add_relationshipassociation", "dcim.view_device", "dcim.view_location", "extras.view_relationship"
|
|
3577
|
+
)
|
|
3568
3578
|
|
|
3569
3579
|
for side, field_error_name, data in associations:
|
|
3570
3580
|
response = self.client.post(self._get_list_url(), data, format="json", **self.header)
|
|
@@ -3585,7 +3595,9 @@ class RelationshipAssociationTest(APIViewTestCases.APIViewTestCase):
|
|
|
3585
3595
|
"destination_id": self.devices[2].pk,
|
|
3586
3596
|
}
|
|
3587
3597
|
|
|
3588
|
-
self.add_permissions(
|
|
3598
|
+
self.add_permissions(
|
|
3599
|
+
"extras.add_relationshipassociation", "extras.view_relationship", "dcim.view_device", "dcim.view_location"
|
|
3600
|
+
)
|
|
3589
3601
|
|
|
3590
3602
|
response = self.client.post(self._get_list_url(), data, format="json", **self.header)
|
|
3591
3603
|
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
@@ -3636,8 +3648,11 @@ class RelationshipAssociationTest(APIViewTestCases.APIViewTestCase):
|
|
|
3636
3648
|
Check that relationship-associations can be updated via the 'relationships' field.
|
|
3637
3649
|
"""
|
|
3638
3650
|
self.add_permissions(
|
|
3651
|
+
"dcim.view_device",
|
|
3639
3652
|
"dcim.view_location",
|
|
3640
3653
|
"dcim.change_location",
|
|
3654
|
+
"extras.view_relationship",
|
|
3655
|
+
"extras.view_relationshipassociation",
|
|
3641
3656
|
"extras.add_relationshipassociation",
|
|
3642
3657
|
"extras.delete_relationshipassociation",
|
|
3643
3658
|
)
|
|
@@ -277,7 +277,7 @@ class ChangeLogAPITest(APITestCase):
|
|
|
277
277
|
],
|
|
278
278
|
}
|
|
279
279
|
url = reverse("dcim-api:location-list")
|
|
280
|
-
self.add_permissions("dcim.add_location", "extras.view_status")
|
|
280
|
+
self.add_permissions("dcim.add_location", "dcim.view_locationtype", "extras.view_tag", "extras.view_status")
|
|
281
281
|
|
|
282
282
|
response = self.client.post(url, data, format="json", **self.header)
|
|
283
283
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
@@ -310,7 +310,7 @@ class ChangeLogAPITest(APITestCase):
|
|
|
310
310
|
},
|
|
311
311
|
"tags": [{"name": self.tags[2].name}],
|
|
312
312
|
}
|
|
313
|
-
self.add_permissions("dcim.change_location", "extras.view_status")
|
|
313
|
+
self.add_permissions("dcim.change_location", "extras.view_status", "dcim.view_locationtype", "extras.view_tag")
|
|
314
314
|
url = reverse("dcim-api:location-detail", kwargs={"pk": location.pk})
|
|
315
315
|
|
|
316
316
|
response = self.client.put(url, data, format="json", **self.header)
|
|
@@ -457,7 +457,7 @@ class ChangeLogAPITest(APITestCase):
|
|
|
457
457
|
"status": self.statuses[0].pk,
|
|
458
458
|
"location_type": location_type.pk,
|
|
459
459
|
}
|
|
460
|
-
self.add_permissions("dcim.add_location")
|
|
460
|
+
self.add_permissions("dcim.add_location", "dcim.view_locationtype", "extras.view_status")
|
|
461
461
|
url = reverse("dcim-api:location-list")
|
|
462
462
|
|
|
463
463
|
response = self.client.post(url, location_payload, format="json", **self.header)
|
|
@@ -492,7 +492,7 @@ class ChangeLogAPITest(APITestCase):
|
|
|
492
492
|
)
|
|
493
493
|
|
|
494
494
|
payload = {"tagged_vlans": [str(tagged_vlan.pk)], "description": "test vm interface m2m change"}
|
|
495
|
-
self.add_permissions("virtualization.change_vminterface", "ipam.change_vlan")
|
|
495
|
+
self.add_permissions("virtualization.change_vminterface", "ipam.change_vlan", "ipam.view_vlan")
|
|
496
496
|
url = reverse("virtualization-api:vminterface-detail", kwargs={"pk": vm_interface.pk})
|
|
497
497
|
response = self.client.patch(url, payload, format="json", **self.header)
|
|
498
498
|
vm_interface.refresh_from_db()
|
|
@@ -486,7 +486,7 @@ class TestUserContextCustomValidator(CustomValidator):
|
|
|
486
486
|
"""
|
|
487
487
|
Used to validate that the correct user context is available in the custom validator.
|
|
488
488
|
"""
|
|
489
|
-
self.validation_error(self.context[
|
|
489
|
+
self.validation_error(f"TestUserContextCustomValidator: user is {self.context['user']}")
|
|
490
490
|
|
|
491
491
|
|
|
492
492
|
class AppCustomValidationTest(TestCase):
|
|
@@ -527,22 +527,28 @@ class AppCustomValidationTest(TestCase):
|
|
|
527
527
|
|
|
528
528
|
def test_custom_validator_non_web_request_uses_anonymous_user(self):
|
|
529
529
|
location_type = LocationType.objects.get(name="Campus")
|
|
530
|
-
registry["plugin_custom_validators"]["dcim.locationtype"]
|
|
530
|
+
before = registry["plugin_custom_validators"]["dcim.locationtype"]
|
|
531
|
+
try:
|
|
532
|
+
registry["plugin_custom_validators"]["dcim.locationtype"] = [TestUserContextCustomValidator]
|
|
531
533
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
534
|
+
with self.assertRaises(ValidationError) as context:
|
|
535
|
+
location_type.clean()
|
|
536
|
+
self.assertEqual(context.exception.message, "TestUserContextCustomValidator: user is AnonymousUser")
|
|
537
|
+
finally:
|
|
538
|
+
registry["plugin_custom_validators"]["dcim.locationtype"] = before
|
|
537
539
|
|
|
538
540
|
def test_custom_validator_web_request_uses_real_user(self):
|
|
539
541
|
location_type = LocationType.objects.get(name="Campus")
|
|
540
|
-
registry["plugin_custom_validators"]["dcim.locationtype"]
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
542
|
+
before = registry["plugin_custom_validators"]["dcim.locationtype"]
|
|
543
|
+
try:
|
|
544
|
+
registry["plugin_custom_validators"]["dcim.locationtype"] = [TestUserContextCustomValidator]
|
|
545
|
+
|
|
546
|
+
with self.assertRaises(ValidationError) as context:
|
|
547
|
+
with web_request_context(user=self.user):
|
|
548
|
+
location_type.clean()
|
|
549
|
+
self.assertEqual(context.exception.message, f"TestUserContextCustomValidator: user is {self.user}")
|
|
550
|
+
finally:
|
|
551
|
+
registry["plugin_custom_validators"]["dcim.locationtype"] = before
|
|
546
552
|
|
|
547
553
|
|
|
548
554
|
class ExampleModelCustomActionViewTest(TestCase):
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import logging
|
|
2
3
|
import uuid
|
|
3
4
|
|
|
4
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.core.cache import cache
|
|
5
7
|
from django.core.exceptions import ValidationError
|
|
6
8
|
from django.urls import reverse
|
|
7
9
|
from django.utils.html import format_html
|
|
10
|
+
import redis.exceptions
|
|
8
11
|
|
|
9
12
|
from nautobot.circuits.models import CircuitType
|
|
10
13
|
from nautobot.core.forms import (
|
|
@@ -179,6 +182,12 @@ class RelationshipBaseTest:
|
|
|
179
182
|
),
|
|
180
183
|
]
|
|
181
184
|
|
|
185
|
+
def tearDown(self):
|
|
186
|
+
"""Ensure that relationship caches are cleared to avoid leakage into other tests."""
|
|
187
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
188
|
+
cache.delete_pattern(f"{Relationship.objects.get_for_model_source.cache_key_prefix}.*")
|
|
189
|
+
cache.delete_pattern(f"{Relationship.objects.get_for_model_destination.cache_key_prefix}.*")
|
|
190
|
+
|
|
182
191
|
|
|
183
192
|
class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
184
193
|
model = Relationship
|
|
@@ -46,7 +46,7 @@ class TaggedItemTest(APITestCase):
|
|
|
46
46
|
"location_type": self.location_type.pk,
|
|
47
47
|
}
|
|
48
48
|
url = reverse("dcim-api:location-list")
|
|
49
|
-
self.add_permissions("dcim.add_location")
|
|
49
|
+
self.add_permissions("dcim.add_location", "dcim.view_locationtype", "extras.view_tag", "extras.view_status")
|
|
50
50
|
|
|
51
51
|
response = self.client.post(url, data, format="json", **self.header)
|
|
52
52
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
@@ -67,7 +67,7 @@ class TaggedItemTest(APITestCase):
|
|
|
67
67
|
{"name": self.tags[3].name},
|
|
68
68
|
]
|
|
69
69
|
}
|
|
70
|
-
self.add_permissions("dcim.change_location")
|
|
70
|
+
self.add_permissions("dcim.change_location", "extras.view_tag")
|
|
71
71
|
url = reverse("dcim-api:location-detail", kwargs={"pk": location.pk})
|
|
72
72
|
|
|
73
73
|
response = self.client.patch(url, data, format="json", **self.header)
|
|
@@ -3584,23 +3584,28 @@ class StatusTestCase(
|
|
|
3584
3584
|
ViewTestCases.GetObjectViewTestCase,
|
|
3585
3585
|
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
3586
3586
|
ViewTestCases.ListObjectsViewTestCase,
|
|
3587
|
+
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
3587
3588
|
):
|
|
3588
3589
|
model = Status
|
|
3589
3590
|
|
|
3590
3591
|
@classmethod
|
|
3591
3592
|
def setUpTestData(cls):
|
|
3592
3593
|
# Status objects to test.
|
|
3593
|
-
|
|
3594
|
+
device_ct = ContentType.objects.get_for_model(Device)
|
|
3595
|
+
circuit_ct = ContentType.objects.get_for_model(Circuit)
|
|
3596
|
+
interface_ct = ContentType.objects.get_for_model(Interface)
|
|
3594
3597
|
|
|
3595
3598
|
cls.form_data = {
|
|
3596
3599
|
"name": "new_status",
|
|
3597
3600
|
"description": "I am a new status object.",
|
|
3598
3601
|
"color": "ffcc00",
|
|
3599
|
-
"content_types": [
|
|
3602
|
+
"content_types": [device_ct.pk],
|
|
3600
3603
|
}
|
|
3601
3604
|
|
|
3602
3605
|
cls.bulk_edit_data = {
|
|
3603
3606
|
"color": "000000",
|
|
3607
|
+
"add_content_types": [interface_ct.pk, circuit_ct.pk],
|
|
3608
|
+
"remove_content_types": [device_ct.pk],
|
|
3604
3609
|
}
|
|
3605
3610
|
|
|
3606
3611
|
|
|
@@ -3790,25 +3795,29 @@ class WebhookTestCase(
|
|
|
3790
3795
|
}
|
|
3791
3796
|
|
|
3792
3797
|
|
|
3793
|
-
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
3798
|
+
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases.BulkEditObjectsViewTestCase):
|
|
3794
3799
|
model = Role
|
|
3795
3800
|
|
|
3796
3801
|
@classmethod
|
|
3797
3802
|
def setUpTestData(cls):
|
|
3798
|
-
#
|
|
3799
|
-
|
|
3803
|
+
# Role objects to test.
|
|
3804
|
+
device_ct = ContentType.objects.get_for_model(Device)
|
|
3805
|
+
ipaddress_ct = ContentType.objects.get_for_model(IPAddress)
|
|
3806
|
+
prefix_ct = ContentType.objects.get_for_model(Prefix)
|
|
3800
3807
|
|
|
3801
3808
|
cls.form_data = {
|
|
3802
3809
|
"name": "New Role",
|
|
3803
3810
|
"description": "I am a new role object.",
|
|
3804
3811
|
"color": ColorChoices.COLOR_GREY,
|
|
3805
|
-
"content_types": [
|
|
3812
|
+
"content_types": [device_ct.pk],
|
|
3806
3813
|
}
|
|
3807
3814
|
|
|
3808
3815
|
cls.bulk_edit_data = {
|
|
3809
3816
|
"color": "000000",
|
|
3810
3817
|
"description": "I used to be a new role object.",
|
|
3811
3818
|
"weight": 255,
|
|
3819
|
+
"add_content_types": [ipaddress_ct.pk, prefix_ct.pk],
|
|
3820
|
+
"remove_content_types": [device_ct.pk],
|
|
3812
3821
|
}
|
|
3813
3822
|
|
|
3814
3823
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
nautobot/extras/urls.py
CHANGED
|
@@ -17,7 +17,6 @@ from nautobot.extras.models import (
|
|
|
17
17
|
Note,
|
|
18
18
|
Relationship,
|
|
19
19
|
SecretsGroup,
|
|
20
|
-
Status,
|
|
21
20
|
Tag,
|
|
22
21
|
Webhook,
|
|
23
22
|
)
|
|
@@ -36,6 +35,7 @@ router.register("roles", views.RoleUIViewSet)
|
|
|
36
35
|
router.register("saved-views", views.SavedViewUIViewSet)
|
|
37
36
|
router.register("secrets", views.SecretUIViewSet)
|
|
38
37
|
router.register("static-group-associations", views.StaticGroupAssociationUIViewSet)
|
|
38
|
+
router.register("statuses", views.StatusUIViewSet)
|
|
39
39
|
router.register("teams", views.TeamUIViewSet)
|
|
40
40
|
|
|
41
41
|
urlpatterns = [
|
|
@@ -589,35 +589,6 @@ urlpatterns = [
|
|
|
589
589
|
name="secretsgroup_notes",
|
|
590
590
|
kwargs={"model": SecretsGroup},
|
|
591
591
|
),
|
|
592
|
-
# Custom statuses
|
|
593
|
-
path("statuses/", views.StatusListView.as_view(), name="status_list"),
|
|
594
|
-
path("statuses/add/", views.StatusEditView.as_view(), name="status_add"),
|
|
595
|
-
path("statuses/edit/", views.StatusBulkEditView.as_view(), name="status_bulk_edit"),
|
|
596
|
-
path(
|
|
597
|
-
"statuses/delete/",
|
|
598
|
-
views.StatusBulkDeleteView.as_view(),
|
|
599
|
-
name="status_bulk_delete",
|
|
600
|
-
),
|
|
601
|
-
path("statuses/import/", views.StatusBulkImportView.as_view(), name="status_import"), # 3.0 TODO: remove, unused
|
|
602
|
-
path("statuses/<uuid:pk>/", views.StatusView.as_view(), name="status"),
|
|
603
|
-
path("statuses/<uuid:pk>/edit/", views.StatusEditView.as_view(), name="status_edit"),
|
|
604
|
-
path(
|
|
605
|
-
"statuses/<uuid:pk>/delete/",
|
|
606
|
-
views.StatusDeleteView.as_view(),
|
|
607
|
-
name="status_delete",
|
|
608
|
-
),
|
|
609
|
-
path(
|
|
610
|
-
"statuses/<uuid:pk>/changelog/",
|
|
611
|
-
views.ObjectChangeLogView.as_view(),
|
|
612
|
-
name="status_changelog",
|
|
613
|
-
kwargs={"model": Status},
|
|
614
|
-
),
|
|
615
|
-
path(
|
|
616
|
-
"statuses/<uuid:pk>/notes/",
|
|
617
|
-
views.ObjectNotesView.as_view(),
|
|
618
|
-
name="status_notes",
|
|
619
|
-
kwargs={"model": Status},
|
|
620
|
-
),
|
|
621
592
|
# Tags
|
|
622
593
|
path("tags/", views.TagListView.as_view(), name="tag_list"),
|
|
623
594
|
path("tags/add/", views.TagEditView.as_view(), name="tag_add"),
|
nautobot/extras/views.py
CHANGED
|
@@ -2929,62 +2929,18 @@ class DynamicGroupBulkAssignView(GetReturnURLMixin, ObjectPermissionRequiredMixi
|
|
|
2929
2929
|
#
|
|
2930
2930
|
|
|
2931
2931
|
|
|
2932
|
-
class
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
class StatusEditView(generic.ObjectEditView):
|
|
2942
|
-
"""Edit a single `Status` object."""
|
|
2943
|
-
|
|
2944
|
-
queryset = Status.objects.all()
|
|
2945
|
-
model_form = forms.StatusForm
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
class StatusBulkEditView(generic.BulkEditView):
|
|
2949
|
-
"""Edit multiple `Status` objects."""
|
|
2950
|
-
|
|
2951
|
-
queryset = Status.objects.all()
|
|
2952
|
-
table = tables.StatusTable
|
|
2953
|
-
form = forms.StatusBulkEditForm
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
class StatusBulkDeleteView(generic.BulkDeleteView):
|
|
2957
|
-
"""Delete multiple `Status` objects."""
|
|
2958
|
-
|
|
2959
|
-
queryset = Status.objects.all()
|
|
2960
|
-
table = tables.StatusTable
|
|
2961
|
-
filterset = filters.StatusFilterSet
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
class StatusDeleteView(generic.ObjectDeleteView):
|
|
2965
|
-
"""Delete a single `Status` object."""
|
|
2966
|
-
|
|
2967
|
-
queryset = Status.objects.all()
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
class StatusBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
|
|
2971
|
-
"""Bulk CSV import of multiple `Status` objects."""
|
|
2972
|
-
|
|
2973
|
-
queryset = Status.objects.all()
|
|
2974
|
-
table = tables.StatusTable
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
class StatusView(generic.ObjectView):
|
|
2978
|
-
"""Detail view for a single `Status` object."""
|
|
2979
|
-
|
|
2932
|
+
class StatusUIViewSet(NautobotUIViewSet):
|
|
2933
|
+
bulk_update_form_class = forms.StatusBulkEditForm
|
|
2934
|
+
filterset_class = filters.StatusFilterSet
|
|
2935
|
+
filterset_form_class = forms.StatusFilterForm
|
|
2936
|
+
form_class = forms.StatusForm
|
|
2937
|
+
serializer_class = serializers.StatusSerializer
|
|
2938
|
+
table_class = tables.StatusTable
|
|
2980
2939
|
queryset = Status.objects.all()
|
|
2981
2940
|
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
"content_types": instance.content_types.order_by("app_label", "model"),
|
|
2986
|
-
**super().get_extra_context(request, instance),
|
|
2987
|
-
}
|
|
2941
|
+
object_detail_content = object_detail.ObjectDetailContent(
|
|
2942
|
+
panels=(object_detail.ObjectFieldsPanel(weight=100, section=SectionChoices.LEFT_HALF, fields="__all__"),)
|
|
2943
|
+
)
|
|
2988
2944
|
|
|
2989
2945
|
|
|
2990
2946
|
#
|
nautobot/ipam/tables.py
CHANGED
|
@@ -355,7 +355,11 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
355
355
|
template_code=PREFIX_COPY_LINK, attrs={"td": {"class": "text-nowrap"}}, order_by=("network", "prefix_length")
|
|
356
356
|
)
|
|
357
357
|
vrf_count = LinkedCountColumn(
|
|
358
|
-
viewname="ipam:vrf_list",
|
|
358
|
+
viewname="ipam:vrf_list",
|
|
359
|
+
url_params={"prefix": "pk"},
|
|
360
|
+
display_field="name",
|
|
361
|
+
reverse_lookup="prefixes",
|
|
362
|
+
verbose_name="VRFs",
|
|
359
363
|
)
|
|
360
364
|
tenant = TenantColumn()
|
|
361
365
|
namespace = tables.Column(linkify=True)
|
|
@@ -364,7 +368,7 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
364
368
|
children = tables.Column(accessor="descendants_count", orderable=False)
|
|
365
369
|
date_allocated = tables.DateTimeColumn()
|
|
366
370
|
location_count = LinkedCountColumn(
|
|
367
|
-
viewname="dcim:location_list", url_params={"prefixes": "pk"}, verbose_name="Locations"
|
|
371
|
+
viewname="dcim:location_list", url_params={"prefixes": "pk"}, display_field="name", verbose_name="Locations"
|
|
368
372
|
)
|
|
369
373
|
cloud_networks_count = LinkedCountColumn(
|
|
370
374
|
viewname="cloud:cloudnetwork_list", url_params={"prefixes": "pk"}, verbose_name="Cloud Networks"
|
|
@@ -1,42 +1 @@
|
|
|
1
1
|
{% extends 'generic/object_retrieve.html' %}
|
|
2
|
-
{% load helpers %}
|
|
3
|
-
|
|
4
|
-
{% block content_left_page %}
|
|
5
|
-
<div class="panel panel-default">
|
|
6
|
-
<div class="panel-heading">
|
|
7
|
-
<strong>Namespace</strong>
|
|
8
|
-
</div>
|
|
9
|
-
<table class="table table-hover panel-body attr-table">
|
|
10
|
-
<tr>
|
|
11
|
-
<td>Name</td>
|
|
12
|
-
<td>{{ object.name }}</td>
|
|
13
|
-
</tr>
|
|
14
|
-
<tr>
|
|
15
|
-
<td>Description</td>
|
|
16
|
-
<td>{{ object.description }}</td>
|
|
17
|
-
</tr>
|
|
18
|
-
<tr>
|
|
19
|
-
<td>Location</td>
|
|
20
|
-
<td>{{ object.location | hyperlinked_object }}</td>
|
|
21
|
-
</tr>
|
|
22
|
-
</table>
|
|
23
|
-
</div>
|
|
24
|
-
{% endblock content_left_page %}
|
|
25
|
-
|
|
26
|
-
{% block extra_nav_tabs %}
|
|
27
|
-
{% if perms.ipam.view_vrf %}
|
|
28
|
-
<li role="presentation"{% if active_tab == 'vrfs' %} class="active"{% endif %}>
|
|
29
|
-
<a href="{% url 'ipam:namespace_vrfs' pk=object.pk %}">VRFs <span class="badge">{{ vrf_count }}</span></a>
|
|
30
|
-
</li>
|
|
31
|
-
{% endif %}
|
|
32
|
-
{% if perms.ipam.view_prefix %}
|
|
33
|
-
<li role="presentation"{% if active_tab == 'prefixes' %} class="active"{% endif %}>
|
|
34
|
-
<a href="{% url 'ipam:namespace_prefixes' pk=object.pk %}">Prefixes <span class="badge">{{ prefix_count }}</span></a>
|
|
35
|
-
</li>
|
|
36
|
-
{% endif %}
|
|
37
|
-
{% if perms.ipam.view_ipaddress %}
|
|
38
|
-
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}>
|
|
39
|
-
<a href="{% url 'ipam:namespace_ipaddresses' pk=object.pk %}">IP Addresses <span class="badge">{{ ip_address_count }}</span></a>
|
|
40
|
-
</li>
|
|
41
|
-
{% endif %}
|
|
42
|
-
{% endblock extra_nav_tabs %}
|
|
@@ -1,46 +1,2 @@
|
|
|
1
|
-
{% extends '
|
|
2
|
-
{%
|
|
3
|
-
|
|
4
|
-
{% block extra_breadcrumbs %}
|
|
5
|
-
<li>{{ object.parent|hyperlinked_object }}</li>
|
|
6
|
-
{% endblock extra_breadcrumbs %}
|
|
7
|
-
|
|
8
|
-
{% block content_left_page %}
|
|
9
|
-
<div class="panel panel-default">
|
|
10
|
-
<div class="panel-heading">
|
|
11
|
-
<strong>Service</strong>
|
|
12
|
-
</div>
|
|
13
|
-
<table class="table table-hover panel-body attr-table">
|
|
14
|
-
<tr>
|
|
15
|
-
<td>Name</td>
|
|
16
|
-
<td>{{ object.name }}</td>
|
|
17
|
-
</tr>
|
|
18
|
-
<tr>
|
|
19
|
-
<td>Parent</td>
|
|
20
|
-
<td>{{ object.parent|hyperlinked_object }}</td>
|
|
21
|
-
</tr>
|
|
22
|
-
<tr>
|
|
23
|
-
<td>Protocol</td>
|
|
24
|
-
<td>{{ object.get_protocol_display }}</td>
|
|
25
|
-
</tr>
|
|
26
|
-
<tr>
|
|
27
|
-
<td>Ports</td>
|
|
28
|
-
<td>{{ object.port_list }}</td>
|
|
29
|
-
</tr>
|
|
30
|
-
<tr>
|
|
31
|
-
<td>IP Addresses</td>
|
|
32
|
-
<td>
|
|
33
|
-
{% for ipaddress in object.ip_addresses.all %}
|
|
34
|
-
{{ ipaddress|hyperlinked_object }}<br>
|
|
35
|
-
{% empty %}
|
|
36
|
-
<span class="text-muted">None</span>
|
|
37
|
-
{% endfor %}
|
|
38
|
-
</td>
|
|
39
|
-
</tr>
|
|
40
|
-
<tr>
|
|
41
|
-
<td>Description</td>
|
|
42
|
-
<td>{{ object.description|placeholder }}</td>
|
|
43
|
-
</tr>
|
|
44
|
-
</table>
|
|
45
|
-
</div>
|
|
46
|
-
{% endblock content_left_page %}
|
|
1
|
+
{% extends 'ipam/service_retrieve.html' %}
|
|
2
|
+
{% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
|
|
@@ -1,18 +1,2 @@
|
|
|
1
1
|
{% extends 'generic/object_create.html' %}
|
|
2
|
-
{%
|
|
3
|
-
|
|
4
|
-
{% block form %}
|
|
5
|
-
<div class="panel panel-default">
|
|
6
|
-
<div class="panel-heading"><strong>Service</strong></div>
|
|
7
|
-
<div class="panel-body">
|
|
8
|
-
{% render_field form.device %}
|
|
9
|
-
{% render_field form.virtual_machine %}
|
|
10
|
-
{% render_field form.name %}
|
|
11
|
-
{% render_field form.protocol %}
|
|
12
|
-
{% render_field form.ports %}
|
|
13
|
-
{% render_field form.ip_addresses %}
|
|
14
|
-
{% render_field form.description %}
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
{% include 'inc/extras_features_edit_form_fields.html' %}
|
|
18
|
-
{% endblock %}
|
|
2
|
+
{% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
|
|
File without changes
|