diff --git a/awx/api/views/mixin.py b/awx/api/views/mixin.py index ee174d5091..191327c64f 100644 --- a/awx/api/views/mixin.py +++ b/awx/api/views/mixin.py @@ -29,7 +29,7 @@ from awx.main.models.ha import ( ) from awx.main.models.organization import Team from awx.main.models.projects import Project -from awx.main.models.inventory import Inventory +from awx.main.models.inventory import Inventory, Host from awx.main.models.jobs import JobTemplate from awx.conf.license import ( feature_enabled, @@ -235,6 +235,8 @@ class OrganizationCountsMixin(object): db_results['projects'] = project_qs\ .values('organization').annotate(Count('organization')).order_by('organization') + db_results['hosts'] = Host.objects.active_counts_by_org() + # Other members and admins of organization are always viewable db_results['users'] = org_qs.annotate( users=Count('member_role__members', distinct=True), @@ -246,7 +248,7 @@ class OrganizationCountsMixin(object): org_id = org['id'] count_context[org_id] = { 'inventories': 0, 'teams': 0, 'users': 0, 'job_templates': 0, - 'admins': 0, 'projects': 0} + 'admins': 0, 'projects': 0, 'hosts': 0} for res, count_qs in db_results.items(): if res == 'job_templates_project': @@ -255,6 +257,8 @@ class OrganizationCountsMixin(object): org_reference = JT_inventory_reference elif res == 'users': org_reference = 'id' + elif res == 'hosts': + org_reference = 'inventory__organization' else: org_reference = 'organization' for entry in count_qs: diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index 3c46ad3cca..0a6e40ba53 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -17,6 +17,7 @@ from awx.conf.license import ( from awx.main.models import ( ActivityStream, Inventory, + Host, Project, JobTemplate, WorkflowJobTemplate, @@ -119,6 +120,7 @@ class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPI organization__id=org_id).count() org_counts['job_templates'] = JobTemplate.accessible_objects(**access_kwargs).filter( project__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 diff --git a/awx/main/managers.py b/awx/main/managers.py index 341a7e806a..d554792699 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -29,6 +29,34 @@ class HostManager(models.Manager): """ return self.order_by().exclude(inventory_sources__source='tower').values('name').distinct().count() + def org_active_count(self, org_id): + """Return count of active, unique hosts used by an organization. + Construction of query involves: + - remove any ordering specified in model's Meta + - Exclude hosts sourced from another Tower + - Consider only hosts where the canonical inventory is owned by the organization + - Restrict the query to only return the name column + - Only consider results that are unique + - Return the count of this query + """ + return self.order_by().exclude( + inventory_sources__source='tower' + ).filter(inventory__organization=org_id).values('name').distinct().count() + + def active_counts_by_org(self): + """Return the counts of active, unique hosts for each organization. + Construction of query involves: + - remove any ordering specified in model's Meta + - Exclude hosts sourced from another Tower + - Consider only hosts where the canonical inventory is owned by each organization + - Restrict the query to only count distinct names + - Return the counts + """ + return self.order_by().exclude( + inventory_sources__source='tower' + ).values('inventory__organization').annotate( + inventory__organization__count=models.Count('name', distinct=True)) + def get_queryset(self): """When the parent instance of the host query set has a `kind=smart` and a `host_filter` set. Use the `host_filter` to generate the queryset for the hosts.