diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 8d502eb5c9..c5e146b0a3 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -599,7 +599,7 @@ class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetac search_fields = ('hostname',) -class ScheduleList(ListAPIView): +class ScheduleList(ListCreateAPIView): view_name = _("Schedules") model = Schedule @@ -1291,7 +1291,7 @@ class ProjectUpdateNotificationsList(SubListAPIView): search_fields = ('subject', 'notification_type', 'body',) -class ProjectUpdateScmInventoryUpdates(SubListCreateAPIView): +class ProjectUpdateScmInventoryUpdates(SubListAPIView): view_name = _("Project Update SCM Inventory Updates") model = InventoryUpdate @@ -1484,10 +1484,11 @@ class OAuth2TokenActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIVi search_fields = ('changes',) -class UserTeamsList(ListAPIView): +class UserTeamsList(SubListAPIView): - model = User + model = Team serializer_class = TeamSerializer + parent_model = User def get_queryset(self): u = get_object_or_404(User, pk=self.kwargs['pk']) @@ -1665,7 +1666,7 @@ class CredentialTypeDetail(RetrieveUpdateDestroyAPIView): return super(CredentialTypeDetail, self).destroy(request, *args, **kwargs) -class CredentialTypeCredentialList(SubListAPIView): +class CredentialTypeCredentialList(SubListCreateAPIView): model = Credential parent_model = CredentialType diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py index 060d979f76..1cab239698 100644 --- a/awx/main/tests/unit/test_views.py +++ b/awx/main/tests/unit/test_views.py @@ -8,6 +8,7 @@ from rest_framework.generics import ListAPIView # AWX from awx.main.views import ApiErrorView from awx.api.views import JobList, InventorySourceList +from awx.api.generics import ListCreateAPIView, SubListAttachDetachAPIView HTTP_METHOD_NAMES = [ @@ -73,3 +74,30 @@ def test_views_have_search_fields(all_views): for v in views_missing_search ])) ) + + +def test_global_creation_always_possible(all_views): + """To not make life very difficult for clients, this test + asserts that all creatable resources can be created by + POSTing to the global resource list + """ + views_by_model = {} + for View in all_views: + if not getattr(View, 'deprecated', False) and issubclass(View, ListAPIView) and hasattr(View, 'model'): + views_by_model.setdefault(View.model, []).append(View) + for model, views in views_by_model.items(): + creatable = False + global_view = None + creatable_view = None + for View in views: + if '{}ListView'.format(model.__name__) == View.__name__: + global_view = View + if issubclass(View, ListCreateAPIView) and not issubclass(View, SubListAttachDetachAPIView): + creatable = True + creatable_view = View + if not creatable or not global_view: + continue + assert 'POST' in global_view().allowed_methods, ( + 'Resource {} should be creatable in global list view {}. ' + 'Can be created now in {}'.format(model, global_view, creatable_view) + )