diff --git a/awx/api/generics.py b/awx/api/generics.py index 0735f768e9..5930953628 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -5,10 +5,13 @@ import inspect import logging import json +import time # Django from django.http import Http404 +from django.conf import settings from django.contrib.auth.models import User +from django.db import connection from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from django.utils.safestring import mark_safe @@ -75,12 +78,33 @@ class APIView(views.APIView): def initialize_request(self, request, *args, **kwargs): ''' Store the Django REST Framework Request object as an attribute on the - normal Django request. + normal Django request, store time the request started. ''' + self.time_started = time.time() + if getattr(settings, 'SQL_DEBUG', False): + self.queries_before = len(connection.queries) drf_request = super(APIView, self).initialize_request(request, *args, **kwargs) request.drf_request = drf_request return drf_request + def finalize_response(self, request, response, *args, **kwargs): + ''' + Log warning for 400 requests. Add header with elapsed time. + ''' + if response.status_code >= 400: + logger.warn("status %s received by user %s attempting to access %s" % (response.status_code, request.user, request.path)) + response = super(APIView, self).finalize_response(request, response, *args, **kwargs) + time_started = getattr(self, 'time_started', None) + if time_started: + time_elapsed = time.time() - self.time_started + response['X-API-Time'] = '%0.3fs' % time_elapsed + if getattr(settings, 'SQL_DEBUG', False): + queries_before = getattr(self, 'queries_before', 0) + q_times = [float(q['time']) for q in connection.queries[queries_before:]] + response['X-API-Query-Count'] = len(q_times) + response['X-API-Query-Time'] = '%0.3fs' % sum(q_times) + return response + def get_authenticate_header(self, request): """ Determine the WWW-Authenticate header to use for 401 responses. Try to @@ -134,11 +158,6 @@ class APIView(views.APIView): ret['added_in_version'] = added_in_version return ret - def finalize_response(self, request, response, *args, **kwargs): - if response.status_code >= 400: - logger.warn("status %s received by user %s attempting to access %s" % (response.status_code, request.user, request.path)) - return super(APIView, self).finalize_response(request, response, *args, **kwargs) - class GenericAPIView(generics.GenericAPIView, APIView): # Base class for all model-based views. diff --git a/awx/api/renderers.py b/awx/api/renderers.py index 3ae5dd2616..2b575832ac 100644 --- a/awx/api/renderers.py +++ b/awx/api/renderers.py @@ -1,6 +1,9 @@ # Copyright (c) 2014 AnsibleWorks, Inc. # All Rights Reserved. +# Django +from django.utils.datastructures import SortedDict + # Django REST Framework from rest_framework import renderers @@ -31,6 +34,15 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer): if method in ('DELETE', 'OPTIONS'): return True # Don't actually need to return a form + def get_context(self, data, accepted_media_type, renderer_context): + context = super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context) + # FIXME: Sort headers / preserve sorting? + #response_headers = SortedDict(context['response'].items()) + #if 'Content-Type' in context['response_headers']: + # response_headers['Content-Type'] = context['response_headers']['Content-Type'] + #context['response_headers'] = response_headers + return context + class PlainTextRenderer(renderers.BaseRenderer): media_type = 'text/plain' diff --git a/awx/api/views.py b/awx/api/views.py index cd516e8087..ecd101afad 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -947,12 +947,36 @@ class InventoryScriptView(RetrieveAPIView): data['all'] = SortedDict() data['all']['vars'] = self.object.variables_dict + # Build in-memory mapping of groups and their hosts. + group_hosts_kw = dict(group__inventory_id=self.object.id, group__active=True, + host__inventory_id=self.object.id, host__active=True) + if 'enabled' in hosts_q: + group_hosts_kw['host__enabled'] = hosts_q['enabled'] + group_hosts_qs = Group.hosts.through.objects.filter(**group_hosts_kw) + group_hosts_qs = group_hosts_qs.order_by('host__name') + group_hosts_qs = group_hosts_qs.values_list('group_id', 'host_id', 'host__name') + group_hosts_map = {} + for group_id, host_id, host_name in group_hosts_qs: + group_hostnames = group_hosts_map.setdefault(group_id, []) + group_hostnames.append(host_name) + + # Build in-memory mapping of groups and their children. + group_parents_qs = Group.parents.through.objects.filter( + from_group__inventory_id=self.object.id, from_group__active=True, + to_group__inventory_id=self.object.id, to_group__active=True, + ) + group_parents_qs = group_parents_qs.order_by('from_group__name') + group_parents_qs = group_parents_qs.values_list('from_group_id', 'from_group__name', 'to_group_id') + group_children_map = {} + for from_group_id, from_group_name, to_group_id in group_parents_qs: + group_children = group_children_map.setdefault(to_group_id, []) + group_children.append(from_group_name) + + # Now use in-memory maps to build up group info. for group in self.object.groups.filter(active=True): - hosts = group.hosts.filter(**hosts_q) - children = group.children.filter(active=True) group_info = SortedDict() - group_info['hosts'] = list(hosts.values_list('name', flat=True)) - group_info['children'] = list(children.values_list('name', flat=True)) + group_info['hosts'] = group_hosts_map.get(group.id, []) + group_info['children'] = group_children_map.get(group.id, []) group_info['vars'] = group.variables_dict data[group.name] = group_info