mirror of
https://github.com/ansible/awx.git
synced 2026-06-17 04:37:43 -02:30
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea94a2e07 | ||
|
|
46f938ae82 | ||
|
|
33d18f5e5e |
@@ -4,7 +4,8 @@
|
||||
import dateutil
|
||||
import logging
|
||||
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, IntegerField, OuterRef, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.timezone import now
|
||||
@@ -15,7 +16,7 @@ from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from awx.main.constants import ACTIVE_STATES
|
||||
from awx.main.models import Organization
|
||||
from awx.main.models import Organization, Role
|
||||
from awx.main.utils import get_object_or_400
|
||||
from awx.main.models.ha import Instance, InstanceGroup, schedule_policy_task
|
||||
from awx.main.models.organization import Team
|
||||
@@ -178,9 +179,28 @@ class OrganizationCountsMixin(object):
|
||||
db_results['projects'] = project_qs.values('organization').annotate(Count('organization')).order_by('organization')
|
||||
|
||||
# Other members and admins of organization are always viewable
|
||||
db_results['users'] = org_qs.annotate(users=Count('member_role__members', distinct=True), admins=Count('admin_role__members', distinct=True)).values(
|
||||
'id', 'users', 'admins'
|
||||
#
|
||||
# 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(),
|
||||
)
|
||||
db_results['users'] = org_qs.annotate(
|
||||
users=Coalesce(member_count, 0),
|
||||
admins=Coalesce(admin_count, 0),
|
||||
).values('id', 'users', 'admins')
|
||||
|
||||
count_context = {}
|
||||
for org in org_id_list:
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import logging
|
||||
|
||||
# Django
|
||||
from django.db.models import Count
|
||||
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 _
|
||||
|
||||
@@ -77,9 +78,29 @@ class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPI
|
||||
|
||||
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=Count('member_role__members', distinct=True), admins=Count('admin_role__members', distinct=True))
|
||||
.annotate(
|
||||
users=Coalesce(member_count, 0),
|
||||
admins=Coalesce(admin_count, 0),
|
||||
)
|
||||
.values('users', 'admins')
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ from django.db.models.functions import Lower
|
||||
|
||||
from ansible_base.lib.utils.db import advisory_lock
|
||||
|
||||
from awx.main.utils.common import memoize
|
||||
from awx.main.utils.filters import SmartFilter
|
||||
from awx.main.constants import RECEPTOR_PENDING
|
||||
|
||||
@@ -86,7 +85,6 @@ class HostLatestSummaryQuerySet(models.QuerySet):
|
||||
class HostManager(models.Manager.from_queryset(HostLatestSummaryQuerySet)):
|
||||
"""Custom manager class for Hosts model."""
|
||||
|
||||
@memoize(ttl=60, cache_key='host_active_count')
|
||||
def active_count(self):
|
||||
"""Return count of active, unique hosts for licensing.
|
||||
Construction of query involves:
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0205_add_ordering_to_instancegroup_and_workflow_nodes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='host',
|
||||
index=models.Index(
|
||||
Lower('name'),
|
||||
name='main_host_name_lower_idx',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -19,7 +19,6 @@ from django.core.exceptions import ValidationError
|
||||
from django.urls import resolve
|
||||
from django.utils.timezone import now
|
||||
from django.db.models import Q, Subquery, OuterRef
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
# REST Framework
|
||||
from rest_framework.exceptions import ParseError
|
||||
@@ -524,9 +523,6 @@ class Host(CommonModelNameNotUnique, RelatedJobsMixin):
|
||||
app_label = 'main'
|
||||
unique_together = (("name", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration.
|
||||
ordering = ('name',)
|
||||
indexes = [
|
||||
models.Index(Lower('name'), name='main_host_name_lower_idx'),
|
||||
]
|
||||
|
||||
inventory = models.ForeignKey(
|
||||
'Inventory',
|
||||
|
||||
@@ -92,22 +92,6 @@ class TestInventoryScript:
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestActiveCount:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _bypass_active_count_cache(self):
|
||||
from django.core.cache import cache
|
||||
|
||||
cache.delete('host_active_count')
|
||||
original_set = cache.set
|
||||
|
||||
def skip_host_cache(key, *args, **kwargs):
|
||||
if key == 'host_active_count':
|
||||
return
|
||||
return original_set(key, *args, **kwargs)
|
||||
|
||||
with mock.patch.object(cache, 'set', side_effect=skip_host_cache):
|
||||
yield
|
||||
cache.delete('host_active_count')
|
||||
|
||||
def test_host_active_count(self, organization):
|
||||
inv1 = Inventory.objects.create(name='inv1', organization=organization)
|
||||
inv2 = Inventory.objects.create(name='inv2', organization=organization)
|
||||
@@ -157,41 +141,6 @@ class TestActiveCount:
|
||||
assert Host.objects.active_count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestActiveCountCache:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_active_count_cache(self):
|
||||
from awx.main.utils.common import memoize_delete
|
||||
|
||||
memoize_delete('host_active_count')
|
||||
yield
|
||||
memoize_delete('host_active_count')
|
||||
|
||||
def test_active_count_cache(self, organization):
|
||||
inv = Inventory.objects.create(name='inv1', organization=organization)
|
||||
inv.hosts.create(name='host1')
|
||||
assert Host.objects.active_count() == 1
|
||||
|
||||
inv.hosts.create(name='host2')
|
||||
assert Host.objects.active_count() == 1 # still cached
|
||||
|
||||
from awx.main.utils.common import memoize_delete
|
||||
|
||||
memoize_delete('host_active_count')
|
||||
assert Host.objects.active_count() == 2
|
||||
|
||||
def test_active_count_cache_after_delete(self, organization):
|
||||
inv = Inventory.objects.create(name='inv1', organization=organization)
|
||||
h = inv.hosts.create(name='host1')
|
||||
assert Host.objects.active_count() == 1
|
||||
|
||||
h.delete()
|
||||
from awx.main.utils.common import memoize_delete
|
||||
|
||||
memoize_delete('host_active_count')
|
||||
assert Host.objects.active_count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSCMUpdateFeatures:
|
||||
def test_source_location(self, scm_inventory_source):
|
||||
|
||||
Reference in New Issue
Block a user