# Copyright (c) 2018 Red Hat, Inc. # All Rights Reserved. # Python import logging # Django from django.db.models import Count, IntegerField, OuterRef, Subquery from django.db.models.functions import Coalesce from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ # AWX from awx.main.models import ( ActivityStream, Inventory, Host, Project, ExecutionEnvironment, JobTemplate, WorkflowJobTemplate, Organization, NotificationTemplate, Role, User, Team, InstanceGroup, Credential, ) from awx.api.generics import ( ListCreateAPIView, RetrieveUpdateDestroyAPIView, SubListAPIView, SubListCreateAttachDetachAPIView, SubListAttachDetachAPIView, SubListCreateAPIView, ResourceAccessList, BaseUsersList, ) from awx.api.serializers import ( OrganizationSerializer, InventorySerializer, UserSerializer, TeamSerializer, ActivityStreamSerializer, RoleSerializer, NotificationTemplateSerializer, InstanceGroupSerializer, ExecutionEnvironmentSerializer, ProjectSerializer, JobTemplateSerializer, WorkflowJobTemplateSerializer, CredentialSerializer, ) from awx.api.views.mixin import RelatedJobsPreventDeleteMixin, OrganizationCountsMixin, OrganizationInstanceGroupMembershipMixin logger = logging.getLogger('awx.api.views.organization') class OrganizationList(OrganizationCountsMixin, ListCreateAPIView): model = Organization serializer_class = OrganizationSerializer resource_purpose = 'organizations' class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): model = Organization serializer_class = OrganizationSerializer resource_purpose = 'organization detail' def get_serializer_context(self, *args, **kwargs): full_context = super(OrganizationDetail, self).get_serializer_context(*args, **kwargs) if not hasattr(self, 'kwargs') or 'pk' not in self.kwargs: return full_context org_id = int(self.kwargs['pk']) org_counts = {} access_kwargs = {'accessor': self.request.user, 'role_field': 'read_role'} # Use independent subqueries instead of double-JOIN Count to avoid # cartesian product. role_members_through = Role.members.through member_count = Subquery( role_members_through.objects.filter(role_id=OuterRef('member_role_id')) .values('role_id') .annotate(cnt=Count('user_id', distinct=True)) .values('cnt'), output_field=IntegerField(), ) admin_count = Subquery( role_members_through.objects.filter(role_id=OuterRef('admin_role_id')) .values('role_id') .annotate(cnt=Count('user_id', distinct=True)) .values('cnt'), output_field=IntegerField(), ) direct_counts = ( Organization.objects.filter(id=org_id).annotate(users=Coalesce(member_count, 0), admins=Coalesce(admin_count, 0)).values('users', 'admins') ) if not direct_counts: return full_context org_counts = direct_counts[0] org_counts['inventories'] = Inventory.accessible_objects(**access_kwargs).filter(organization__id=org_id).count() org_counts['teams'] = Team.accessible_objects(**access_kwargs).filter(organization__id=org_id).count() org_counts['projects'] = Project.accessible_objects(**access_kwargs).filter(organization__id=org_id).count() org_counts['job_templates'] = JobTemplate.accessible_objects(**access_kwargs).filter(organization__id=org_id).count() org_counts['hosts'] = Host.objects.org_active_count(org_id) full_context['related_field_counts'] = {} full_context['related_field_counts'][org_id] = org_counts return full_context class OrganizationInventoriesList(SubListAPIView): model = Inventory serializer_class = InventorySerializer parent_model = Organization relationship = 'inventories' resource_purpose = 'inventories of an organization' class OrganizationUsersList(BaseUsersList): model = User serializer_class = UserSerializer parent_model = Organization relationship = 'member_role.members' ordering = ('username',) resource_purpose = 'users of an organization' class OrganizationAdminsList(BaseUsersList): model = User serializer_class = UserSerializer parent_model = Organization relationship = 'admin_role.members' ordering = ('username',) resource_purpose = 'administrators of an organization' class OrganizationProjectsList(SubListCreateAPIView): model = Project serializer_class = ProjectSerializer parent_model = Organization parent_key = 'organization' resource_purpose = 'projects of an organization' class OrganizationExecutionEnvironmentsList(SubListCreateAttachDetachAPIView): model = ExecutionEnvironment serializer_class = ExecutionEnvironmentSerializer parent_model = Organization relationship = 'executionenvironments' parent_key = 'organization' swagger_topic = "Execution Environments" resource_purpose = 'execution environments of an organization' class OrganizationJobTemplatesList(SubListCreateAPIView): model = JobTemplate serializer_class = JobTemplateSerializer parent_model = Organization parent_key = 'organization' resource_purpose = 'job templates of an organization' class OrganizationWorkflowJobTemplatesList(SubListCreateAPIView): model = WorkflowJobTemplate serializer_class = WorkflowJobTemplateSerializer parent_model = Organization parent_key = 'organization' resource_purpose = 'workflow job templates of an organization' class OrganizationTeamsList(SubListCreateAttachDetachAPIView): model = Team serializer_class = TeamSerializer parent_model = Organization relationship = 'teams' parent_key = 'organization' resource_purpose = 'teams of an organization' class OrganizationActivityStreamList(SubListAPIView): model = ActivityStream serializer_class = ActivityStreamSerializer parent_model = Organization relationship = 'activitystream_set' search_fields = ('changes',) resource_purpose = 'activity stream for an organization' class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView): model = NotificationTemplate serializer_class = NotificationTemplateSerializer parent_model = Organization relationship = 'notification_templates' parent_key = 'organization' resource_purpose = 'notification templates of an organization' class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): model = NotificationTemplate serializer_class = NotificationTemplateSerializer parent_model = Organization resource_purpose = 'base view for notification templates of an organization' class OrganizationNotificationTemplatesStartedList(OrganizationNotificationTemplatesAnyList): relationship = 'notification_templates_started' resource_purpose = 'notification templates for job started events of an organization' class OrganizationNotificationTemplatesErrorList(OrganizationNotificationTemplatesAnyList): relationship = 'notification_templates_error' resource_purpose = 'notification templates for job error events of an organization' class OrganizationNotificationTemplatesSuccessList(OrganizationNotificationTemplatesAnyList): relationship = 'notification_templates_success' resource_purpose = 'notification templates for job success events of an organization' class OrganizationNotificationTemplatesApprovalList(OrganizationNotificationTemplatesAnyList): relationship = 'notification_templates_approvals' resource_purpose = 'notification templates for workflow approval events of an organization' class OrganizationInstanceGroupsList(OrganizationInstanceGroupMembershipMixin, SubListAttachDetachAPIView): model = InstanceGroup serializer_class = InstanceGroupSerializer parent_model = Organization relationship = 'instance_groups' filter_read_permission = False resource_purpose = 'instance groups of an organization' class OrganizationGalaxyCredentialsList(SubListAttachDetachAPIView): model = Credential serializer_class = CredentialSerializer parent_model = Organization relationship = 'galaxy_credentials' filter_read_permission = False resource_purpose = 'galaxy credentials of an organization' def is_valid_relation(self, parent, sub, created=False): if sub.kind != 'galaxy_api_token': return {'msg': _(f"Credential must be a Galaxy credential, not {sub.credential_type.name}.")} class OrganizationAccessList(ResourceAccessList): model = User # needs to be User for AccessLists's parent_model = Organization resource_purpose = 'users who can access the organization' class OrganizationObjectRolesList(SubListAPIView): model = Role serializer_class = RoleSerializer parent_model = Organization search_fields = ('role_field', 'content_type__model') deprecated = True resource_purpose = 'roles of an organization' def get_queryset(self): po = self.get_parent_object() content_type = ContentType.objects.get_for_model(self.parent_model) return Role.objects.filter(content_type=content_type, object_id=po.pk)