From 14b5ab984b0b54e29597ed1cd9b6e8253e65075e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 11 May 2017 13:11:11 -0400 Subject: [PATCH] new view to allow associations but no creations --- awx/api/generics.py | 18 ++++++++- awx/api/views.py | 50 ++++++------------------ awx/main/tests/unit/api/test_generics.py | 16 +++++++- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/awx/api/generics.py b/awx/api/generics.py index 016045d31f..4045e5780a 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -41,7 +41,8 @@ __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'SubDetailAPIView', 'ResourceAccessList', 'ParentMixin', - 'DeleteLastUnattachLabelMixin',] + 'DeleteLastUnattachLabelMixin', + 'SubListAttachDetachAPIView',] logger = logging.getLogger('awx.api.generics') analytics_logger = logging.getLogger('awx.analytics.performance') @@ -553,6 +554,21 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView): return self.attach(request, *args, **kwargs) + +class SubListAttachDetachAPIView(SubListCreateAttachDetachAPIView): + ''' + Derived version of SubListCreateAttachDetachAPIView that prohibits creation + ''' + def post(self, request, *args, **kwargs): + sub_id = request.data.get('id', None) + if not sub_id: + return Response( + dict(msg=_("{} 'id' field is missing.".format( + self.model._meta.verbose_name.title()))), + status=status.HTTP_400_BAD_REQUEST) + return super(SubListAttachDetachAPIView, self).post(request, *args, **kwargs) + + class DeleteLastUnattachLabelMixin(object): ''' Models for which you want the last instance to be deleted from the database diff --git a/awx/api/views.py b/awx/api/views.py index c966f4a765..b16a43427c 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -993,7 +993,7 @@ class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIV new_in_300 = True -class OrganizationInstanceGroupsList(SubListCreateAttachDetachAPIView): +class OrganizationInstanceGroupsList(SubListAttachDetachAPIView): model = InstanceGroup serializer_class = InstanceGroupSerializer @@ -1001,13 +1001,6 @@ class OrganizationInstanceGroupsList(SubListCreateAttachDetachAPIView): relationship = 'instance_groups' new_in_320 = True - def post(self, request, *args, **kwargs): - sub_id = request.data.get('id', None) - if not sub_id: - return Response(dict(msg=_("Instance Group 'id' field is missing.")), - status=status.HTTP_400_BAD_REQUEST) - return super(OrganizationInstanceGroupsList, self).post(request, *args, **kwargs) - class OrganizationAccessList(ResourceAccessList): @@ -1054,7 +1047,7 @@ class TeamUsersList(BaseUsersList): relationship = 'member_role.members' -class TeamRolesList(SubListCreateAttachDetachAPIView): +class TeamRolesList(SubListAttachDetachAPIView): model = Role serializer_class = RoleSerializerWithParentAccess @@ -1070,11 +1063,9 @@ class TeamRolesList(SubListCreateAttachDetachAPIView): return Role.filter_visible_roles(self.request.user, team.member_role.children.all().exclude(pk=team.read_role.pk)) def post(self, request, *args, **kwargs): - # Forbid implicit role creation here sub_id = request.data.get('id', None) if not sub_id: - data = dict(msg=_("Role 'id' field is missing.")) - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return super(TeamRolesList, self).post(request) role = get_object_or_400(Role, pk=sub_id) org_content_type = ContentType.objects.get_for_model(Organization) @@ -1437,7 +1428,7 @@ class UserTeamsList(ListAPIView): Q(member_role__members=u) | Q(admin_role__members=u)).distinct() -class UserRolesList(SubListCreateAttachDetachAPIView): +class UserRolesList(SubListAttachDetachAPIView): model = Role serializer_class = RoleSerializerWithParentAccess @@ -1457,11 +1448,9 @@ class UserRolesList(SubListCreateAttachDetachAPIView): .exclude(content_type=content_type, object_id=u.id) def post(self, request, *args, **kwargs): - # Forbid implicit role creation here sub_id = request.data.get('id', None) if not sub_id: - data = dict(msg=_("Role 'id' field is missing.")) - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return super(UserRolesList, self).post(request) if sub_id == self.request.user.admin_role.pk: raise PermissionDenied(_('You may not perform any action with your own admin_role.')) @@ -1852,20 +1841,13 @@ class InventoryActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView return qs.filter(Q(inventory=parent) | Q(host__in=parent.hosts.all()) | Q(group__in=parent.groups.all())) -class InventoryInstanceGroupsList(SubListCreateAttachDetachAPIView): +class InventoryInstanceGroupsList(SubListAttachDetachAPIView): model = InstanceGroup serializer_class = InstanceGroupSerializer parent_model = Inventory relationship = 'instance_groups' new_in_320 = True - - def post(self, request, *args, **kwargs): - sub_id = request.data.get('id', None) - if not sub_id: - return Response(dict(msg=_("Instance Group 'id' field is missing.")), - status=status.HTTP_400_BAD_REQUEST) - return super(InventoryInstanceGroupsList, self).post(request, *args, **kwargs) class InventoryAccessList(ResourceAccessList): @@ -3009,7 +2991,7 @@ class JobTemplateJobsList(SubListCreateAPIView): parent_key = 'job_template' -class JobTemplateInstanceGroupsList(SubListCreateAttachDetachAPIView): +class JobTemplateInstanceGroupsList(SubListAttachDetachAPIView): model = InstanceGroup serializer_class = InstanceGroupSerializer @@ -3017,13 +2999,6 @@ class JobTemplateInstanceGroupsList(SubListCreateAttachDetachAPIView): relationship = 'instance_groups' new_in_320 = True - def post(self, request, *args, **kwargs): - sub_id = request.data.get('id', None) - if not sub_id: - return Response(dict(msg=_("Instance Group 'id' field is missing.")), - status=status.HTTP_400_BAD_REQUEST) - return super(JobTemplateInstanceGroupsList, self).post(request, *args, **kwargs) - class JobTemplateAccessList(ResourceAccessList): @@ -4350,7 +4325,7 @@ class RoleDetail(RetrieveAPIView): new_in_300 = True -class RoleUsersList(SubListCreateAttachDetachAPIView): +class RoleUsersList(SubListAttachDetachAPIView): model = User serializer_class = UserSerializer @@ -4367,8 +4342,7 @@ class RoleUsersList(SubListCreateAttachDetachAPIView): # Forbid implicit user creation here sub_id = request.data.get('id', None) if not sub_id: - data = dict(msg=_("User 'id' field is missing.")) - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return super(RoleUsersList, self).post(request) user = get_object_or_400(User, pk=sub_id) role = self.get_parent_object() @@ -4392,7 +4366,7 @@ class RoleUsersList(SubListCreateAttachDetachAPIView): return super(RoleUsersList, self).post(request, *args, **kwargs) -class RoleTeamsList(SubListAPIView): +class RoleTeamsList(SubListAttachDetachAPIView): model = Team serializer_class = TeamSerializer @@ -4407,11 +4381,9 @@ class RoleTeamsList(SubListAPIView): return Team.objects.filter(member_role__children=role) def post(self, request, pk, *args, **kwargs): - # Forbid implicit team creation here sub_id = request.data.get('id', None) if not sub_id: - data = dict(msg=_("Team 'id' field is missing.")) - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return super(RoleTeamsList, self).post(request) team = get_object_or_400(Team, pk=sub_id) role = Role.objects.get(pk=self.kwargs['pk']) diff --git a/awx/main/tests/unit/api/test_generics.py b/awx/main/tests/unit/api/test_generics.py index 579440b201..9f3746c043 100644 --- a/awx/main/tests/unit/api/test_generics.py +++ b/awx/main/tests/unit/api/test_generics.py @@ -11,7 +11,7 @@ from rest_framework.exceptions import PermissionDenied # AWX from awx.api.generics import ( ParentMixin, - SubListCreateAttachDetachAPIView, + SubListCreateAttachDetachAPIView, SubListAttachDetachAPIView, DeleteLastUnattachLabelMixin, ResourceAccessList ) @@ -140,6 +140,20 @@ class TestSubListCreateAttachDetachAPIView: view.unattach_by_id.assert_not_called() +def test_attach_detatch_only(mocker): + mock_request = mocker.MagicMock() + mock_request.data = {'name': 'name for my new model'} + view = SubListAttachDetachAPIView() + view.model = mocker.MagicMock() + view.model._meta = mocker.MagicMock() + view.model._meta.verbose_name = "Foo Bar" + + resp = view.post(mock_request) + + assert 'Foo Bar' in resp.data['msg'] + assert 'field is missing' in resp.data['msg'] + + class TestDeleteLastUnattachLabelMixin: @mock.patch('__builtin__.super') def test_unattach_ok(self, super, mocker):