Update Django to 1.8 and DRF to 3.3, add new Django migrations, update serializers/pagination/metadata, update browsable API styling.

This commit is contained in:
Chris Church
2016-02-02 14:50:42 -05:00
parent 6242df1a07
commit 60224cdbe4
140 changed files with 2694 additions and 1375 deletions

View File

@@ -8,9 +8,10 @@ import re
from django.core.exceptions import FieldError, ValidationError
from django.db import models
from django.db.models import Q
from django.db.models.related import RelatedObject
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import force_text
# Django REST Framework
from rest_framework.exceptions import ParseError
@@ -46,7 +47,7 @@ class TypeFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
try:
types = None
for key, value in request.QUERY_PARAMS.items():
for key, value in request.query_params.items():
if key == 'type':
if ',' in value:
types = value.split(',')
@@ -107,23 +108,21 @@ class FieldLookupBackend(BaseFilterBackend):
'last_updated': 'last_job_run',
}.get(name, name)
new_parts.append(name)
if name == 'pk':
field = model._meta.pk
else:
field = model._meta.get_field_by_name(name)[0]
if n < (len(parts) - 2):
if getattr(field, 'rel', None):
model = field.rel.to
else:
model = field.model
new_parts.append(name)
model = getattr(field, 'related_model', None) or field.model
if parts:
new_parts.append(parts[-1])
new_lookup = '__'.join(new_parts)
return field, new_lookup
def to_python_related(self, value):
value = unicode(value)
value = force_text(value)
if value.lower() in ('none', 'null'):
return None
else:
@@ -134,7 +133,7 @@ class FieldLookupBackend(BaseFilterBackend):
return to_python_boolean(value, allow_none=True)
elif isinstance(field, models.BooleanField):
return to_python_boolean(value)
elif isinstance(field, RelatedObject):
elif isinstance(field, ForeignObjectRel):
return self.to_python_related(value)
else:
return field.to_python(value)
@@ -159,12 +158,12 @@ class FieldLookupBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
try:
# Apply filters specified via QUERY_PARAMS. Each entry in the lists
# Apply filters specified via query_params. Each entry in the lists
# below is (negate, field, value).
and_filters = []
or_filters = []
chain_filters = []
for key, values in request.QUERY_PARAMS.lists():
for key, values in request.query_params.lists():
if key in self.RESERVED_NAMES:
continue
@@ -246,7 +245,7 @@ class OrderByBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
try:
order_by = None
for key, value in request.QUERY_PARAMS.items():
for key, value in request.query_params.items():
if key in ('order', 'order_by'):
order_by = value
if ',' in value:

View File

@@ -12,6 +12,7 @@ from django.conf import settings
from django.db import connection
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
# Django REST Framework
@@ -19,7 +20,6 @@ from rest_framework.authentication import get_authorization_header
from rest_framework.exceptions import PermissionDenied
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.request import clone_request
from rest_framework import status
from rest_framework import views
@@ -155,18 +155,6 @@ class APIView(views.APIView):
context = self.get_description_context()
return render_to_string(template_list, context)
def metadata(self, request):
'''
Add version number where view was added to Tower.
'''
ret = super(APIView, self).metadata(request)
added_in_version = '1.2'
for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version
break
ret['added_in_version'] = added_in_version
return ret
class GenericAPIView(generics.GenericAPIView, APIView):
# Base class for all model-based views.
@@ -188,8 +176,12 @@ class GenericAPIView(generics.GenericAPIView, APIView):
def get_queryset(self):
#if hasattr(self.request.user, 'get_queryset'):
# return self.request.user.get_queryset(self.model)
#else:
return super(GenericAPIView, self).get_queryset()
if self.queryset is not None:
return self.queryset._clone()
elif self.model is not None:
return self.model._default_manager.all()
else:
return super(GenericAPIView, self).get_queryset()
def get_description_context(self):
# Set instance attributes needed to get serializer metadata.
@@ -201,69 +193,13 @@ class GenericAPIView(generics.GenericAPIView, APIView):
if hasattr(self.model, "_meta"):
if hasattr(self.model._meta, "verbose_name"):
d.update({
'model_verbose_name': unicode(self.model._meta.verbose_name),
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
'model_verbose_name': smart_text(self.model._meta.verbose_name),
'model_verbose_name_plural': smart_text(self.model._meta.verbose_name_plural),
})
d.update({'serializer_fields': self.get_serializer().metadata()})
d['serializer_fields'] = self.metadata_class().get_serializer_info(self.get_serializer())
d['settings'] = settings
return d
def metadata(self, request):
'''
Add field information for GET requests (so field names/labels are
available even when we can't POST/PUT).
'''
ret = super(GenericAPIView, self).metadata(request)
actions = ret.get('actions', {})
# Remove read only fields from PUT/POST data.
for method in ('POST', 'PUT'):
fields = actions.get(method, {})
for field, meta in fields.items():
if not isinstance(meta, dict):
continue
if meta.pop('read_only', False):
fields.pop(field)
if 'GET' in self.allowed_methods:
cloned_request = clone_request(request, 'GET')
try:
# Test global permissions
self.check_permissions(cloned_request)
# Test object permissions
if hasattr(self, 'retrieve'):
try:
self.get_object()
except Http404:
# Http404 should be acceptable and the serializer
# metadata should be populated. Except this so the
# outer "else" clause of the try-except-else block
# will be executed.
pass
except (exceptions.APIException, PermissionDenied):
pass
else:
# If user has appropriate permissions for the view, include
# appropriate metadata about the fields that should be supplied.
serializer = self.get_serializer()
actions['GET'] = serializer.metadata()
if hasattr(serializer, 'get_types'):
ret['types'] = serializer.get_types()
# Remove fields labeled as write_only, remove field attributes
# that aren't relevant for retrieving data.
for field, meta in actions['GET'].items():
if not isinstance(meta, dict):
continue
meta.pop('required', None)
meta.pop('read_only', None)
meta.pop('default', None)
meta.pop('min_length', None)
meta.pop('max_length', None)
if meta.pop('write_only', False):
actions['GET'].pop(field)
if actions:
ret['actions'] = actions
if getattr(self, 'search_fields', None):
ret['search_fields'] = self.search_fields
return ret
class MongoAPIView(GenericAPIView):
@@ -337,8 +273,8 @@ class SubListAPIView(ListAPIView):
def get_description_context(self):
d = super(SubListAPIView, self).get_description_context()
d.update({
'parent_model_verbose_name': unicode(self.parent_model._meta.verbose_name),
'parent_model_verbose_name_plural': unicode(self.parent_model._meta.verbose_name_plural),
'parent_model_verbose_name': smart_text(self.parent_model._meta.verbose_name),
'parent_model_verbose_name_plural': smart_text(self.parent_model._meta.verbose_name_plural),
})
return d
@@ -388,10 +324,10 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
# Make a copy of the data provided (since it's readonly) in order to
# inject additional data.
if hasattr(request.DATA, 'dict'):
data = request.DATA.dict()
if hasattr(request.data, 'dict'):
data = request.data.dict()
else:
data = request.DATA
data = request.data
# add the parent key to the post data using the pk from the URL
parent_key = getattr(self, 'parent_key', None)
@@ -405,7 +341,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
status=status.HTTP_400_BAD_REQUEST)
# Verify we have permission to add the object as given.
if not request.user.can_access(self.model, 'add', serializer.init_data):
if not request.user.can_access(self.model, 'add', serializer.initial_data):
raise PermissionDenied()
# save the object through the serializer, reload and returned the saved
@@ -424,8 +360,8 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
created = False
parent = self.get_parent_object()
relationship = getattr(parent, self.relationship)
sub_id = request.DATA.get('id', None)
data = request.DATA
sub_id = request.data.get('id', None)
data = request.data
# Create the sub object if an ID is not provided.
if not sub_id:
@@ -462,7 +398,7 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
return Response(status=status.HTTP_204_NO_CONTENT)
def unattach(self, request, *args, **kwargs):
sub_id = request.DATA.get('id', None)
sub_id = request.data.get('id', None)
if not sub_id:
data = dict(msg='"id" is required to disassociate')
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -486,10 +422,10 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
return Response(status=status.HTTP_204_NO_CONTENT)
def post(self, request, *args, **kwargs):
if not isinstance(request.DATA, dict):
if not isinstance(request.data, dict):
return Response('invalid type for post data',
status=status.HTTP_400_BAD_REQUEST)
if 'disassociate' in request.DATA:
if 'disassociate' in request.data:
return self.unattach(request, *args, **kwargs)
else:
return self.attach(request, *args, **kwargs)
@@ -499,9 +435,6 @@ class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
def pre_save(self, obj):
super(RetrieveUpdateAPIView, self).pre_save(obj)
def update(self, request, *args, **kwargs):
self.update_filter(request, *args, **kwargs)
return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs)

