nautobot 1.6.22__py3-none-any.whl → 1.6.24__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/extras/api/views.py +2 -2
- nautobot/extras/tests/test_api.py +23 -1
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_views.py +48 -4
- nautobot/extras/views.py +4 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2824 -2804
- nautobot/project-static/docs/release-notes/version-1.6.html +193 -66
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +187 -187
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/utilities/testing/views.py +6 -1
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/METADATA +1 -1
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/RECORD +17 -17
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/LICENSE.txt +0 -0
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/NOTICE +0 -0
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/WHEEL +0 -0
- {nautobot-1.6.22.dist-info → nautobot-1.6.24.dist-info}/entry_points.txt +0 -0
nautobot/extras/api/views.py
CHANGED
|
@@ -330,13 +330,13 @@ class DynamicGroupViewSet(ModelViewSet, NotesViewSetMixin):
|
|
|
330
330
|
# @extend_schema(methods=["get"], responses={200: member_response})
|
|
331
331
|
@action(detail=True, methods=["get"])
|
|
332
332
|
def members(self, request, pk, *args, **kwargs):
|
|
333
|
-
"""List member objects of
|
|
333
|
+
"""List the member objects of this dynamic group."""
|
|
334
334
|
instance = get_object_or_404(self.queryset, pk=pk)
|
|
335
335
|
|
|
336
336
|
# Retrieve the serializer for the content_type and paginate the results
|
|
337
337
|
member_model_class = instance.content_type.model_class()
|
|
338
338
|
member_serializer_class = get_serializer_for_model(member_model_class)
|
|
339
|
-
members = self.paginate_queryset(instance.members)
|
|
339
|
+
members = self.paginate_queryset(instance.members.restrict(request.user, "view"))
|
|
340
340
|
member_serializer = member_serializer_class(members, many=True, context={"request": request})
|
|
341
341
|
return self.get_paginated_response(member_serializer.data)
|
|
342
342
|
|
|
@@ -69,6 +69,7 @@ from nautobot.ipam.factory import VLANFactory
|
|
|
69
69
|
from nautobot.ipam.models import VLAN, VLANGroup
|
|
70
70
|
from nautobot.users.models import ObjectPermission
|
|
71
71
|
from nautobot.utilities.choices import ColorChoices
|
|
72
|
+
from nautobot.utilities.permissions import get_permission_for_model
|
|
72
73
|
from nautobot.utilities.testing import APITestCase, APIViewTestCases
|
|
73
74
|
from nautobot.utilities.testing.utils import disable_warnings
|
|
74
75
|
from nautobot.utilities.utils import get_route_for_model, slugify_dashes_to_underscores
|
|
@@ -752,13 +753,34 @@ class DynamicGroupTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
|
|
|
752
753
|
def test_get_members(self):
|
|
753
754
|
"""Test that the `/members/` API endpoint returns what is expected."""
|
|
754
755
|
self.add_permissions("extras.view_dynamicgroup")
|
|
755
|
-
instance =
|
|
756
|
+
instance = self.groups[0]
|
|
757
|
+
self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
|
|
756
758
|
member_count = instance.members.count()
|
|
757
759
|
url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
|
|
758
760
|
response = self.client.get(url, **self.header)
|
|
759
761
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
760
762
|
self.assertEqual(member_count, len(response.json()["results"]))
|
|
761
763
|
|
|
764
|
+
def test_get_members_with_constrained_permission(self):
|
|
765
|
+
"""Test that the `/members/` API endpoint enforces permissions on the member model."""
|
|
766
|
+
self.add_permissions("extras.view_dynamicgroup")
|
|
767
|
+
instance = self.groups[0]
|
|
768
|
+
obj1 = instance.members.first()
|
|
769
|
+
obj_perm = ObjectPermission(
|
|
770
|
+
name="Test permission",
|
|
771
|
+
constraints={"pk__in": [obj1.pk]},
|
|
772
|
+
actions=["view"],
|
|
773
|
+
)
|
|
774
|
+
obj_perm.save()
|
|
775
|
+
obj_perm.users.add(self.user)
|
|
776
|
+
obj_perm.object_types.add(instance.content_type)
|
|
777
|
+
|
|
778
|
+
url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
|
|
779
|
+
response = self.client.get(url, **self.header)
|
|
780
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
781
|
+
self.assertEqual(len(response.json()["results"]), 1)
|
|
782
|
+
self.assertEqual(response.json()["results"][0]["id"], str(obj1.pk))
|
|
783
|
+
|
|
762
784
|
|
|
763
785
|
class DynamicGroupMembershipTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
|
|
764
786
|
model = DynamicGroupMembership
|
|
@@ -1002,7 +1002,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase):
|
|
|
1002
1002
|
group.members_cached
|
|
1003
1003
|
self.assertEqual(mock_get_queryset.call_count, 1)
|
|
1004
1004
|
|
|
1005
|
-
time.sleep(
|
|
1005
|
+
time.sleep(3) # Let the cache expire
|
|
1006
1006
|
|
|
1007
1007
|
group.members_cached
|
|
1008
1008
|
self.assertEqual(mock_get_queryset.call_count, 2)
|
|
@@ -66,6 +66,7 @@ from nautobot.extras.utils import get_job_content_type, TaggableClassesQuery
|
|
|
66
66
|
from nautobot.ipam.factory import VLANFactory
|
|
67
67
|
from nautobot.ipam.models import VLAN, VLANGroup
|
|
68
68
|
from nautobot.users.models import ObjectPermission
|
|
69
|
+
from nautobot.utilities.permissions import get_permission_for_model
|
|
69
70
|
from nautobot.utilities.testing import ViewTestCases, TestCase, extract_page_body, extract_form_failures
|
|
70
71
|
from nautobot.utilities.testing.utils import disable_warnings, post_data
|
|
71
72
|
from nautobot.utilities.utils import slugify_dashes_to_underscores
|
|
@@ -614,9 +615,11 @@ class DynamicGroupTestCase(
|
|
|
614
615
|
content_type = ContentType.objects.get_for_model(Device)
|
|
615
616
|
|
|
616
617
|
# DynamicGroup objects to test.
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
618
|
+
cls.dynamic_groups = [
|
|
619
|
+
DynamicGroup.objects.create(name="DG 1", slug="dg-1", content_type=content_type),
|
|
620
|
+
DynamicGroup.objects.create(name="DG 2", slug="dg-2", content_type=content_type),
|
|
621
|
+
DynamicGroup.objects.create(name="DG 3", slug="dg-3", content_type=content_type),
|
|
622
|
+
]
|
|
620
623
|
|
|
621
624
|
manufacturer = Manufacturer.objects.create(name="Manufacturer 1", slug="manufacturer-1")
|
|
622
625
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1", slug="device-type-1")
|
|
@@ -637,6 +640,38 @@ class DynamicGroupTestCase(
|
|
|
637
640
|
"dynamic_group_memberships-MAX_NUM_FORMS": "1000",
|
|
638
641
|
}
|
|
639
642
|
|
|
643
|
+
def test_get_object_with_permission(self):
|
|
644
|
+
instance = self._get_queryset().first()
|
|
645
|
+
# Add view permissions for the group's members:
|
|
646
|
+
self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
|
|
647
|
+
|
|
648
|
+
response = super().test_get_object_with_permission()
|
|
649
|
+
|
|
650
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
651
|
+
# Check that the "members" table in the detail view includes all appropriate member objects
|
|
652
|
+
for member in instance.members:
|
|
653
|
+
self.assertIn(str(member.pk), response_body)
|
|
654
|
+
|
|
655
|
+
def test_get_object_with_constrained_permission(self):
|
|
656
|
+
instance = self._get_queryset().first()
|
|
657
|
+
# Add view permission for one of the group's members but not the others:
|
|
658
|
+
member1, member2 = instance.members[:2]
|
|
659
|
+
obj_perm = ObjectPermission(
|
|
660
|
+
name="Members permission",
|
|
661
|
+
constraints={"pk": member1.pk},
|
|
662
|
+
actions=["view"],
|
|
663
|
+
)
|
|
664
|
+
obj_perm.save()
|
|
665
|
+
obj_perm.users.add(self.user)
|
|
666
|
+
obj_perm.object_types.add(instance.content_type)
|
|
667
|
+
|
|
668
|
+
response = super().test_get_object_with_constrained_permission()
|
|
669
|
+
|
|
670
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
671
|
+
# Check that the "members" table in the detail view includes all permitted member objects
|
|
672
|
+
self.assertIn(str(member1.pk), response_body)
|
|
673
|
+
self.assertNotIn(str(member2.pk), response_body)
|
|
674
|
+
|
|
640
675
|
def test_get_object_dynamic_groups_anonymous(self):
|
|
641
676
|
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
642
677
|
self.client.logout()
|
|
@@ -660,7 +695,6 @@ class DynamicGroupTestCase(
|
|
|
660
695
|
self.assertIn("DG 3", response_body, msg=response_body)
|
|
661
696
|
|
|
662
697
|
def test_get_object_dynamic_groups_with_constrained_permission(self):
|
|
663
|
-
self.add_permissions("extras.view_dynamicgroup")
|
|
664
698
|
obj_perm = ObjectPermission(
|
|
665
699
|
name="View a device",
|
|
666
700
|
constraints={"pk": Device.objects.first().pk},
|
|
@@ -669,12 +703,22 @@ class DynamicGroupTestCase(
|
|
|
669
703
|
obj_perm.save()
|
|
670
704
|
obj_perm.users.add(self.user)
|
|
671
705
|
obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
|
|
706
|
+
obj_perm_2 = ObjectPermission(
|
|
707
|
+
name="View a Dynamic Group",
|
|
708
|
+
constraints={"pk": self.dynamic_groups[0].pk},
|
|
709
|
+
actions=["view"],
|
|
710
|
+
)
|
|
711
|
+
obj_perm_2.save()
|
|
712
|
+
obj_perm_2.users.add(self.user)
|
|
713
|
+
obj_perm_2.object_types.add(ContentType.objects.get_for_model(DynamicGroup))
|
|
672
714
|
|
|
673
715
|
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
674
716
|
response = self.client.get(url)
|
|
675
717
|
self.assertHttpStatus(response, 200)
|
|
676
718
|
response_body = response.content.decode(response.charset)
|
|
677
719
|
self.assertIn("DG 1", response_body, msg=response_body)
|
|
720
|
+
self.assertNotIn("DG 2", response_body, msg=response_body)
|
|
721
|
+
self.assertNotIn("DG 3", response_body, msg=response_body)
|
|
678
722
|
|
|
679
723
|
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.last().pk})
|
|
680
724
|
response = self.client.get(url)
|
nautobot/extras/views.py
CHANGED
|
@@ -550,7 +550,7 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
550
550
|
|
|
551
551
|
if table_class is not None:
|
|
552
552
|
# Members table (for display on Members nav tab)
|
|
553
|
-
members_table = table_class(instance.members, orderable=False)
|
|
553
|
+
members_table = table_class(instance.members.restrict(request.user, "view"), orderable=False)
|
|
554
554
|
paginate = {
|
|
555
555
|
"paginator_class": EnhancedPaginator,
|
|
556
556
|
"per_page": get_paginate_count(request),
|
|
@@ -722,7 +722,9 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
722
722
|
obj = get_object_or_404(model, **kwargs)
|
|
723
723
|
|
|
724
724
|
# Gather all dynamic groups for this object (and its related objects)
|
|
725
|
-
dynamicsgroups_table = tables.DynamicGroupTable(
|
|
725
|
+
dynamicsgroups_table = tables.DynamicGroupTable(
|
|
726
|
+
data=obj.dynamic_groups_cached.restrict(request.user, "view"), orderable=False
|
|
727
|
+
)
|
|
726
728
|
|
|
727
729
|
# Apply the request context
|
|
728
730
|
paginate = {
|