diff --git a/awx/api/generics.py b/awx/api/generics.py index 878dce4fa4..f88fdd824c 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -33,7 +33,7 @@ from rest_framework.negotiation import DefaultContentNegotiation # AWX from awx.api.filters import FieldLookupBackend from awx.main.models import UnifiedJob, UnifiedJobTemplate, User, Role, Credential, WorkflowJobTemplateNode, WorkflowApprovalTemplate -from awx.main.access import access_registry +from awx.main.access import optimize_queryset from awx.main.utils import camelcase_to_underscore, get_search_fields, getattrd, get_object_or_400, decrypt_field, get_awx_version from awx.main.utils.db import get_all_field_names from awx.main.utils.licensing import server_product_name @@ -362,12 +362,7 @@ class GenericAPIView(generics.GenericAPIView, APIView): return self.queryset._clone() elif self.model is not None: qs = self.model._default_manager - if self.model in access_registry: - access_class = access_registry[self.model] - if access_class.select_related: - qs = qs.select_related(*access_class.select_related) - if access_class.prefetch_related: - qs = qs.prefetch_related(*access_class.prefetch_related) + qs = optimize_queryset(qs) return qs else: return super(GenericAPIView, self).get_queryset() @@ -529,11 +524,7 @@ class SubListAPIView(ParentMixin, ListAPIView): self.check_parent_access(parent) sublist_qs = self.get_sublist_queryset(parent) if not self.filter_read_permission: - access_class = access_registry[self.model] - if access_class.prefetch_related: - return sublist_qs.prefetch_related(*access_class.prefetch_related) - if access_class.select_related: - return sublist_qs.select_related(*access_class.select_related) + return optimize_queryset(sublist_qs) qs = self.request.user.get_queryset(self.model).distinct() return qs & sublist_qs diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 676a90eca9..0b4f073126 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -62,7 +62,7 @@ from wsgiref.util import FileWrapper # AWX from awx.main.tasks.system import send_notifications, update_inventory_computed_fields -from awx.main.access import get_user_queryset, HostAccess +from awx.main.access import get_user_queryset from awx.api.generics import ( APIView, BaseUsersList, @@ -794,13 +794,7 @@ class ExecutionEnvironmentActivityStreamList(SubListAPIView): parent_model = models.ExecutionEnvironment relationship = 'activitystream_set' search_fields = ('changes',) - - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - - qs = self.request.user.get_queryset(self.model) - return qs.filter(execution_environment=parent) + filter_read_permission = False class ProjectList(ListCreateAPIView): @@ -1634,13 +1628,7 @@ class InventoryHostsList(HostRelatedSearchMixin, SubListCreateAttachDetachAPIVie parent_model = models.Inventory relationship = 'hosts' parent_key = 'inventory' - - def get_queryset(self): - inventory = self.get_parent_object() - qs = getattrd(inventory, self.relationship).all() - # Apply queryset optimizations - qs = qs.select_related(*HostAccess.select_related).prefetch_related(*HostAccess.prefetch_related) - return qs + filter_read_permission = False class HostGroupsList(SubListCreateAttachDetachAPIView): @@ -2862,16 +2850,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su 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 - 'relationship' - ''' - - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - return getattr(parent, self.relationship).all() + filter_read_permission = False def is_valid_relation(self, parent, sub, created=False): if created: @@ -2946,14 +2925,7 @@ class WorkflowJobNodeChildrenBaseList(SubListAPIView): parent_model = models.WorkflowJobNode relationship = '' search_fields = ('unified_job_template__name', 'unified_job_template__description') - - # - # Limit the set of WorkflowJobNodes to the related nodes of specified by self.relationship - # - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - return getattr(parent, self.relationship).all() + filter_read_permission = False class WorkflowJobNodeSuccessNodesList(WorkflowJobNodeChildrenBaseList): @@ -3132,11 +3104,8 @@ class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView): relationship = 'workflow_job_template_nodes' parent_key = 'workflow_job_template' search_fields = ('unified_job_template__name', 'unified_job_template__description') - - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - return getattr(parent, self.relationship).order_by('id') + ordering = ('id',) # assure ordering by id for consistency + filter_read_permission = False class WorkflowJobTemplateJobsList(SubListAPIView): @@ -3228,11 +3197,8 @@ class WorkflowJobWorkflowNodesList(SubListAPIView): relationship = 'workflow_job_nodes' parent_key = 'workflow_job' search_fields = ('unified_job_template__name', 'unified_job_template__description') - - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - return getattr(parent, self.relationship).order_by('id') + ordering = ('id',) # assure ordering by id for consistency + filter_read_permission = False class WorkflowJobCancel(GenericCancelView): @@ -3546,11 +3512,7 @@ class BaseJobHostSummariesList(SubListAPIView): relationship = 'job_host_summaries' name = _('Job Host Summaries List') search_fields = ('host_name',) - - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - return getattr(parent, self.relationship).select_related('job', 'job__job_template', 'host') + filter_read_permission = False class HostJobHostSummariesList(BaseJobHostSummariesList): diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index 1dd03388d7..fc8610d347 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -61,12 +61,6 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView): model = Organization serializer_class = OrganizationSerializer - def get_queryset(self): - qs = Organization.accessible_objects(self.request.user, 'read_role') - qs = qs.select_related('admin_role', 'auditor_role', 'member_role', 'read_role') - qs = qs.prefetch_related('created_by', 'modified_by') - return qs - class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): model = Organization diff --git a/awx/main/access.py b/awx/main/access.py index 5d51ab3b91..da5bc524b2 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -2952,3 +2952,19 @@ class WorkflowApprovalTemplateAccess(BaseAccess): for cls in BaseAccess.__subclasses__(): access_registry[cls.model] = cls access_registry[UnpartitionedJobEvent] = UnpartitionedJobEventAccess + + +def optimize_queryset(queryset): + """ + A utility method in case you already have a queryset and just want to + apply the standard optimizations for that model. + In other words, use if you do not want to start from filtered_queryset for some reason. + """ + if not queryset.model or queryset.model not in access_registry: + return queryset + access_class = access_registry[queryset.model] + if access_class.select_related: + queryset = queryset.select_related(*access_class.select_related) + if access_class.prefetch_related: + queryset = queryset.prefetch_related(*access_class.prefetch_related) + return queryset