From ec7f1c25407dfbe4d3340458a8b2fac046f4ed9e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 14 May 2018 10:12:49 -0400 Subject: [PATCH] enforce consistently setting view search_fields --- awx/api/views.py | 56 +++++++++++++++++++++++++++++++ awx/main/tests/unit/test_views.py | 45 +++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/awx/api/views.py b/awx/api/views.py index ff9b5d234a..16320d9d57 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -610,6 +610,7 @@ class InstanceList(ListAPIView): view_name = _("Instances") model = Instance serializer_class = InstanceSerializer + search_fields = ('hostname',) class InstanceDetail(RetrieveUpdateAPIView): @@ -696,6 +697,7 @@ class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetac serializer_class = InstanceSerializer parent_model = InstanceGroup relationship = "instances" + search_fields = ('hostname',) class ScheduleList(ListAPIView): @@ -1072,6 +1074,7 @@ class OrganizationActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIV serializer_class = ActivityStreamSerializer parent_model = Organization relationship = 'activitystream_set' + search_fields = ('changes',) class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView): @@ -1126,6 +1129,7 @@ class OrganizationObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Organization + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -1165,6 +1169,7 @@ class TeamRolesList(SubListAttachDetachAPIView): metadata_class = RoleMetadata parent_model = Team relationship='member_role.children' + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): team = get_object_or_404(Team, pk=self.kwargs['pk']) @@ -1202,6 +1207,7 @@ class TeamObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Team + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -1233,6 +1239,7 @@ class TeamActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = Team relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -1328,6 +1335,7 @@ class ProjectActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = Project relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -1415,6 +1423,7 @@ class ProjectUpdateEventsList(SubListAPIView): parent_model = ProjectUpdate relationship = 'project_update_events' view_name = _('Project Update Events List') + search_fields = ('stdout',) def finalize_response(self, request, response, *args, **kwargs): response['X-UI-Max-Events'] = settings.MAX_UI_JOB_EVENTS @@ -1428,6 +1437,7 @@ class SystemJobEventsList(SubListAPIView): parent_model = SystemJob relationship = 'system_job_events' view_name = _('System Job Events List') + search_fields = ('stdout',) def finalize_response(self, request, response, *args, **kwargs): response['X-UI-Max-Events'] = settings.MAX_UI_JOB_EVENTS @@ -1441,6 +1451,7 @@ class InventoryUpdateEventsList(SubListAPIView): parent_model = InventoryUpdate relationship = 'inventory_update_events' view_name = _('Inventory Update Events List') + search_fields = ('stdout',) def finalize_response(self, request, response, *args, **kwargs): response['X-UI-Max-Events'] = settings.MAX_UI_JOB_EVENTS @@ -1468,6 +1479,7 @@ class ProjectUpdateNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = ProjectUpdate relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class ProjectUpdateScmInventoryUpdates(SubListCreateAPIView): @@ -1491,6 +1503,7 @@ class ProjectObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Project + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -1573,6 +1586,7 @@ class OAuth2ApplicationActivityStreamList(ActivityStreamEnforcementMixin, SubLis parent_model = OAuth2Application relationship = 'activitystream_set' swagger_topic = 'Authentication' + search_fields = ('changes',) class OAuth2TokenList(ListCreateAPIView): @@ -1657,6 +1671,7 @@ class OAuth2TokenActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIVi parent_model = OAuth2AccessToken relationship = 'activitystream_set' swagger_topic = 'Authentication' + search_fields = ('changes',) class UserTeamsList(ListAPIView): @@ -1680,6 +1695,7 @@ class UserRolesList(SubListAttachDetachAPIView): parent_model = User relationship='roles' permission_classes = (IsAuthenticated,) + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): u = get_object_or_404(User, pk=self.kwargs['pk']) @@ -1766,6 +1782,7 @@ class UserActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = User relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -1852,6 +1869,7 @@ class CredentialTypeActivityStreamList(ActivityStreamEnforcementMixin, SubListAP serializer_class = ActivityStreamSerializer parent_model = CredentialType relationship = 'activitystream_set' + search_fields = ('changes',) # remove in 3.3 @@ -1965,6 +1983,7 @@ class CredentialActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIVie serializer_class = ActivityStreamSerializer parent_model = Credential relationship = 'activitystream_set' + search_fields = ('changes',) class CredentialAccessList(ResourceAccessList): @@ -1978,6 +1997,7 @@ class CredentialObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Credential + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -2018,6 +2038,7 @@ class InventoryScriptObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = CustomInventoryScript + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -2105,6 +2126,7 @@ class InventoryActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView serializer_class = ActivityStreamSerializer parent_model = Inventory relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -2132,6 +2154,7 @@ class InventoryObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Inventory + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -2275,6 +2298,7 @@ class HostActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = Host relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -2288,6 +2312,7 @@ class HostFactVersionsList(SystemTrackingEnforcementMixin, ParentMixin, ListAPIV model = Fact serializer_class = FactVersionSerializer parent_model = Host + search_fields = ('facts',) def get_queryset(self): from_spec = self.request.query_params.get('from', None) @@ -2521,6 +2546,7 @@ class GroupActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = Group relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -2752,6 +2778,7 @@ class InventorySourceActivityStreamList(ActivityStreamEnforcementMixin, SubListA serializer_class = ActivityStreamSerializer parent_model = InventorySource relationship = 'activitystream_set' + search_fields = ('changes',) class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): @@ -2891,6 +2918,7 @@ class InventoryUpdateNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = InventoryUpdate relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class JobTemplateList(ListCreateAPIView): @@ -3229,6 +3257,7 @@ class JobTemplateActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIVi serializer_class = ActivityStreamSerializer parent_model = JobTemplate relationship = 'activitystream_set' + search_fields = ('changes',) class JobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): @@ -3512,6 +3541,7 @@ class JobTemplateObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = JobTemplate + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -3529,6 +3559,7 @@ class WorkflowJobNodeList(WorkflowsEnforcementMixin, ListAPIView): model = WorkflowJobNode serializer_class = WorkflowJobNodeListSerializer + search_fields = ('unified_job_template__name', 'unified_job_template__description',) class WorkflowJobNodeDetail(WorkflowsEnforcementMixin, RetrieveAPIView): @@ -3549,6 +3580,7 @@ class WorkflowJobTemplateNodeList(WorkflowsEnforcementMixin, ListCreateAPIView): model = WorkflowJobTemplateNode serializer_class = WorkflowJobTemplateNodeSerializer + search_fields = ('unified_job_template__name', 'unified_job_template__description',) class WorkflowJobTemplateNodeDetail(WorkflowsEnforcementMixin, RetrieveUpdateDestroyAPIView): @@ -3570,6 +3602,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(WorkflowsEnforcementMixin, Enforce parent_model = WorkflowJobTemplateNode relationship = '' enforce_parent_relationship = 'workflow_job_template' + search_fields = ('unified_job_template__name', 'unified_job_template__description',) ''' Limit the set of WorkflowJobTemplateNodes to the related nodes of specified by @@ -3639,6 +3672,7 @@ class WorkflowJobNodeChildrenBaseList(WorkflowsEnforcementMixin, SubListAPIView) serializer_class = WorkflowJobNodeListSerializer parent_model = WorkflowJobNode relationship = '' + search_fields = ('unified_job_template__name', 'unified_job_template__description',) # #Limit the set of WorkflowJobeNodes to the related nodes of specified by @@ -3794,6 +3828,7 @@ class WorkflowJobTemplateWorkflowNodesList(WorkflowsEnforcementMixin, SubListCre parent_model = WorkflowJobTemplate relationship = 'workflow_job_template_nodes' parent_key = 'workflow_job_template' + search_fields = ('unified_job_template__name', 'unified_job_template__description',) def get_queryset(self): return super(WorkflowJobTemplateWorkflowNodesList, self).get_queryset().order_by('id') @@ -3854,6 +3889,7 @@ class WorkflowJobTemplateObjectRolesList(WorkflowsEnforcementMixin, SubListAPIVi model = Role serializer_class = RoleSerializer parent_model = WorkflowJobTemplate + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): po = self.get_parent_object() @@ -3867,6 +3903,7 @@ class WorkflowJobTemplateActivityStreamList(WorkflowsEnforcementMixin, ActivityS serializer_class = ActivityStreamSerializer parent_model = WorkflowJobTemplate relationship = 'activitystream_set' + search_fields = ('changes',) def get_queryset(self): parent = self.get_parent_object() @@ -3896,6 +3933,7 @@ class WorkflowJobWorkflowNodesList(WorkflowsEnforcementMixin, SubListAPIView): parent_model = WorkflowJob relationship = 'workflow_job_nodes' parent_key = 'workflow_job' + search_fields = ('unified_job_template__name', 'unified_job_template__description',) def get_queryset(self): return super(WorkflowJobWorkflowNodesList, self).get_queryset().order_by('id') @@ -3924,6 +3962,7 @@ class WorkflowJobNotificationsList(WorkflowsEnforcementMixin, SubListAPIView): serializer_class = NotificationSerializer parent_model = WorkflowJob relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class WorkflowJobActivityStreamList(WorkflowsEnforcementMixin, ActivityStreamEnforcementMixin, SubListAPIView): @@ -3932,6 +3971,7 @@ class WorkflowJobActivityStreamList(WorkflowsEnforcementMixin, ActivityStreamEnf serializer_class = ActivityStreamSerializer parent_model = WorkflowJob relationship = 'activitystream_set' + search_fields = ('changes',) class SystemJobTemplateList(ListAPIView): @@ -4087,6 +4127,7 @@ class JobActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): serializer_class = ActivityStreamSerializer parent_model = Job relationship = 'activitystream_set' + search_fields = ('changes',) # TODO: remove endpoint in 3.3 @@ -4290,6 +4331,7 @@ class JobNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = Job relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class BaseJobHostSummariesList(SubListAPIView): @@ -4299,6 +4341,7 @@ class BaseJobHostSummariesList(SubListAPIView): parent_model = None # Subclasses must define this attribute. relationship = 'job_host_summaries' view_name = _('Job Host Summaries List') + search_fields = ('host_name',) def get_queryset(self): parent = self.get_parent_object() @@ -4331,6 +4374,7 @@ class JobEventList(ListAPIView): model = JobEvent serializer_class = JobEventSerializer + search_fields = ('stdout',) class JobEventDetail(RetrieveAPIView): @@ -4346,6 +4390,7 @@ class JobEventChildrenList(SubListAPIView): parent_model = JobEvent relationship = 'children' view_name = _('Job Event Children List') + search_fields = ('stdout',) class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView): @@ -4559,6 +4604,7 @@ class AdHocCommandEventList(ListAPIView): model = AdHocCommandEvent serializer_class = AdHocCommandEventSerializer + search_fields = ('stdout',) class AdHocCommandEventDetail(RetrieveAPIView): @@ -4574,6 +4620,7 @@ class BaseAdHocCommandEventsList(SubListAPIView): parent_model = None # Subclasses must define this attribute. relationship = 'ad_hoc_command_events' view_name = _('Ad Hoc Command Events List') + search_fields = ('stdout',) class HostAdHocCommandEventsList(BaseAdHocCommandEventsList): @@ -4596,6 +4643,7 @@ class AdHocCommandActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIV serializer_class = ActivityStreamSerializer parent_model = AdHocCommand relationship = 'activitystream_set' + search_fields = ('changes',) class AdHocCommandNotificationsList(SubListAPIView): @@ -4604,6 +4652,7 @@ class AdHocCommandNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = AdHocCommand relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class SystemJobList(ListCreateAPIView): @@ -4644,6 +4693,7 @@ class SystemJobNotificationsList(SubListAPIView): serializer_class = NotificationSerializer parent_model = SystemJob relationship = 'notifications' + search_fields = ('subject', 'notification_type', 'body',) class UnifiedJobTemplateList(ListAPIView): @@ -4849,6 +4899,7 @@ class NotificationTemplateNotificationList(SubListAPIView): parent_model = NotificationTemplate relationship = 'notifications' parent_key = 'notification_template' + search_fields = ('subject', 'notification_type', 'body',) class NotificationTemplateCopy(CopyAPIView): @@ -4861,6 +4912,7 @@ class NotificationList(ListAPIView): model = Notification serializer_class = NotificationSerializer + search_fields = ('subject', 'notification_type', 'body',) class NotificationDetail(RetrieveAPIView): @@ -4885,6 +4937,7 @@ class ActivityStreamList(ActivityStreamEnforcementMixin, SimpleListAPIView): model = ActivityStream serializer_class = ActivityStreamSerializer + search_fields = ('changes',) class ActivityStreamDetail(ActivityStreamEnforcementMixin, RetrieveAPIView): @@ -4898,6 +4951,7 @@ class RoleList(ListAPIView): model = Role serializer_class = RoleSerializer permission_classes = (IsAuthenticated,) + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): result = Role.visible_roles(self.request.user) @@ -5010,6 +5064,7 @@ class RoleParentsList(SubListAPIView): parent_model = Role relationship = 'parents' permission_classes = (IsAuthenticated,) + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): role = Role.objects.get(pk=self.kwargs['pk']) @@ -5023,6 +5078,7 @@ class RoleChildrenList(SubListAPIView): parent_model = Role relationship = 'children' permission_classes = (IsAuthenticated,) + search_fields = ('role_field', 'content_type__model',) def get_queryset(self): role = Role.objects.get(pk=self.kwargs['pk']) diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py index 6e3572dc7d..f1bad79400 100644 --- a/awx/main/tests/unit/test_views.py +++ b/awx/main/tests/unit/test_views.py @@ -3,6 +3,10 @@ import mock # Django REST Framework from rest_framework import exceptions +from rest_framework.generics import ListAPIView + +# Django +from django.core.urlresolvers import RegexURLResolver, RegexURLPattern # AWX from awx.main.views import ApiErrorView @@ -52,3 +56,44 @@ def test_disable_post_on_v1_inventory_source_list(version, supports_post): inv_source_list.request = mock.MagicMock() with mock.patch('awx.api.views.get_request_version', return_value=version): assert ('POST' in inv_source_list.allowed_methods) == supports_post + + +def test_views_have_search_fields(): + from awx.api.urls import urlpatterns as api_patterns + patterns = set([]) + url_views = set([]) + # Add recursive URL patterns + unprocessed = set(api_patterns) + while unprocessed: + to_process = unprocessed.copy() + unprocessed = set([]) + for pattern in to_process: + if hasattr(pattern, 'lookup_str') and not pattern.lookup_str.startswith('awx.api'): + continue + patterns.add(pattern) + if isinstance(pattern, RegexURLResolver): + for sub_pattern in pattern.url_patterns: + if sub_pattern not in patterns: + unprocessed.add(sub_pattern) + # Get view classes + for pattern in patterns: + if isinstance(pattern, RegexURLPattern) and hasattr(pattern.callback, 'view_class'): + cls = pattern.callback.view_class + if issubclass(cls, ListAPIView): + url_views.add(pattern.callback.view_class) + + # Gather any views that don't have search fields defined + views_missing_search = [] + for View in url_views: + view = View() + if not hasattr(view, 'search_fields') or len(view.search_fields) == 0: + views_missing_search.append(view) + + if views_missing_search: + raise Exception('{} views do not have search fields defined:\n{}'.format( + len(views_missing_search), + '\n'.join([ + v.__class__.__name__ + ' (model: {})'.format(getattr(v, 'model', type(None)).__name__) + for v in views_missing_search + ])) + )