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.
Files changed (115) hide show
  1. nautobot/core/api/mixins.py +10 -0
  2. nautobot/core/celery/encoders.py +2 -2
  3. nautobot/core/forms/fields.py +21 -5
  4. nautobot/core/forms/utils.py +1 -0
  5. nautobot/core/jobs/bulk_actions.py +1 -1
  6. nautobot/core/management/commands/generate_test_data.py +1 -1
  7. nautobot/core/models/name_color_content_types.py +9 -0
  8. nautobot/core/models/validators.py +7 -0
  9. nautobot/core/settings.py +0 -14
  10. nautobot/core/settings.yaml +0 -28
  11. nautobot/core/tables.py +6 -1
  12. nautobot/core/templates/generic/object_retrieve.html +1 -1
  13. nautobot/core/testing/api.py +18 -0
  14. nautobot/core/tests/nautobot_config.py +0 -2
  15. nautobot/core/tests/runner.py +17 -140
  16. nautobot/core/tests/test_api.py +4 -4
  17. nautobot/core/tests/test_authentication.py +83 -4
  18. nautobot/core/tests/test_forms.py +11 -8
  19. nautobot/core/tests/test_graphql.py +9 -0
  20. nautobot/core/tests/test_jobs.py +7 -0
  21. nautobot/core/ui/object_detail.py +31 -0
  22. nautobot/dcim/factory.py +2 -0
  23. nautobot/dcim/filters/__init__.py +5 -0
  24. nautobot/dcim/forms.py +17 -1
  25. nautobot/dcim/migrations/0068_alter_softwareimagefile_download_url.py +19 -0
  26. nautobot/dcim/migrations/0069_softwareimagefile_external_integration.py +25 -0
  27. nautobot/dcim/models/devices.py +9 -2
  28. nautobot/dcim/tables/devices.py +1 -0
  29. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +4 -0
  30. nautobot/dcim/tests/test_api.py +74 -31
  31. nautobot/dcim/tests/test_filters.py +2 -0
  32. nautobot/dcim/tests/test_models.py +65 -0
  33. nautobot/dcim/tests/test_views.py +3 -0
  34. nautobot/extras/forms/forms.py +7 -3
  35. nautobot/extras/plugins/marketplace_manifest.yml +18 -0
  36. nautobot/extras/tables.py +4 -5
  37. nautobot/extras/templates/extras/inc/panel_changelog.html +1 -1
  38. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  39. nautobot/extras/templates/extras/status.html +1 -37
  40. nautobot/extras/tests/integration/test_notes.py +1 -1
  41. nautobot/extras/tests/test_api.py +22 -7
  42. nautobot/extras/tests/test_changelog.py +4 -4
  43. nautobot/extras/tests/test_customfields.py +3 -0
  44. nautobot/extras/tests/test_plugins.py +19 -13
  45. nautobot/extras/tests/test_relationships.py +9 -0
  46. nautobot/extras/tests/test_tags.py +2 -2
  47. nautobot/extras/tests/test_views.py +15 -6
  48. nautobot/extras/urls.py +1 -30
  49. nautobot/extras/views.py +10 -54
  50. nautobot/ipam/tables.py +6 -2
  51. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -41
  52. nautobot/ipam/templates/ipam/service.html +2 -46
  53. nautobot/ipam/templates/ipam/service_edit.html +1 -17
  54. nautobot/ipam/templates/ipam/service_retrieve.html +7 -0
  55. nautobot/ipam/tests/migration/__init__.py +0 -0
  56. nautobot/ipam/tests/migration/test_migrations.py +510 -0
  57. nautobot/ipam/tests/test_api.py +66 -36
  58. nautobot/ipam/tests/test_filters.py +0 -10
  59. nautobot/ipam/tests/test_views.py +44 -2
  60. nautobot/ipam/urls.py +2 -47
  61. nautobot/ipam/utils/migrations.py +185 -152
  62. nautobot/ipam/utils/testing.py +177 -0
  63. nautobot/ipam/views.py +95 -157
  64. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +47 -0
  65. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -0
  66. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +63 -0
  67. nautobot/project-static/docs/development/apps/api/testing.html +0 -87
  68. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
  69. nautobot/project-static/docs/development/core/best-practices.html +3 -3
  70. nautobot/project-static/docs/development/core/getting-started.html +78 -107
  71. nautobot/project-static/docs/development/core/release-checklist.html +1 -1
  72. nautobot/project-static/docs/development/core/style-guide.html +1 -1
  73. nautobot/project-static/docs/development/core/testing.html +24 -198
  74. nautobot/project-static/docs/media/user-guide/administration/getting-started/nautobot-cloud.png +0 -0
  75. nautobot/project-static/docs/objects.inv +0 -0
  76. nautobot/project-static/docs/overview/application_stack.html +1 -1
  77. nautobot/project-static/docs/release-notes/version-2.4.html +226 -1
  78. nautobot/project-static/docs/search/search_index.json +1 -1
  79. nautobot/project-static/docs/sitemap.xml +290 -290
  80. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  81. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +2 -48
  82. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +71 -0
  83. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -1
  84. nautobot/project-static/docs/user-guide/administration/installation/index.html +257 -16
  85. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
  86. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
  87. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -0
  88. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -11
  89. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +8 -8
  90. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -0
  91. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +40 -25
  92. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -4
  93. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
  94. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +77 -5
  95. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
  96. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -1
  97. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
  98. nautobot/project-static/docs/user-guide/index.html +89 -2
  99. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +207 -122
  100. nautobot/virtualization/forms.py +20 -0
  101. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -39
  102. nautobot/virtualization/templates/virtualization/clustertype.html +1 -0
  103. nautobot/virtualization/tests/test_api.py +14 -3
  104. nautobot/virtualization/tests/test_views.py +10 -2
  105. nautobot/virtualization/urls.py +10 -93
  106. nautobot/virtualization/views.py +33 -72
  107. {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/METADATA +6 -5
  108. {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/RECORD +113 -108
  109. {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/WHEEL +1 -1
  110. nautobot/core/tests/performance_baselines.yml +0 -8900
  111. nautobot/ipam/tests/test_migrations.py +0 -462
  112. /nautobot/ipam/templates/ipam/{namespace_ipaddresses.html → namespace_ip_addresses.html} +0 -0
  113. {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/LICENSE.txt +0 -0
  114. {nautobot-2.4.5.dist-info → nautobot-2.4.6.dist-info}/NOTICE +0 -0
  115. {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("dcim.view_location", "dcim.add_location", "extras.add_relationshipassociation")
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("extras.add_relationshipassociation")
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("extras.add_relationshipassociation")
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()
@@ -472,6 +472,9 @@ class CustomFieldDataAPITest(APITestCase):
472
472
  "dcim.add_location",
473
473
  "dcim.change_location",
474
474
  "dcim.view_location",
475
+ "dcim.view_locationtype",
476
+ "extras.view_status",
477
+ "extras.view_customfield",
475
478
  )
476
479
 
477
480
  def setUp(self):
@@ -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["user"])
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"] = [TestUserContextCustomValidator]
530
+ before = registry["plugin_custom_validators"]["dcim.locationtype"]
531
+ try:
532
+ registry["plugin_custom_validators"]["dcim.locationtype"] = [TestUserContextCustomValidator]
531
533
 
532
- from django.contrib.auth.models import AnonymousUser
533
-
534
- with self.assertRaises(ValidationError) as context:
535
- location_type.clean()
536
- self.assertEqual(context.exception.message, AnonymousUser())
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"] = [TestUserContextCustomValidator]
541
-
542
- with self.assertRaises(ValidationError) as context:
543
- with web_request_context(user=self.user):
544
- location_type.clean()
545
- self.assertEqual(context.exception.message, self.user)
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
- content_type = ContentType.objects.get_for_model(Device)
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": [content_type.pk],
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
- # Status objects to test.
3799
- content_type = ContentType.objects.get_for_model(Device)
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": [content_type.pk],
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 StatusListView(generic.ObjectListView):
2933
- """List `Status` objects."""
2934
-
2935
- queryset = Status.objects.all()
2936
- filterset = filters.StatusFilterSet
2937
- filterset_form = forms.StatusFilterForm
2938
- table = tables.StatusTable
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
- def get_extra_context(self, request, instance):
2983
- """Return ordered content types."""
2984
- return {
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", url_params={"prefix": "pk"}, reverse_lookup="prefixes", verbose_name="VRFs"
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 'generic/object_retrieve.html' %}
2
- {% load helpers %}
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
- {% load form_helpers %}
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 %}
@@ -0,0 +1,7 @@
1
+ {% extends 'generic/object_retrieve.html' %}
2
+ {% load helpers %}
3
+
4
+ {% block extra_breadcrumbs %}
5
+ <li>{{ object.parent|hyperlinked_object }}</li>
6
+ {% endblock extra_breadcrumbs %}
7
+
File without changes