139
awx/api/metadata.py Normal file
View File

@@ -0,0 +1,139 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved.
# Django
from django.core.exceptions import PermissionDenied
from django.http import Http404
# Django REST Framework
from rest_framework import exceptions
from rest_framework import metadata
from rest_framework import serializers
from rest_framework.request import clone_request
# Ansible Tower
from awx.main.models import InventorySource
class Metadata(metadata.SimpleMetadata):
def get_field_info(self, field):
field_info = super(Metadata, self).get_field_info(field)
# Indicate if a field has a default value.
# FIXME: Still isn't showing all default values?
try:
field_info['default'] = field.get_default()
except serializers.SkipField:
pass
# Indicate if a field is write-only.
if getattr(field, 'write_only', False):
field_info['write_only'] = True
# Update choices to be a list of 2-tuples instead of list of dicts with
# value/display_name.
if 'choices' in field_info:
choices = []
for choice in field_info['choices']:
if isinstance(choice, dict):
choices.append((choice.get('value'), choice.get('display_name')))
else:
choices.append(choice)
field_info['choices'] = choices
# Special handling of inventory source_region choices that vary based on
# selected inventory source.
if field.field_name == 'source_regions':
for cp in ('azure', 'ec2', 'gce', 'rax'):
get_regions = getattr(InventorySource, 'get_%s_region_choices' % cp)
field_info['%s_region_choices' % cp] = get_regions()
# Special handling of group_by choices for EC2.
if field.field_name == 'group_by':
for cp in ('ec2',):
get_group_by_choices = getattr(InventorySource, 'get_%s_group_by_choices' % cp)
field_info['%s_group_by_choices' % cp] = get_group_by_choices()
# Update type of fields returned...
if field.field_name == 'type':
field_info['type'] = 'multiple choice'
elif field.field_name == 'url':
field_info['type'] = 'string'
elif field.field_name in ('related', 'summary_fields'):
field_info['type'] = 'object'
elif field.field_name in ('created', 'modified'):
field_info['type'] = 'datetime'
return field_info
def determine_actions(self, request, view):
# Add field information for GET requests (so field names/labels are
# available even when we can't POST/PUT).
actions = {}
for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):
view.request = clone_request(request, method)
try:
# Test global permissions
if hasattr(view, 'check_permissions'):
view.check_permissions(view.request)
# Test object permissions
if method == 'PUT' and hasattr(view, 'get_object'):
view.get_object()
except (exceptions.APIException, PermissionDenied, Http404):
continue
else:
# If user has appropriate permissions for the view, include
# appropriate metadata about the fields that should be supplied.
serializer = view.get_serializer()
actions[method] = self.get_serializer_info(serializer)
finally:
view.request = request
for field, meta in actions[method].items():
if not isinstance(meta, dict):
continue
# Add type choices if available from the serializer.
if field == 'type' and hasattr(serializer, 'get_type_choices'):
meta['choices'] = serializer.get_type_choices()
# For GET method, remove meta attributes that aren't relevant
# when reading a field and remove write-only fields.
if method == 'GET':
meta.pop('required', None)
meta.pop('read_only', None)
meta.pop('default', None)
meta.pop('min_length', None)
meta.pop('max_length', None)
if meta.pop('write_only', False):
actions['GET'].pop(field)
# For PUT/POST methods, remove read-only fields.
if method in ('PUT', 'POST'):
if meta.pop('read_only', False):
actions[method].pop(field)
return actions
def determine_metadata(self, request, view):
metadata = super(Metadata, self).determine_metadata(request, view)
# Add version number in which view was added to Tower.
added_in_version = '1.2'
for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
if getattr(view, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version
break
metadata['added_in_version'] = added_in_version
# Add type(s) handled by this view/serializer.
serializer = view.get_serializer()
if hasattr(serializer, 'get_types'):
metadata['types'] = serializer.get_types()
# Add search fields if available from the view.
if getattr(view, 'search_fields', None):
metadata['search_fields'] = view.search_fields
return metadata

View File

@@ -2,36 +2,26 @@
# All Rights Reserved.
# Django REST Framework
from rest_framework import serializers, pagination
from rest_framework.templatetags.rest_framework import replace_query_param
from rest_framework import pagination
from rest_framework.utils.urls import remove_query_param, replace_query_param
class NextPageField(pagination.NextPageField):
'''Pagination field to output URL path.'''
def to_native(self, value):
if not value.has_next():
class Pagination(pagination.PageNumberPagination):
page_size_query_param = 'page_size'
def get_next_link(self):
if not self.page.has_next():
return None
page = value.next_page_number()
request = self.context.get('request')
url = request and request.get_full_path() or ''
return replace_query_param(url, self.page_field, page)
url = self.request and self.request.get_full_path() or ''
page_number = self.page.next_page_number()
return replace_query_param(url, self.page_query_param, page_number)
class PreviousPageField(pagination.NextPageField):
'''Pagination field to output URL path.'''
def to_native(self, value):
if not value.has_previous():
def get_previous_link(self):
if not self.page.has_previous():
return None
page = value.previous_page_number()
request = self.context.get('request')
url = request and request.get_full_path() or ''
return replace_query_param(url, self.page_field, page)
class PaginationSerializer(pagination.BasePaginationSerializer):
'''
Custom pagination serializer to output only URL path (without host/port).
'''
count = serializers.Field(source='paginator.count')
next = NextPageField(source='*')
previous = PreviousPageField(source='*')
url = self.request and self.request.get_full_path() or ''
page_number = self.page.previous_page_number()
if page_number == 1:
return remove_query_param(url, self.page_query_param)
return replace_query_param(url, self.page_query_param, page_number)

View File

@@ -61,7 +61,7 @@ class ModelAccessPermission(permissions.BasePermission):
else:
if obj:
return True
return check_user_access(request.user, view.model, 'add', request.DATA)
return check_user_access(request.user, view.model, 'add', request.data)
def check_put_permissions(self, request, view, obj=None):
if not obj:
@@ -70,10 +70,10 @@ class ModelAccessPermission(permissions.BasePermission):
return True
if getattr(view, 'is_variable_data', False):
return check_user_access(request.user, view.model, 'change', obj,
dict(variables=request.DATA))
dict(variables=request.data))
else:
return check_user_access(request.user, view.model, 'change', obj,
request.DATA)
request.data)
def check_patch_permissions(self, request, view, obj=None):
return self.check_put_permissions(request, view, obj)
@@ -127,7 +127,7 @@ class ModelAccessPermission(permissions.BasePermission):
def has_permission(self, request, view, obj=None):
logger.debug('has_permission(user=%s method=%s data=%r, %s, %r)',
request.user, request.method, request.DATA,
request.user, request.method, request.data,
view.__class__.__name__, obj)
try:
response = self.check_permissions(request, view, obj)
@@ -156,7 +156,7 @@ class JobTemplateCallbackPermission(ModelAccessPermission):
# Require method to be POST, host_config_key to be specified and match
# the requested job template, and require the job template to be
# active in order to proceed.
host_config_key = request.DATA.get('host_config_key', '')
host_config_key = request.data.get('host_config_key', '')
if request.method.lower() != 'post':
raise PermissionDenied()
elif not host_config_key:

