mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
Merge pull request #1230 from AlanCoding/647_org_summary_numbers
Provide counts of organization resources in list view
This commit is contained in:
commit
96517d2381
@ -798,6 +798,18 @@ class OrganizationSerializer(BaseSerializer):
|
||||
))
|
||||
return res
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
summary_dict = super(OrganizationSerializer, self).get_summary_fields(obj)
|
||||
counts_dict = self.context.get('related_field_counts', None)
|
||||
if counts_dict is not None and summary_dict is not None:
|
||||
if obj.id not in counts_dict:
|
||||
summary_dict['related_field_counts'] = {
|
||||
'inventories': 0, 'teams': 0, 'users': 0,
|
||||
'job_templates': 0, 'admins': 0, 'projects': 0}
|
||||
else:
|
||||
summary_dict['related_field_counts'] = counts_dict[obj.id]
|
||||
return summary_dict
|
||||
|
||||
|
||||
class ProjectOptionsSerializer(BaseSerializer):
|
||||
|
||||
|
||||
@ -614,6 +614,78 @@ class OrganizationList(ListCreateAPIView):
|
||||
# Okay, create the organization as usual.
|
||||
return super(OrganizationList, self).create(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_context(self, *args, **kwargs):
|
||||
full_context = super(OrganizationList, self).get_serializer_context(*args, **kwargs)
|
||||
|
||||
if self.request is None:
|
||||
return full_context
|
||||
|
||||
db_results = {}
|
||||
org_qs = self.request.user.get_queryset(self.model)
|
||||
org_id_list = org_qs.values('id')
|
||||
if len(org_id_list) == 0:
|
||||
if self.request.method == 'POST':
|
||||
full_context['related_field_counts'] = {}
|
||||
return full_context
|
||||
|
||||
inv_qs = self.request.user.get_queryset(Inventory)
|
||||
project_qs = self.request.user.get_queryset(Project)
|
||||
user_qs = self.request.user.get_queryset(User)
|
||||
|
||||
# Produce counts of Foreign Key relationships
|
||||
db_results['inventories'] = inv_qs\
|
||||
.values('organization').annotate(Count('organization')).order_by('organization')
|
||||
|
||||
db_results['teams'] = self.request.user.get_queryset(Team)\
|
||||
.values('organization').annotate(Count('organization')).order_by('organization')
|
||||
|
||||
# TODO: When RBAC branch merges, change this to project relationship
|
||||
JT_reference = 'inventory__organization'
|
||||
# Extra filter is applied on the inventory, because this catches
|
||||
# the case of deleted (and purged) inventory
|
||||
db_results['job_templates'] = self.request.user.get_queryset(JobTemplate)\
|
||||
.filter(inventory__in=inv_qs)\
|
||||
.values(JT_reference).annotate(Count(JT_reference))\
|
||||
.order_by(JT_reference)
|
||||
|
||||
# Produce counts of m2m relationships
|
||||
db_results['projects'] = Organization.projects.through.objects\
|
||||
.filter(project__in=project_qs, organization__in=org_qs)\
|
||||
.values('organization')\
|
||||
.annotate(Count('organization')).order_by('organization')
|
||||
|
||||
# TODO: When RBAC branch merges, change these to role relation
|
||||
db_results['users'] = Organization.users.through.objects\
|
||||
.filter(user__in=user_qs, organization__in=org_qs)\
|
||||
.values('organization')\
|
||||
.annotate(Count('organization')).order_by('organization')
|
||||
|
||||
db_results['admins'] = Organization.admins.through.objects\
|
||||
.filter(user__in=user_qs, organization__in=org_qs)\
|
||||
.values('organization')\
|
||||
.annotate(Count('organization')).order_by('organization')
|
||||
|
||||
count_context = {}
|
||||
for org in org_id_list:
|
||||
org_id = org['id']
|
||||
count_context[org_id] = {
|
||||
'inventories': 0, 'teams': 0, 'users': 0, 'job_templates': 0,
|
||||
'admins': 0, 'projects': 0}
|
||||
|
||||
for res in db_results:
|
||||
if res == 'job_templates':
|
||||
org_reference = JT_reference
|
||||
else:
|
||||
org_reference = 'organization'
|
||||
for entry in db_results[res]:
|
||||
org_id = entry[org_reference]
|
||||
if org_id in count_context:
|
||||
count_context[org_id][res] = entry['%s__count' % org_reference]
|
||||
|
||||
full_context['related_field_counts'] = count_context
|
||||
|
||||
return full_context
|
||||
|
||||
class OrganizationDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = Organization
|
||||
|
||||
144
awx/main/tests/functional/api/test_organization_counts.py
Normal file
144
awx/main/tests/functional/api/test_organization_counts.py
Normal file
@ -0,0 +1,144 @@
|
||||
import pytest
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@pytest.fixture
|
||||
def resourced_organization(organization, project, team, inventory, user):
|
||||
admin_user = user('test-admin', True)
|
||||
member_user = user('org-member')
|
||||
|
||||
# Associate one resource of every type with the organization
|
||||
organization.users.add(member_user)
|
||||
organization.admins.add(admin_user)
|
||||
organization.projects.add(project)
|
||||
# organization.teams.create(name='org-team')
|
||||
# inventory = organization.inventories.create(name="associated-inv")
|
||||
project.jobtemplates.create(name="test-jt",
|
||||
description="test-job-template-desc",
|
||||
inventory=inventory,
|
||||
playbook="test_playbook.yml")
|
||||
|
||||
return organization
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_org_counts_admin(resourced_organization, user, get):
|
||||
# Check that all types of resources are counted by a superuser
|
||||
external_admin = user('admin', True)
|
||||
response = get(reverse('api:organization_list', args=[]), external_admin)
|
||||
assert response.status_code == 200
|
||||
|
||||
counts = response.data['results'][0]['summary_fields']['related_field_counts']
|
||||
assert counts == {
|
||||
'users': 1,
|
||||
'admins': 1,
|
||||
'job_templates': 1,
|
||||
'projects': 1,
|
||||
'inventories': 1,
|
||||
'teams': 1
|
||||
}
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_org_counts_member(resourced_organization, get):
|
||||
# Check that a non-admin user can only see the full project and
|
||||
# user count, consistent with the RBAC rules
|
||||
member_user = resourced_organization.users.get(username='org-member')
|
||||
response = get(reverse('api:organization_list', args=[]), member_user)
|
||||
assert response.status_code == 200
|
||||
|
||||
counts = response.data['results'][0]['summary_fields']['related_field_counts']
|
||||
|
||||
assert counts == {
|
||||
'users': 1, # User can see themselves
|
||||
'admins': 0,
|
||||
'job_templates': 0,
|
||||
'projects': 1, # Projects are shared with all the organization
|
||||
'inventories': 0,
|
||||
'teams': 0
|
||||
}
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_new_org_zero_counts(user, post):
|
||||
# Check that a POST to the organization list endpoint returns
|
||||
# correct counts, including the new record
|
||||
org_list_url = reverse('api:organization_list', args=[])
|
||||
post_response = post(url=org_list_url, data={'name': 'test organization',
|
||||
'description': ''}, user=user('admin', True))
|
||||
assert post_response.status_code == 201
|
||||
|
||||
new_org_list = post_response.render().data
|
||||
counts_dict = new_org_list['summary_fields']['related_field_counts']
|
||||
assert counts_dict == {
|
||||
'users': 0,
|
||||
'admins': 0,
|
||||
'job_templates': 0,
|
||||
'projects': 0,
|
||||
'inventories': 0,
|
||||
'teams': 0
|
||||
}
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_two_organizations(resourced_organization, organizations, user, get):
|
||||
# Check correct results for two organizations are returned
|
||||
external_admin = user('admin', True)
|
||||
organization_zero = organizations(1)[0]
|
||||
response = get(reverse('api:organization_list', args=[]), external_admin)
|
||||
assert response.status_code == 200
|
||||
|
||||
org_id_full = resourced_organization.id
|
||||
org_id_zero = organization_zero.id
|
||||
counts = {}
|
||||
for i in range(2):
|
||||
org_id = response.data['results'][i]['id']
|
||||
counts[org_id] = response.data['results'][i]['summary_fields']['related_field_counts']
|
||||
|
||||
assert counts[org_id_full] == {
|
||||
'users': 1,
|
||||
'admins': 1,
|
||||
'job_templates': 1,
|
||||
'projects': 1,
|
||||
'inventories': 1,
|
||||
'teams': 1
|
||||
}
|
||||
assert counts[org_id_zero] == {
|
||||
'users': 0,
|
||||
'admins': 0,
|
||||
'job_templates': 0,
|
||||
'projects': 0,
|
||||
'inventories': 0,
|
||||
'teams': 0
|
||||
}
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_JT_associated_with_project(organizations, project, user, get):
|
||||
# Check that adding a project to an organization gets the project's JT
|
||||
# included in the organization's JT count
|
||||
external_admin = user('admin', True)
|
||||
two_orgs = organizations(2)
|
||||
organization = two_orgs[0]
|
||||
other_org = two_orgs[1]
|
||||
|
||||
unrelated_inv = other_org.inventories.create(name='not-in-organization')
|
||||
project.jobtemplates.create(name="test-jt",
|
||||
description="test-job-template-desc",
|
||||
inventory=unrelated_inv,
|
||||
playbook="test_playbook.yml")
|
||||
organization.projects.add(project)
|
||||
|
||||
response = get(reverse('api:organization_list', args=[]), external_admin)
|
||||
assert response.status_code == 200
|
||||
|
||||
org_id = organization.id
|
||||
counts = {}
|
||||
for i in range(2):
|
||||
working_id = response.data['results'][i]['id']
|
||||
counts[working_id] = response.data['results'][i]['summary_fields']['related_field_counts']
|
||||
|
||||
assert counts[org_id] == {
|
||||
'users': 0,
|
||||
'admins': 0,
|
||||
'job_templates': 1,
|
||||
'projects': 1,
|
||||
'inventories': 0,
|
||||
'teams': 0
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user