View File

@@ -4,6 +4,7 @@
# Django REST Framework
from rest_framework import renderers
class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
'''
Customizations to the default browsable API renderer.
@@ -16,14 +17,16 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
return renderers.JSONRenderer()
return renderer
def get_raw_data_form(self, view, method, request):
def get_raw_data_form(self, data, view, method, request):
# Set a flag on the view to indiciate to the view/serializer that we're
# creating a raw data form for the browsable API.
try:
setattr(view, '_raw_data_form_marker', True)
return super(BrowsableAPIRenderer, self).get_raw_data_form(view, method, request)
return super(BrowsableAPIRenderer, self).get_raw_data_form(data, view, method, request)
finally:
delattr(view, '_raw_data_form_marker')
def get_rendered_html_form(self, view, method, request):
def get_rendered_html_form(self, data, view, method, request):
'''Never show auto-generated form (only raw form).'''
obj = getattr(view, 'object', None)
if not self.show_form_for_method(view, method, request, obj):
@@ -31,9 +34,10 @@ 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)
return context
def get_filter_form(self, data, view, request):
# Don't show filter form in browsable API.
return
class PlainTextRenderer(renderers.BaseRenderer):
@@ -45,9 +49,12 @@ class PlainTextRenderer(renderers.BaseRenderer):
data = unicode(data)
return data.encode(self.charset)
class DownloadTextRenderer(PlainTextRenderer):
format = "txt_download"
class AnsiTextRenderer(PlainTextRenderer):
media_type = 'text/plain'

File diff suppressed because it is too large Load Diff

4
awx/api/test_api.py Normal file
View File

@@ -0,0 +1,4 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved.
from awx.api.tests import * # noqa

View File

@@ -1,7 +1,7 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
from ordereddict import OrderedDict
from collections import OrderedDict
import copy
import functools
@@ -23,21 +23,22 @@ def paginated(method):
def func(self, request, *args, **kwargs):
# Manually spin up pagination.
# How many results do we show?
limit = api_settings.PAGINATE_BY
if request.QUERY_PARAMS.get(api_settings.PAGINATE_BY_PARAM, False):
limit = request.QUERY_PARAMS[api_settings.PAGINATE_BY_PARAM]
if api_settings.MAX_PAGINATE_BY:
limit = min(api_settings.MAX_PAGINATE_BY, limit)
paginator_class = api_settings.DEFAULT_PAGINATION_CLASS
limit = paginator_class.page_size
if request.query_params.get(paginator_class.page_size_query_param, False):
limit = request.query_params[paginator_class.page_size_query_param]
if paginator_class.max_page_size:
limit = min(paginator_class.max_page_size, limit)
limit = int(limit)
# Get the order parameter if it's given
if request.QUERY_PARAMS.get("ordering", False):
ordering = request.QUERY_PARAMS["ordering"]
if request.query_params.get("ordering", False):
ordering = request.query_params["ordering"]
else:
ordering = None
# What page are we on?
page = int(request.QUERY_PARAMS.get('page', 1))
page = int(request.query_params.get('page', 1))
offset = (page - 1) * limit
# Add the limit, offset, page, and order variables to the keyword arguments

View File

@@ -12,6 +12,7 @@ import socket
import sys
import errno
from base64 import b64encode
from collections import OrderedDict
# Django
from django.conf import settings
@@ -21,7 +22,7 @@ from django.core.exceptions import FieldError
from django.db.models import Q, Count
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt
@@ -31,14 +32,16 @@ from django.http import HttpResponse
# Django REST Framework
from rest_framework.exceptions import PermissionDenied, ParseError
from rest_framework.parsers import YAMLParser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.renderers import YAMLRenderer
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import exception_handler
from rest_framework import status
# Django REST Framework YAML
from rest_framework_yaml.parsers import YAMLParser
from rest_framework_yaml.renderers import YAMLRenderer
# MongoEngine
import mongoengine
@@ -71,7 +74,7 @@ from awx.fact.models import * # noqa
from awx.main.utils import emit_websocket_notification
from awx.main.conf import tower_settings
def api_exception_handler(exc):
def api_exception_handler(exc, context):
'''
Override default API exception handler to catch IntegrityError exceptions.
'''
@@ -79,8 +82,7 @@ def api_exception_handler(exc):
exc = ParseError(exc.args[0])
if isinstance(exc, FieldError):
exc = ParseError(exc.args[0])
return exception_handler(exc)
return exception_handler(exc, context)
class ApiRootView(APIView):
@@ -110,7 +112,7 @@ class ApiV1RootView(APIView):
def get(self, request, format=None):
''' list top level resources '''
data = SortedDict()
data = OrderedDict()
data['authtoken'] = reverse('api:auth_token_view')
data['ping'] = reverse('api:api_v1_ping_view')
data['config'] = reverse('api:api_v1_config_view')
@@ -224,20 +226,20 @@ class ApiV1ConfigView(APIView):
def post(self, request):
if not request.user.is_superuser:
return Response(None, status=status.HTTP_404_NOT_FOUND)
if not type(request.DATA) == dict:
if not type(request.data) == dict:
return Response({"error": "Invalid license data"}, status=status.HTTP_400_BAD_REQUEST)
if "eula_accepted" not in request.DATA:
if "eula_accepted" not in request.data:
return Response({"error": "Missing 'eula_accepted' property"}, status=status.HTTP_400_BAD_REQUEST)
try:
eula_accepted = to_python_boolean(request.DATA["eula_accepted"])
eula_accepted = to_python_boolean(request.data["eula_accepted"])
except ValueError:
return Response({"error": "'eula_accepted' value is invalid"}, status=status.HTTP_400_BAD_REQUEST)
if not eula_accepted:
return Response({"error": "'eula_accepted' must be True"}, status=status.HTTP_400_BAD_REQUEST)
request.DATA.pop("eula_accepted")
request.data.pop("eula_accepted")
try:
data_actual = json.dumps(request.DATA)
data_actual = json.dumps(request.data)
except Exception:
# FIX: Log
return Response({"error": "Invalid JSON"}, status=status.HTTP_400_BAD_REQUEST)
@@ -306,7 +308,7 @@ class DashboardView(APIView):
def get(self, request, format=None):
''' Show Dashboard Details '''
data = SortedDict()
data = OrderedDict()
data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view'),
'inventory_graph': reverse('api:dashboard_inventory_graph_view')}
user_inventory = get_user_queryset(request.user, Inventory)
@@ -411,8 +413,8 @@ class DashboardJobsGraphView(APIView):
new_in_200 = True
def get(self, request, format=None):
period = request.QUERY_PARAMS.get('period', 'month')
job_type = request.QUERY_PARAMS.get('job_type', 'all')
period = request.query_params.get('period', 'month')
job_type = request.query_params.get('job_type', 'all')
user_unified_jobs = get_user_queryset(request.user, UnifiedJob)
@@ -460,7 +462,7 @@ class DashboardInventoryGraphView(APIView):
new_in_200 = True
def get(self, request, format=None):
period = request.QUERY_PARAMS.get('period', 'month')
period = request.query_params.get('period', 'month')
end_date = now()
if period == 'month':
@@ -476,7 +478,7 @@ class DashboardInventoryGraphView(APIView):
start_date = start_date.replace(minute=0, second=0, microsecond=0)
delta = dateutil.relativedelta.relativedelta(hours=1)
else:
raise ParseError(u'Unknown period "%s"' % unicode(period))
raise ParseError(u'Unknown period "%s"' % force_text(period))
host_stats = []
date = start_date
@@ -527,7 +529,7 @@ class AuthView(APIView):
new_in_240 = True
def get(self, request):
data = SortedDict()
data = OrderedDict()
err_backend, err_message = request.session.get('social_auth_error', (None, None))
auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS).items()
# Return auth backends in consistent order: Google, GitHub, SAML.
@@ -567,27 +569,27 @@ class AuthTokenView(APIView):
model = AuthToken
def post(self, request):
serializer = self.serializer_class(data=request.DATA)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
request_hash = AuthToken.get_request_hash(self.request)
try:
token = AuthToken.objects.filter(user=serializer.object['user'],
token = AuthToken.objects.filter(user=serializer.validated_data['user'],
request_hash=request_hash,
expires__gt=now(),
reason='')[0]
token.refresh()
except IndexError:
token = AuthToken.objects.create(user=serializer.object['user'],
token = AuthToken.objects.create(user=serializer.validated_data['user'],
request_hash=request_hash)
# Get user un-expired tokens that are not invalidated that are
# over the configured limit.
# Mark them as invalid and inform the user
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.object['user'])
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user'])
for t in invalid_tokens:
# TODO: send socket notification
emit_websocket_notification('/socket.io/control',
'limit_reached',
dict(reason=unicode(AuthToken.reason_long('limit_reached'))),
dict(reason=force_text(AuthToken.reason_long('limit_reached'))),
token_key=t.key)
t.invalidate(reason='limit_reached')
@@ -769,7 +771,7 @@ class ProjectList(ListCreateAPIView):
# Not optimal, but make sure the project status and last_updated fields
# are up to date here...
projects_qs = Project.objects.filter(active=True)
projects_qs = projects_qs.select_related('current_update', 'last_updated')
projects_qs = projects_qs.select_related('current_job', 'last_job')
for project in projects_qs:
project._set_status_and_last_job_run()
return super(ProjectList, self).get(request, *args, **kwargs)
@@ -994,15 +996,15 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
def update_filter(self, request, *args, **kwargs):
''' make sure non-read-only fields that can only be edited by admins, are only edited by admins '''
obj = self.get_object()
can_change = request.user.can_access(User, 'change', obj, request.DATA)
can_admin = request.user.can_access(User, 'admin', obj, request.DATA)
can_change = request.user.can_access(User, 'change', obj, request.data)
can_admin = request.user.can_access(User, 'admin', obj, request.data)
if can_change and not can_admin:
admin_only_edit_fields = ('last_name', 'first_name', 'username',
'is_active', 'is_superuser')
changed = {}
for field in admin_only_edit_fields:
left = getattr(obj, field, None)
right = request.DATA.get(field, None)
right = request.data.get(field, None)
if left is not None and right is not None and left != right:
changed[field] = (left, right)
if changed:
@@ -1061,11 +1063,11 @@ class InventoryScriptDetail(RetrieveUpdateDestroyAPIView):
serializer_class = CustomInventoryScriptSerializer
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
can_delete = request.user.can_access(self.model, 'delete', obj)
instance = self.get_object()
can_delete = request.user.can_access(self.model, 'delete', instance)
if not can_delete:
raise PermissionDenied("Cannot delete inventory script")
for inv_src in InventorySource.objects.filter(source_script=obj):
for inv_src in InventorySource.objects.filter(source_script=instance):
inv_src.source_script = None
inv_src.save()
return super(InventoryScriptDetail, self).destroy(request, *args, **kwargs)
@@ -1137,10 +1139,10 @@ class InventorySingleFactView(MongoAPIView):
raise LicenseForbids('Your license does not permit use '
'of system tracking.')
fact_key = request.QUERY_PARAMS.get("fact_key", None)
fact_value = request.QUERY_PARAMS.get("fact_value", None)
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
module_spec = request.QUERY_PARAMS.get("module", None)
fact_key = request.query_params.get("fact_key", None)
fact_value = request.query_params.get("fact_value", None)
datetime_spec = request.query_params.get("timestamp", None)
module_spec = request.query_params.get("module", None)
if fact_key is None or fact_value is None or module_spec is None:
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
@@ -1231,9 +1233,9 @@ class HostFactVersionsList(MongoListAPIView):
filter_backends = (MongoFilterBackend,)
def get_queryset(self):
from_spec = self.request.QUERY_PARAMS.get('from', None)
to_spec = self.request.QUERY_PARAMS.get('to', None)
module_spec = self.request.QUERY_PARAMS.get('module', None)
from_spec = self.request.query_params.get('from', None)
to_spec = self.request.query_params.get('to', None)
module_spec = self.request.query_params.get('module', None)
if not feature_enabled("system_tracking"):
raise LicenseForbids("Your license does not permit use "
@@ -1285,10 +1287,10 @@ class HostSingleFactView(MongoAPIView):
raise LicenseForbids('Your license does not permit use '
'of system tracking.')
fact_key = request.QUERY_PARAMS.get("fact_key", None)
fact_value = request.QUERY_PARAMS.get("fact_value", None)
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
module_spec = request.QUERY_PARAMS.get("module", None)
fact_key = request.query_params.get("fact_key", None)
fact_value = request.query_params.get("fact_value", None)
datetime_spec = request.query_params.get("timestamp", None)
module_spec = request.query_params.get("module", None)
if fact_key is None or fact_value is None or module_spec is None:
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
@@ -1310,8 +1312,8 @@ class HostFactCompareView(MongoAPIView):
raise LicenseForbids('Your license does not permit use '
'of system tracking.')
datetime_spec = request.QUERY_PARAMS.get('datetime', None)
module_spec = request.QUERY_PARAMS.get('module', "ansible")
datetime_spec = request.query_params.get('datetime', None)
module_spec = request.query_params.get('module', "ansible")
datetime_actual = dateutil.parser.parse(datetime_spec) if datetime_spec is not None else now()
host_obj = self.get_parent_object()
@@ -1333,7 +1335,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
relationship = 'children'
def unattach(self, request, *args, **kwargs):
sub_id = request.DATA.get('id', None)
sub_id = request.data.get('id', None)
if sub_id is not None:
return super(GroupChildrenList, self).unattach(request, *args, **kwargs)
parent = self.get_parent_object()
@@ -1345,7 +1347,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
Special case for disassociating a child group from the parent. If the
child group has no more parents, then automatically mark it inactive.
'''
sub_id = request.DATA.get('id', None)
sub_id = request.data.get('id', None)
if not sub_id:
data = dict(msg='"id" is required to disassociate')
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -1394,12 +1396,12 @@ class GroupHostsList(SubListCreateAttachDetachAPIView):
def create(self, request, *args, **kwargs):
parent_group = Group.objects.get(id=self.kwargs['pk'])
existing_hosts = Host.objects.filter(inventory=parent_group.inventory, name=request.DATA['name'])
if existing_hosts.count() > 0 and ('variables' not in request.DATA or
request.DATA['variables'] == '' or
request.DATA['variables'] == '{}' or
request.DATA['variables'] == '---'):
request.DATA['id'] = existing_hosts[0].id
existing_hosts = Host.objects.filter(inventory=parent_group.inventory, name=request.data['name'])
if existing_hosts.count() > 0 and ('variables' not in request.data or
request.data['variables'] == '' or
request.data['variables'] == '{}' or
request.data['variables'] == '---'):
request.data['id'] = existing_hosts[0].id
return self.attach(request, *args, **kwargs)
return super(GroupHostsList, self).create(request, *args, **kwargs)
@@ -1483,10 +1485,10 @@ class GroupSingleFactView(MongoAPIView):
raise LicenseForbids('Your license does not permit use '
'of system tracking.')
fact_key = request.QUERY_PARAMS.get("fact_key", None)
fact_value = request.QUERY_PARAMS.get("fact_value", None)
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
module_spec = request.QUERY_PARAMS.get("module", None)
fact_key = request.query_params.get("fact_key", None)
fact_value = request.query_params.get("fact_value", None)
datetime_spec = request.query_params.get("timestamp", None)
module_spec = request.query_params.get("module", None)
if fact_key is None or fact_value is None or module_spec is None:
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
@@ -1547,33 +1549,33 @@ class InventoryScriptView(RetrieveAPIView):
filter_backends = ()
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
hostname = request.QUERY_PARAMS.get('host', '')
hostvars = bool(request.QUERY_PARAMS.get('hostvars', ''))
show_all = bool(request.QUERY_PARAMS.get('all', ''))
obj = self.get_object()
hostname = request.query_params.get('host', '')
hostvars = bool(request.query_params.get('hostvars', ''))
show_all = bool(request.query_params.get('all', ''))
if show_all:
hosts_q = dict(active=True)
else:
hosts_q = dict(active=True, enabled=True)
if hostname:
host = get_object_or_404(self.object.hosts, name=hostname, **hosts_q)
host = get_object_or_404(obj.hosts, name=hostname, **hosts_q)
data = host.variables_dict
else:
data = SortedDict()
if self.object.variables_dict:
all_group = data.setdefault('all', SortedDict())
all_group['vars'] = self.object.variables_dict
data = OrderedDict()
if obj.variables_dict:
all_group = data.setdefault('all', OrderedDict())
all_group['vars'] = obj.variables_dict
# Add hosts without a group to the all group.
groupless_hosts_qs = self.object.hosts.filter(groups__isnull=True, **hosts_q).order_by('name')
groupless_hosts_qs = obj.hosts.filter(groups__isnull=True, **hosts_q).order_by('name')
groupless_hosts = list(groupless_hosts_qs.values_list('name', flat=True))
if groupless_hosts:
all_group = data.setdefault('all', SortedDict())
all_group = data.setdefault('all', OrderedDict())
all_group['hosts'] = groupless_hosts
# 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)
group_hosts_kw = dict(group__inventory_id=obj.id, group__active=True,
host__inventory_id=obj.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)
@@ -1586,8 +1588,8 @@ class InventoryScriptView(RetrieveAPIView):
# 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,
from_group__inventory_id=obj.id, from_group__active=True,
to_group__inventory_id=obj.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')
@@ -1597,28 +1599,27 @@ class InventoryScriptView(RetrieveAPIView):
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):
group_info = SortedDict()
for group in obj.groups.filter(active=True):
group_info = OrderedDict()
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
if hostvars:
data.setdefault('_meta', SortedDict())
data['_meta'].setdefault('hostvars', SortedDict())
for host in self.object.hosts.filter(**hosts_q):
data.setdefault('_meta', OrderedDict())
data['_meta'].setdefault('hostvars', OrderedDict())
for host in obj.hosts.filter(**hosts_q):
data['_meta']['hostvars'][host.name] = host.variables_dict
# workaround for Ansible inventory bug (github #3687), localhost
# must be explicitly listed in the all group for dynamic inventory
# scripts to pick it up.
localhost_names = ('localhost', '127.0.0.1', '::1')
localhosts_qs = self.object.hosts.filter(name__in=localhost_names,
**hosts_q)
localhosts_qs = obj.hosts.filter(name__in=localhost_names, **hosts_q)
localhosts = list(localhosts_qs.values_list('name', flat=True))
if localhosts:
all_group = data.setdefault('all', SortedDict())
all_group = data.setdefault('all', OrderedDict())
all_group_hosts = all_group.get('hosts', [])
all_group_hosts.extend(localhosts)
all_group['hosts'] = sorted(set(all_group_hosts))
@@ -1657,13 +1658,6 @@ class InventoryTreeView(RetrieveAPIView):
group_children_map)
return Response(tree_data)
def get_description_context(self):
d = super(InventoryTreeView, self).get_description_context()
d.update({
'serializer_fields': GroupTreeSerializer().metadata(),
})
return d
class InventoryInventorySourcesList(SubListAPIView):
model = InventorySource
@@ -1828,23 +1822,23 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
if 'credential' not in request.DATA and 'credential_id' in request.DATA:
request.DATA['credential'] = request.DATA['credential_id']
if 'credential' not in request.data and 'credential_id' in request.data:
request.data['credential'] = request.data['credential_id']
passwords = {}
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA, 'passwords': passwords})
serializer = self.serializer_class(instance=obj, data=request.data, context={'obj': obj, 'data': request.data, 'passwords': passwords})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# At this point, a credential is gauranteed to exist at serializer.object.credential
if not request.user.can_access(Credential, 'read', serializer.object.credential):
# At this point, a credential is gauranteed to exist at serializer.instance.credential
if not request.user.can_access(Credential, 'read', serializer.instance.credential):
raise PermissionDenied()
kv = {
'credential': serializer.object.credential.pk,
'credential': serializer.instance.credential.pk,
}
if 'extra_vars' in request.DATA:
kv['extra_vars'] = request.DATA['extra_vars']
if 'extra_vars' in request.data:
kv['extra_vars'] = request.data['extra_vars']
kv.update(passwords)
new_job = obj.create_unified_job(**kv)
@@ -1892,7 +1886,7 @@ class JobTemplateSurveySpec(GenericAPIView):
if not request.user.can_access(self.model, 'change', obj, None):
raise PermissionDenied()
try:
obj.survey_spec = json.dumps(request.DATA)
obj.survey_spec = json.dumps(request.data)
except ValueError:
# TODO: Log
return Response(dict(error="Invalid JSON when parsing survey spec"), status=status.HTTP_400_BAD_REQUEST)
@@ -2040,7 +2034,7 @@ class JobTemplateCallback(GenericAPIView):
def post(self, request, *args, **kwargs):
extra_vars = None
if request.content_type == "application/json":
extra_vars = request.DATA.get("extra_vars", None)
extra_vars = request.data.get("extra_vars", None)
# Permission class should have already validated host_config_key.
job_template = self.get_object()
# Attempt to find matching hosts based on remote address.
@@ -2144,8 +2138,8 @@ class SystemJobTemplateLaunch(GenericAPIView):
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
new_job = obj.create_unified_job(**request.DATA)
new_job.signal_start(**request.DATA)
new_job = obj.create_unified_job(**request.data)
new_job.signal_start(**request.data)
data = dict(system_job=new_job.id)
return Response(data, status=status.HTTP_202_ACCEPTED)
@@ -2223,7 +2217,7 @@ class JobStart(GenericAPIView):
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
if obj.can_start:
result = obj.signal_start(**request.DATA)
result = obj.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=obj.passwords_needed_to_start)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -2262,15 +2256,15 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView):
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
# Note: is_valid() may modify request.DATA
# Note: is_valid() may modify request.data
# It will remove any key/value pair who's key is not in the 'passwords_needed_to_start' list
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA})
serializer = self.serializer_class(data=request.data, context={'obj': obj, 'data': request.data})
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
obj.launch_type = 'relaunch'
new_job = obj.copy()
result = new_job.signal_start(**request.DATA)
result = new_job.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=new_job.passwords_needed_to_start)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -2357,13 +2351,11 @@ class JobJobEventsList(BaseJobEventsList):
# Post allowed for job event callback only.
def post(self, request, *args, **kwargs):
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
data = request.DATA.copy()
data = request.data.copy()
data['job'] = parent_obj.pk
serializer = self.get_serializer(data=data)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
self.instance = serializer.save()
headers = {'Location': serializer.data['url']}
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
@@ -2392,16 +2384,16 @@ class JobJobPlaysList(BaseJobEventsList):
# doing this here for the moment until/unless we need to implement more
# complex filtering (since we aren't under a serializer)
if "id__in" in request.QUERY_PARAMS:
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.QUERY_PARAMS["id__in"].split(",")])
elif "id__gt" in request.QUERY_PARAMS:
qs = qs.filter(id__gt=request.QUERY_PARAMS['id__gt'])
elif "id__lt" in request.QUERY_PARAMS:
qs = qs.filter(id__lt=request.QUERY_PARAMS['id__lt'])
if "failed" in request.QUERY_PARAMS:
qs = qs.filter(failed=(request.QUERY_PARAMS['failed'].lower() == 'true'))
if "play__icontains" in request.QUERY_PARAMS:
qs = qs.filter(play__icontains=request.QUERY_PARAMS['play__icontains'])
if "id__in" in request.query_params:
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.query_params["id__in"].split(",")])
elif "id__gt" in request.query_params:
qs = qs.filter(id__gt=request.query_params['id__gt'])
elif "id__lt" in request.query_params:
qs = qs.filter(id__lt=request.query_params['id__lt'])
if "failed" in request.query_params:
qs = qs.filter(failed=(request.query_params['failed'].lower() == 'true'))
if "play__icontains" in request.query_params:
qs = qs.filter(play__icontains=request.query_params['play__icontains'])
count = qs.count()
@@ -2465,10 +2457,10 @@ class JobJobTasksList(BaseJobEventsList):
return ({'detail': 'job not found'}, -1, status.HTTP_404_NOT_FOUND)
job = job[0]
if 'event_id' not in request.QUERY_PARAMS:
if 'event_id' not in request.query_params:
return ({'detail': '"event_id" not provided'}, -1, status.HTTP_400_BAD_REQUEST)
parent_task = job.job_events.filter(pk=int(request.QUERY_PARAMS.get('event_id', -1)))
parent_task = job.job_events.filter(pk=int(request.query_params.get('event_id', -1)))
if not parent_task.exists():
return ({'detail': 'parent event not found'}, -1, status.HTTP_404_NOT_FOUND)
parent_task = parent_task[0]
@@ -2507,16 +2499,16 @@ class JobJobTasksList(BaseJobEventsList):
# doing this here for the moment until/unless we need to implement more
# complex filtering (since we aren't under a serializer)
if "id__in" in request.QUERY_PARAMS:
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.QUERY_PARAMS["id__in"].split(",")])
elif "id__gt" in request.QUERY_PARAMS:
qs = qs.filter(id__gt=request.QUERY_PARAMS['id__gt'])
elif "id__lt" in request.QUERY_PARAMS:
qs = qs.filter(id__lt=request.QUERY_PARAMS['id__lt'])
if "failed" in request.QUERY_PARAMS:
qs = qs.filter(failed=(request.QUERY_PARAMS['failed'].lower() == 'true'))
if "task__icontains" in request.QUERY_PARAMS:
qs = qs.filter(task__icontains=request.QUERY_PARAMS['task__icontains'])
if "id__in" in request.query_params:
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.query_params["id__in"].split(",")])
elif "id__gt" in request.query_params:
qs = qs.filter(id__gt=request.query_params['id__gt'])
elif "id__lt" in request.query_params:
qs = qs.filter(id__lt=request.query_params['id__lt'])
if "failed" in request.query_params:
qs = qs.filter(failed=(request.query_params['failed'].lower() == 'true'))
if "task__icontains" in request.query_params:
qs = qs.filter(task__icontains=request.query_params['task__icontains'])
if ordering is not None:
qs = qs.order_by(ordering)
@@ -2594,7 +2586,7 @@ class AdHocCommandList(ListCreateAPIView):
def create(self, request, *args, **kwargs):
# Inject inventory ID and limit if parent objects is a host/group.
if hasattr(self, 'get_parent_object') and not getattr(self, 'parent_key', None):
data = request.DATA
data = request.data
# HACK: Make request data mutable.
if getattr(data, '_mutable', None) is False:
data._mutable = True
@@ -2604,11 +2596,11 @@ class AdHocCommandList(ListCreateAPIView):
data['limit'] = parent_obj.name
# Check for passwords needed before creating ad hoc command.
credential_pk = get_pk_from_dict(request.DATA, 'credential')
credential_pk = get_pk_from_dict(request.data, 'credential')
if credential_pk:
credential = get_object_or_400(Credential, pk=credential_pk)
needed = credential.passwords_needed
provided = dict([(field, request.DATA.get(field, '')) for field in needed])
provided = dict([(field, request.data.get(field, '')) for field in needed])
if not all(provided.values()):
data = dict(passwords_needed_to_start=needed)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -2619,7 +2611,7 @@ class AdHocCommandList(ListCreateAPIView):
# Start ad hoc command running when created.
ad_hoc_command = get_object_or_400(self.model, pk=response.data['id'])
result = ad_hoc_command.signal_start(**request.DATA)
result = ad_hoc_command.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=ad_hoc_command.passwords_needed_to_start)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -2702,21 +2694,21 @@ class AdHocCommandRelaunch(GenericAPIView):
data[field[:-3]] = getattr(obj, field)
else:
data[field] = getattr(obj, field)
serializer = self.get_serializer(data=data)
serializer = AdHocCommandSerializer(data=data, context=self.get_serializer_context())
if not serializer.is_valid():
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
# Check for passwords needed before copying ad hoc command.
needed = obj.passwords_needed_to_start
provided = dict([(field, request.DATA.get(field, '')) for field in needed])
provided = dict([(field, request.data.get(field, '')) for field in needed])
if not all(provided.values()):
data = dict(passwords_needed_to_start=needed)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
# Copy and start the new ad hoc command.
new_ad_hoc_command = obj.copy()
result = new_ad_hoc_command.signal_start(**request.DATA)
result = new_ad_hoc_command.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=new_ad_hoc_command.passwords_needed_to_start)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
@@ -2773,13 +2765,11 @@ class AdHocCommandAdHocCommandEventsList(BaseAdHocCommandEventsList):
if request.user:
raise PermissionDenied()
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
data = request.DATA.copy()
data['ad_hoc_command'] = parent_obj.pk
data = request.data.copy()
data['ad_hoc_command'] = parent_obj
serializer = self.get_serializer(data=data)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
self.instance = serializer.save()
headers = {'Location': serializer.data['url']}
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
@@ -2870,11 +2860,11 @@ class UnifiedJobStdout(RetrieveAPIView):
return Response(response_message)
if request.accepted_renderer.format in ('html', 'api', 'json'):
content_format = request.QUERY_PARAMS.get('content_format', 'html')
content_encoding = request.QUERY_PARAMS.get('content_encoding', None)
start_line = request.QUERY_PARAMS.get('start_line', 0)
end_line = request.QUERY_PARAMS.get('end_line', None)
dark_val = request.QUERY_PARAMS.get('dark', '')
content_format = request.query_params.get('content_format', 'html')
content_encoding = request.query_params.get('content_encoding', None)
start_line = request.query_params.get('start_line', 0)
end_line = request.query_params.get('end_line', None)
dark_val = request.query_params.get('dark', '')
dark = bool(dark_val and dark_val[0].lower() in ('1', 't', 'y'))
content_only = bool(request.accepted_renderer.format in ('api', 'json'))
dark_bg = (content_only and dark) or (not content_only and (dark or not dark_val))
@@ -2973,7 +2963,7 @@ class SettingsList(ListCreateAPIView):
def get_queryset(self):
class SettingsIntermediary(object):
def __init__(self, key, description, category, value,
value_type, user):
value_type, user=None):
self.key = key
self.description = description
self.category = category
@@ -3004,8 +2994,7 @@ class SettingsList(ListCreateAPIView):
m_entry['description'],
m_entry['category'],
m_entry['default'],
m_entry['type'],
None))
m_entry['type']))
return settings_actual
def delete(self, request, *args, **kwargs):
@@ -3023,7 +3012,7 @@ class SettingsReset(APIView):
# NOTE: Extend more with user settings
if not request.user.can_access(TowerSettings, 'delete', None):
raise PermissionDenied()
settings_key = request.DATA.get('key', None)
settings_key = request.data.get('key', None)
if settings_key is not None:
TowerSettings.objects.filter(key=settings_key).delete()
return Response(status=status.HTTP_204_NO_CONTENT)