mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 10:27:37 -02:30
Moved API code into separate Django app.
This commit is contained in:
@@ -15,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
from awx.main.compat import format_html
|
||||
from awx.lib.compat import format_html
|
||||
from awx.main.models import *
|
||||
from awx.main.forms import *
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import authentication
|
||||
from rest_framework import exceptions
|
||||
|
||||
# AWX
|
||||
from awx.main.models import Job, AuthToken
|
||||
|
||||
class TokenAuthentication(authentication.TokenAuthentication):
|
||||
'''
|
||||
Custom token authentication using tokens that expire and are associated
|
||||
with parameters specific to the request.
|
||||
'''
|
||||
|
||||
model = AuthToken
|
||||
|
||||
def authenticate(self, request):
|
||||
self.request = request
|
||||
return super(TokenAuthentication, self).authenticate(request)
|
||||
|
||||
def authenticate_credentials(self, key):
|
||||
try:
|
||||
request_hash = self.model.get_request_hash(self.request)
|
||||
token = self.model.objects.get(key=key, request_hash=request_hash)
|
||||
except self.model.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed('Invalid token')
|
||||
|
||||
if token.expired:
|
||||
raise exceptions.AuthenticationFailed('Token is expired')
|
||||
|
||||
if not token.user.is_active:
|
||||
raise exceptions.AuthenticationFailed('User inactive or deleted')
|
||||
|
||||
token.refresh()
|
||||
|
||||
return (token.user, token)
|
||||
|
||||
class JobTaskAuthentication(authentication.BaseAuthentication):
|
||||
'''
|
||||
Custom authentication used for views accessed by the inventory and callback
|
||||
scripts when running a job.
|
||||
'''
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = authentication.get_authorization_header(request).split()
|
||||
if len(auth) != 2 or auth[0].lower() != 'token' or '-' not in auth[1]:
|
||||
return None
|
||||
job_id, job_key = auth[1].split('-', 1)
|
||||
try:
|
||||
job = Job.objects.get(pk=job_id, status='running')
|
||||
except Job.DoesNotExist:
|
||||
return None
|
||||
token = job.task_auth_token
|
||||
if auth[1] != token:
|
||||
raise exceptions.AuthenticationFailed('Invalid job task token')
|
||||
return (None, token)
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return 'Token'
|
||||
@@ -1,411 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import inspect
|
||||
import json
|
||||
|
||||
# Django
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
|
||||
# Django REST Framework
|
||||
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
|
||||
|
||||
# AWX
|
||||
from awx.main.models import *
|
||||
from awx.main.utils import *
|
||||
|
||||
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
||||
|
||||
__all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'ListCreateAPIView',
|
||||
'SubListAPIView', 'SubListCreateAPIView', 'RetrieveAPIView',
|
||||
'RetrieveUpdateAPIView', 'RetrieveUpdateDestroyAPIView']
|
||||
|
||||
def get_view_name(cls, suffix=None):
|
||||
'''
|
||||
Wrapper around REST framework get_view_name() to support get_name() method
|
||||
and view_name property on a view class.
|
||||
'''
|
||||
name = ''
|
||||
if hasattr(cls, 'get_name') and callable(cls.get_name):
|
||||
name = cls().get_name()
|
||||
elif hasattr(cls, 'view_name'):
|
||||
if callable(cls.view_name):
|
||||
name = cls.view_name()
|
||||
else:
|
||||
name = cls.view_name
|
||||
if name:
|
||||
return ('%s %s' % (name, suffix)) if suffix else name
|
||||
return views.get_view_name(cls, suffix=None)
|
||||
|
||||
def get_view_description(cls, html=False):
|
||||
'''
|
||||
Wrapper around REST framework get_view_description() to support
|
||||
get_description() method and view_description property on a view class.
|
||||
'''
|
||||
if hasattr(cls, 'get_description') and callable(cls.get_description):
|
||||
desc = cls().get_description(html=html)
|
||||
cls = type(cls.__name__, (object,), {'__doc__': desc})
|
||||
elif hasattr(cls, 'view_description'):
|
||||
if callable(cls.view_description):
|
||||
view_desc = cls.view_description()
|
||||
else:
|
||||
view_desc = cls.view_description
|
||||
cls = type(cls.__name__, (object,), {'__doc__': view_desc})
|
||||
desc = views.get_view_description(cls, html=html)
|
||||
if html:
|
||||
desc = '<div class="description">%s</div>' % desc
|
||||
return mark_safe(desc)
|
||||
|
||||
class APIView(views.APIView):
|
||||
|
||||
def get_authenticate_header(self, request):
|
||||
"""
|
||||
Determine the WWW-Authenticate header to use for 401 responses. Try to
|
||||
use the request header as an indication for which authentication method
|
||||
was attempted.
|
||||
"""
|
||||
for authenticator in self.get_authenticators():
|
||||
resp_hdr = authenticator.authenticate_header(request)
|
||||
if not resp_hdr:
|
||||
continue
|
||||
req_hdr = get_authorization_header(request)
|
||||
if not req_hdr:
|
||||
continue
|
||||
if resp_hdr.split()[0] and resp_hdr.split()[0] == req_hdr.split()[0]:
|
||||
return resp_hdr
|
||||
return super(APIView, self).get_authenticate_header(request)
|
||||
|
||||
def get_description_context(self):
|
||||
return {
|
||||
'docstring': type(self).__doc__ or '',
|
||||
'new_in_13': getattr(self, 'new_in_13', False),
|
||||
'new_in_14': getattr(self, 'new_in_14', False),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
template_list = []
|
||||
for klass in inspect.getmro(type(self)):
|
||||
template_basename = camelcase_to_underscore(klass.__name__)
|
||||
template_list.append('main/%s.md' % template_basename)
|
||||
context = self.get_description_context()
|
||||
return render_to_string(template_list, context)
|
||||
|
||||
class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
# Base class for all model-based views.
|
||||
|
||||
# Subclasses should define:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
|
||||
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()
|
||||
|
||||
def get_description_context(self):
|
||||
# Set instance attributes needed to get serializer metadata.
|
||||
if not hasattr(self, 'request'):
|
||||
self.request = None
|
||||
if not hasattr(self, 'format_kwarg'):
|
||||
self.format_kwarg = 'format'
|
||||
d = super(GenericAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'model_verbose_name': unicode(self.model._meta.verbose_name),
|
||||
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
||||
'serializer_fields': self.get_serializer().metadata(),
|
||||
})
|
||||
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.get('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 actions:
|
||||
ret['actions'] = actions
|
||||
if getattr(self, 'search_fields', None):
|
||||
ret['search_fields'] = self.search_fields
|
||||
return ret
|
||||
|
||||
class ListAPIView(generics.ListAPIView, GenericAPIView):
|
||||
# Base class for a read-only list view.
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.user.get_queryset(self.model)
|
||||
|
||||
def get_description_context(self):
|
||||
opts = self.model._meta
|
||||
if 'username' in opts.get_all_field_names():
|
||||
order_field = 'username'
|
||||
else:
|
||||
order_field = 'name'
|
||||
d = super(ListAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'order_field': order_field,
|
||||
})
|
||||
return d
|
||||
|
||||
@property
|
||||
def search_fields(self):
|
||||
fields = []
|
||||
for field in self.model._meta.fields:
|
||||
if field.name in ('username', 'first_name', 'last_name', 'email',
|
||||
'name', 'description', 'email'):
|
||||
fields.append(field.name)
|
||||
return fields
|
||||
|
||||
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
||||
# Base class for a list view that allows creating new objects.
|
||||
|
||||
def pre_save(self, obj):
|
||||
super(ListCreateAPIView, self).pre_save(obj)
|
||||
if isinstance(obj, PrimordialModel):
|
||||
obj.created_by = self.request.user
|
||||
|
||||
class SubListAPIView(ListAPIView):
|
||||
# Base class for a read-only sublist view.
|
||||
|
||||
# Subclasses should define at least:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
# parent_model = ModelClass
|
||||
# relationship = 'rel_name_from_parent_to_model'
|
||||
# And optionally (user must have given access permission on parent object
|
||||
# to view sublist):
|
||||
# parent_access = 'read'
|
||||
|
||||
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),
|
||||
})
|
||||
return d
|
||||
|
||||
def get_parent_object(self):
|
||||
parent_filter = {
|
||||
self.lookup_field: self.kwargs.get(self.lookup_field, None),
|
||||
}
|
||||
return get_object_or_404(self.parent_model, **parent_filter)
|
||||
|
||||
def check_parent_access(self, parent=None):
|
||||
parent = parent or self.get_parent_object()
|
||||
parent_access = getattr(self, 'parent_access', 'read')
|
||||
if parent_access in ('read', 'delete'):
|
||||
args = (self.parent_model, parent_access, parent)
|
||||
else:
|
||||
args = (self.parent_model, parent_access, parent, None)
|
||||
if not self.request.user.can_access(*args):
|
||||
raise PermissionDenied()
|
||||
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
self.check_parent_access(parent)
|
||||
qs = self.request.user.get_queryset(self.model).distinct()
|
||||
sublist_qs = getattr(parent, self.relationship).distinct()
|
||||
return qs & sublist_qs
|
||||
|
||||
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
# Base class for a sublist view that allows for creating subobjects and
|
||||
# attaching/detaching them from the parent.
|
||||
|
||||
# In addition to SubListAPIView properties, subclasses may define (if the
|
||||
# sub_obj requires a foreign key to the parent):
|
||||
# parent_key = 'field_on_model_referring_to_parent'
|
||||
|
||||
def get_description_context(self):
|
||||
d = super(SubListCreateAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'parent_key': getattr(self, 'parent_key', None),
|
||||
})
|
||||
return d
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# If the object ID was not specified, it probably doesn't exist in the
|
||||
# DB yet. We want to see if we can create it. The URL may choose to
|
||||
# inject it's primary key into the object because we are posting to a
|
||||
# subcollection. Use all the normal access control mechanisms.
|
||||
|
||||
# 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()
|
||||
else:
|
||||
data = request.DATA
|
||||
|
||||
# add the parent key to the post data using the pk from the URL
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
if parent_key:
|
||||
data[parent_key] = self.kwargs['pk']
|
||||
|
||||
# attempt to deserialize the object
|
||||
serializer = self.serializer_class(data=data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
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):
|
||||
raise PermissionDenied()
|
||||
|
||||
# save the object through the serializer, reload and returned the saved
|
||||
# object deserialized
|
||||
obj = serializer.save()
|
||||
serializer = self.serializer_class(obj)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def attach(self, request, *args, **kwargs):
|
||||
created = False
|
||||
parent = self.get_parent_object()
|
||||
relationship = getattr(parent, self.relationship)
|
||||
sub_id = request.DATA.get('id', None)
|
||||
data = request.DATA
|
||||
|
||||
# Create the sub object if an ID is not provided.
|
||||
if not sub_id:
|
||||
response = self.create(request, *args, **kwargs)
|
||||
if response.status_code != status.HTTP_201_CREATED:
|
||||
return response
|
||||
sub_id = response.data['id']
|
||||
data = response.data
|
||||
try:
|
||||
location = response['Location']
|
||||
except KeyError:
|
||||
location = None
|
||||
created = True
|
||||
|
||||
# Retrive the sub object (whether created or by ID).
|
||||
sub = get_object_or_400(self.model, pk=sub_id)
|
||||
|
||||
# Verify we have permission to attach.
|
||||
if not request.user.can_access(self.parent_model, 'attach', parent, sub,
|
||||
self.relationship, data,
|
||||
skip_sub_obj_read_check=created):
|
||||
raise PermissionDenied()
|
||||
|
||||
# Attach the object to the collection.
|
||||
if sub not in relationship.all():
|
||||
relationship.add(sub)
|
||||
|
||||
if created:
|
||||
headers = {}
|
||||
if location:
|
||||
headers['Location'] = location
|
||||
return Response(data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
else:
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unattach(self, request, *args, **kwargs):
|
||||
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)
|
||||
|
||||
parent = self.get_parent_object()
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
relationship = getattr(parent, self.relationship)
|
||||
sub = get_object_or_400(self.model, pk=sub_id)
|
||||
|
||||
if not request.user.can_access(self.parent_model, 'unattach', parent,
|
||||
sub, self.relationship):
|
||||
raise PermissionDenied()
|
||||
|
||||
if parent_key:
|
||||
# sub object has a ForeignKey to the parent, so we can't remove it
|
||||
# from the set, only mark it as inactive.
|
||||
sub.mark_inactive()
|
||||
else:
|
||||
relationship.remove(sub)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not isinstance(request.DATA, dict):
|
||||
return Response('invalid type for post data',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'disassociate' in request.DATA:
|
||||
return self.unattach(request, *args, **kwargs)
|
||||
else:
|
||||
return self.attach(request, *args, **kwargs)
|
||||
|
||||
class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
|
||||
pass
|
||||
|
||||
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
||||
|
||||
def pre_save(self, obj):
|
||||
super(RetrieveUpdateAPIView, self).pre_save(obj)
|
||||
if isinstance(obj, PrimordialModel):
|
||||
obj.created_by = self.request.user
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
self.update_filter(request, *args, **kwargs)
|
||||
return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs)
|
||||
|
||||
def update_filter(self, request, *args, **kwargs):
|
||||
''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering '''
|
||||
pass
|
||||
|
||||
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
# somewhat lame that delete has to call it's own permissions check
|
||||
obj = self.get_object()
|
||||
# FIXME: Why isn't the active check being caught earlier by RBAC?
|
||||
if getattr(obj, 'active', True) == False:
|
||||
raise Http404()
|
||||
if getattr(obj, 'is_active', True) == False:
|
||||
raise Http404()
|
||||
if not request.user.can_access(self.model, 'delete', obj):
|
||||
raise PermissionDenied()
|
||||
if hasattr(obj, 'mark_inactive'):
|
||||
obj.mark_inactive()
|
||||
else:
|
||||
raise NotImplementedError('destroy() not implemented yet for %s' % obj)
|
||||
return HttpResponse(status=204)
|
||||
@@ -1,31 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
'''
|
||||
Compability library for support of both Django 1.4.x and Django 1.5.x.
|
||||
'''
|
||||
|
||||
try:
|
||||
from django.utils.html import format_html
|
||||
except ImportError:
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import mark_safe
|
||||
def format_html(format_string, *args, **kwargs):
|
||||
args_safe = map(conditional_escape, args)
|
||||
kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
|
||||
kwargs.items()])
|
||||
return mark_safe(format_string.format(*args_safe, **kwargs_safe))
|
||||
|
||||
try:
|
||||
from django.utils.log import RequireDebugTrue
|
||||
except ImportError:
|
||||
import logging
|
||||
from django.conf import settings
|
||||
class RequireDebugTrue(logging.Filter):
|
||||
def filter(self, record):
|
||||
return settings.DEBUG
|
||||
|
||||
try:
|
||||
from django.utils.text import slugify
|
||||
except ImportError:
|
||||
from django.template.defaultfilters import slugify
|
||||
@@ -1,196 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import re
|
||||
|
||||
# Django
|
||||
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
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
class ActiveOnlyBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter to show only objects where is_active/active is True.
|
||||
'''
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
for field in queryset.model._meta.fields:
|
||||
if field.name == 'is_active':
|
||||
queryset = queryset.filter(is_active=True)
|
||||
elif field.name == 'active':
|
||||
queryset = queryset.filter(active=True)
|
||||
return queryset
|
||||
|
||||
class FieldLookupBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter using field lookups provided via query string parameters.
|
||||
'''
|
||||
|
||||
RESERVED_NAMES = ('page', 'page_size', 'format', 'order', 'order_by',
|
||||
'search')
|
||||
|
||||
SUPPORTED_LOOKUPS = ('exact', 'iexact', 'contains', 'icontains',
|
||||
'startswith', 'istartswith', 'endswith', 'iendswith',
|
||||
'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||
'isnull')
|
||||
|
||||
def get_field_from_lookup(self, model, lookup):
|
||||
field = None
|
||||
parts = lookup.split('__')
|
||||
if parts and parts[-1] not in self.SUPPORTED_LOOKUPS:
|
||||
parts.append('exact')
|
||||
# FIXME: Could build up a list of models used across relationships, use
|
||||
# those lookups combined with request.user.get_queryset(Model) to make
|
||||
# sure user cannot query using objects he could not view.
|
||||
for n, name in enumerate(parts[:-1]):
|
||||
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
|
||||
return field
|
||||
|
||||
def to_python_boolean(self, value, allow_none=False):
|
||||
value = unicode(value)
|
||||
if value.lower() in ('true', '1'):
|
||||
return True
|
||||
elif value.lower() in ('false', '0'):
|
||||
return False
|
||||
elif allow_none and value.lower() in ('none', 'null'):
|
||||
return None
|
||||
else:
|
||||
raise ValueError(u'Unable to convert "%s" to boolean' % unicode(value))
|
||||
|
||||
def to_python_related(self, value):
|
||||
value = unicode(value)
|
||||
if value.lower() in ('none', 'null'):
|
||||
return None
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
def value_to_python_for_field(self, field, value):
|
||||
if isinstance(field, models.NullBooleanField):
|
||||
return self.to_python_boolean(value, allow_none=True)
|
||||
elif isinstance(field, models.BooleanField):
|
||||
return self.to_python_boolean(value)
|
||||
elif isinstance(field, RelatedObject):
|
||||
return self.to_python_related(value)
|
||||
else:
|
||||
return field.to_python(value)
|
||||
|
||||
def value_to_python(self, model, lookup, value):
|
||||
field = self.get_field_from_lookup(model, lookup)
|
||||
if lookup.endswith('__isnull'):
|
||||
value = self.to_python_boolean(value)
|
||||
elif lookup.endswith('__in'):
|
||||
items = []
|
||||
for item in value.split(','):
|
||||
items.append(self.value_to_python_for_field(field, item))
|
||||
value = items
|
||||
elif lookup.endswith('__regex') or lookup.endswith('__iregex'):
|
||||
try:
|
||||
re.compile(value)
|
||||
except re.error, e:
|
||||
raise ValueError(e.args[0])
|
||||
return value
|
||||
else:
|
||||
value = self.value_to_python_for_field(field, value)
|
||||
return value
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
# Apply filters specified via QUERY_PARAMS. Each entry in the lists
|
||||
# below is (negate, field, value).
|
||||
and_filters = []
|
||||
or_filters = []
|
||||
for key, values in request.QUERY_PARAMS.lists():
|
||||
if key in self.RESERVED_NAMES:
|
||||
continue
|
||||
|
||||
# Custom __int filter suffix (internal use only).
|
||||
q_int = False
|
||||
if key.endswith('__int'):
|
||||
key = key[:-5]
|
||||
q_int = True
|
||||
# Custom or__ filter prefix (or__ can precede not__).
|
||||
q_or = False
|
||||
if key.startswith('or__'):
|
||||
key = key[4:]
|
||||
q_or = True
|
||||
# Custom not__ filter prefix.
|
||||
q_not = False
|
||||
if key.startswith('not__'):
|
||||
key = key[5:]
|
||||
q_not = True
|
||||
|
||||
# Convert value(s) to python and add to the appropriate list.
|
||||
for value in values:
|
||||
if q_int:
|
||||
value = int(value)
|
||||
value = self.value_to_python(queryset.model, key, value)
|
||||
if q_or:
|
||||
or_filters.append((q_not, key, value))
|
||||
else:
|
||||
and_filters.append((q_not, key, value))
|
||||
|
||||
# Now build Q objects for database query filter.
|
||||
if and_filters or or_filters:
|
||||
args = []
|
||||
for n, k, v in and_filters:
|
||||
if n:
|
||||
args.append(~Q(**{k:v}))
|
||||
else:
|
||||
args.append(Q(**{k:v}))
|
||||
if or_filters:
|
||||
q = Q()
|
||||
for n,k,v in or_filters:
|
||||
if n:
|
||||
q |= ~Q(**{k:v})
|
||||
else:
|
||||
q |= Q(**{k:v})
|
||||
args.append(q)
|
||||
queryset = queryset.filter(*args)
|
||||
return queryset
|
||||
except (FieldError, FieldDoesNotExist, ValueError), e:
|
||||
raise ParseError(e.args[0])
|
||||
except ValidationError, e:
|
||||
raise ParseError(e.messages)
|
||||
|
||||
class OrderByBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter to apply ordering based on query string parameters.
|
||||
'''
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
order_by = None
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
if key in ('order', 'order_by'):
|
||||
order_by = value
|
||||
if ',' in value:
|
||||
order_by = value.split(',')
|
||||
else:
|
||||
order_by = (value,)
|
||||
if order_by:
|
||||
queryset = queryset.order_by(*order_by)
|
||||
# Fetch the first result to run the query, otherwise we don't
|
||||
# always catch the FieldError for invalid field names.
|
||||
try:
|
||||
queryset[0]
|
||||
except IndexError:
|
||||
pass
|
||||
return queryset
|
||||
except FieldError, e:
|
||||
# Return a 400 for invalid field names.
|
||||
raise ParseError(*e.args)
|
||||
@@ -35,7 +35,7 @@ from taggit.managers import TaggableManager
|
||||
from djcelery.models import TaskMeta
|
||||
|
||||
# AWX
|
||||
from awx.main.compat import slugify
|
||||
from awx.lib.compat import slugify
|
||||
from awx.main.fields import AutoOneToOneField
|
||||
from awx.main.utils import encrypt_field, decrypt_field
|
||||
|
||||
@@ -343,7 +343,7 @@ class Organization(CommonModel):
|
||||
projects = models.ManyToManyField('Project', blank=True, related_name='organizations')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:organization_detail', args=(self.pk,))
|
||||
return reverse('api:organization_detail', args=(self.pk,))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
@@ -412,7 +412,7 @@ class Inventory(CommonModel):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:inventory_detail', args=(self.pk,))
|
||||
return reverse('api:inventory_detail', args=(self.pk,))
|
||||
|
||||
def mark_inactive(self, save=True):
|
||||
'''
|
||||
@@ -537,7 +537,7 @@ class Host(CommonModelNameNotUnique):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:host_detail', args=(self.pk,))
|
||||
return reverse('api:host_detail', args=(self.pk,))
|
||||
|
||||
def mark_inactive(self, save=True):
|
||||
'''
|
||||
@@ -667,7 +667,7 @@ class Group(CommonModelNameNotUnique):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:group_detail', args=(self.pk,))
|
||||
return reverse('api:group_detail', args=(self.pk,))
|
||||
|
||||
def mark_inactive(self, save=True):
|
||||
'''
|
||||
@@ -938,7 +938,7 @@ class InventorySource(PrimordialModel):
|
||||
return inventory_update
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:inventory_source_detail', args=(self.pk,))
|
||||
return reverse('api:inventory_source_detail', args=(self.pk,))
|
||||
|
||||
class InventoryUpdate(CommonTask):
|
||||
'''
|
||||
@@ -972,7 +972,7 @@ class InventoryUpdate(CommonTask):
|
||||
return self.inventory_source
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:inventory_update_detail', args=(self.pk,))
|
||||
return reverse('api:inventory_update_detail', args=(self.pk,))
|
||||
|
||||
def _get_task_class(self):
|
||||
from awx.main.tasks import RunInventoryUpdate
|
||||
@@ -1087,7 +1087,7 @@ class Credential(CommonModelNameNotUnique):
|
||||
return needed
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:credential_detail', args=(self.pk,))
|
||||
return reverse('api:credential_detail', args=(self.pk,))
|
||||
|
||||
def clean(self):
|
||||
if self.user and self.team:
|
||||
@@ -1142,7 +1142,7 @@ class Team(CommonModelNameNotUnique):
|
||||
organization = models.ForeignKey('Organization', blank=False, null=True, on_delete=SET_NULL, related_name='teams')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:team_detail', args=(self.pk,))
|
||||
return reverse('api:team_detail', args=(self.pk,))
|
||||
|
||||
class Project(CommonModel):
|
||||
'''
|
||||
@@ -1377,7 +1377,7 @@ class Project(CommonModel):
|
||||
return project_update
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:project_detail', args=(self.pk,))
|
||||
return reverse('api:project_detail', args=(self.pk,))
|
||||
|
||||
def get_project_path(self, check_if_exists=True):
|
||||
local_path = os.path.basename(self.local_path)
|
||||
@@ -1435,7 +1435,7 @@ class ProjectUpdate(CommonTask):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:project_update_detail', args=(self.pk,))
|
||||
return reverse('api:project_update_detail', args=(self.pk,))
|
||||
|
||||
def _get_parent_instance(self):
|
||||
return self.project
|
||||
@@ -1507,7 +1507,7 @@ class Permission(CommonModelNameNotUnique):
|
||||
))
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:permission_detail', args=(self.pk,))
|
||||
return reverse('api:permission_detail', args=(self.pk,))
|
||||
|
||||
# TODO: other job types (later)
|
||||
|
||||
@@ -1607,7 +1607,7 @@ class JobTemplate(CommonModel):
|
||||
return job
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_template_detail', args=(self.pk,))
|
||||
return reverse('api:job_template_detail', args=(self.pk,))
|
||||
|
||||
def can_start_without_user_input(self):
|
||||
'''
|
||||
@@ -1719,7 +1719,7 @@ class Job(CommonTask):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_detail', args=(self.pk,))
|
||||
return reverse('api:job_detail', args=(self.pk,))
|
||||
|
||||
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
||||
|
||||
@@ -1829,7 +1829,7 @@ class JobHostSummary(models.Model):
|
||||
self.processed, self.skipped)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_host_summary_detail', args=(self.pk,))
|
||||
return reverse('api:job_host_summary_detail', args=(self.pk,))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.failed = bool(self.dark or self.failures)
|
||||
@@ -1982,7 +1982,7 @@ class JobEvent(models.Model):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_event_detail', args=(self.pk,))
|
||||
return reverse('api:job_event_detail', args=(self.pk,))
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s @ %s' % (self.get_event_display(), self.created.isoformat())
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import serializers, pagination
|
||||
from rest_framework.templatetags.rest_framework import replace_query_param
|
||||
|
||||
class NextPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.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)
|
||||
|
||||
class PreviousPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.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='*')
|
||||
@@ -1,188 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import logging
|
||||
|
||||
# Django
|
||||
from django.http import Http404
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import permissions
|
||||
|
||||
# AWX
|
||||
from awx.main.access import *
|
||||
from awx.main.models import *
|
||||
from awx.main.utils import get_object_or_400
|
||||
|
||||
logger = logging.getLogger('awx.main.permissions')
|
||||
|
||||
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
|
||||
'JobTaskPermission']
|
||||
|
||||
class ModelAccessPermission(permissions.BasePermission):
|
||||
'''
|
||||
Default permissions class to check user access based on the model and
|
||||
request method, optionally verifying the request data.
|
||||
'''
|
||||
|
||||
def check_options_permissions(self, request, view, obj=None):
|
||||
return self.check_get_permissions(request, view, obj)
|
||||
|
||||
def check_head_permissions(self, request, view, obj=None):
|
||||
return self.check_get_permissions(request, view, obj)
|
||||
|
||||
def check_get_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = get_object_or_400(view.parent_model, pk=view.kwargs['pk'])
|
||||
if not check_user_access(request.user, view.parent_model, 'read',
|
||||
parent_obj):
|
||||
return False
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'read', obj)
|
||||
|
||||
def check_post_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = get_object_or_400(view.parent_model, pk=view.kwargs['pk'])
|
||||
return True
|
||||
elif getattr(view, 'is_job_start', False):
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'start', obj)
|
||||
elif getattr(view, 'is_job_cancel', False):
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'cancel', obj)
|
||||
else:
|
||||
if obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'add', request.DATA)
|
||||
|
||||
def check_put_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
if getattr(view, 'is_variable_data', False):
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
dict(variables=request.DATA))
|
||||
else:
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
request.DATA)
|
||||
|
||||
def check_patch_permissions(self, request, view, obj=None):
|
||||
return self.check_put_permissions(request, view, obj)
|
||||
|
||||
def check_delete_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
return check_user_access(request.user, view.model, 'delete', obj)
|
||||
|
||||
def check_permissions(self, request, view, obj=None):
|
||||
'''
|
||||
Perform basic permissions checking before delegating to the appropriate
|
||||
method based on the request method.
|
||||
'''
|
||||
|
||||
# Check that obj (if given) is active, otherwise raise a 404.
|
||||
active = getattr(obj, 'active', getattr(obj, 'is_active', True))
|
||||
if callable(active):
|
||||
active = active()
|
||||
if not active:
|
||||
raise Http404()
|
||||
|
||||
# Don't allow anonymous users. 401, not 403, hence no raised exception.
|
||||
if not request.user or request.user.is_anonymous():
|
||||
return False
|
||||
|
||||
# Don't allow inactive users (and respond with a 403).
|
||||
if not request.user.is_active:
|
||||
raise PermissionDenied('your account is inactive')
|
||||
|
||||
# Always allow superusers (as long as they are active).
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
|
||||
# Check permissions for the given view and object, based on the request
|
||||
# method used.
|
||||
check_method = getattr(self, 'check_%s_permissions' % \
|
||||
request.method.lower(), None)
|
||||
result = check_method and check_method(request, view, obj)
|
||||
if not result:
|
||||
raise PermissionDenied()
|
||||
|
||||
return result
|
||||
|
||||
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,
|
||||
view.__class__.__name__, obj)
|
||||
try:
|
||||
response = self.check_permissions(request, view, obj)
|
||||
except Exception, e:
|
||||
logger.debug('has_permission raised %r', e, exc_info=True)
|
||||
raise
|
||||
else:
|
||||
logger.debug('has_permission returned %r', response)
|
||||
return response
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return self.has_permission(request, view, obj)
|
||||
|
||||
class JobTemplateCallbackPermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission check used by job template callback view for requests from
|
||||
empheral hosts.
|
||||
'''
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
# If another authentication method was used and it's not a POST, return
|
||||
# True to fall through to the next permission class.
|
||||
if (request.user or request.auth) and request.method.lower() != 'post':
|
||||
return super(JobTemplateCallbackPermission, self).has_permission(request, view, obj)
|
||||
|
||||
# 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', '')
|
||||
if request.method.lower() != 'post':
|
||||
raise PermissionDenied()
|
||||
elif not host_config_key:
|
||||
raise PermissionDenied()
|
||||
elif obj and not obj.active:
|
||||
raise PermissionDenied()
|
||||
elif obj and obj.host_config_key != host_config_key:
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
return True
|
||||
|
||||
class JobTaskPermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission checks used for API callbacks from running a task.
|
||||
'''
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
# If another authentication method was used other than the one for job
|
||||
# callbacks, default to the superclass permissions checking.
|
||||
if request.user or not request.auth:
|
||||
return super(JobTaskPermission, self).has_permission(request, view, obj)
|
||||
|
||||
# Verify that the job ID present in the auth token is for a valid,
|
||||
# active job.
|
||||
try:
|
||||
job = Job.objects.get(active=True, status='running',
|
||||
pk=int(request.auth.split('-')[0]))
|
||||
except (Job.DoesNotExist, TypeError):
|
||||
return False
|
||||
|
||||
# Verify that the request method is one of those allowed for the given
|
||||
# view, also that the job or inventory being accessed matches the auth
|
||||
# token.
|
||||
if view.model == Inventory and request.method.lower() in ('head', 'get'):
|
||||
return bool(not obj or obj.pk == job.inventory.pk)
|
||||
elif view.model == JobEvent and request.method.lower() == 'post':
|
||||
return bool(not obj or obj.pk == job.pk)
|
||||
else:
|
||||
return False
|
||||
@@ -1,18 +0,0 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import renderers
|
||||
|
||||
class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
'''
|
||||
Customizations to the default browsable API renderer.
|
||||
'''
|
||||
|
||||
def get_rendered_html_form(self, 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):
|
||||
return
|
||||
if method in ('DELETE', 'OPTIONS'):
|
||||
return True # Don't actually need to return a form
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,120 +0,0 @@
|
||||
The resulting data structure contains:
|
||||
|
||||
{
|
||||
"count": 99,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
The `count` field indicates the total number of {{ model_verbose_name_plural }}
|
||||
found for the given query. The `next` and `previous` fields provides links to
|
||||
additional results if there are more than will fit on a single page. The
|
||||
`results` list contains zero or more {{ model_verbose_name }} records.
|
||||
|
||||
## Results
|
||||
|
||||
Each {{ model_verbose_name }} data structure includes the following fields:
|
||||
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
|
||||
## Sorting
|
||||
|
||||
To specify that {{ model_verbose_name_plural }} are returned in a particular
|
||||
order, use the `order_by` query string parameter on the GET request.
|
||||
|
||||
?order_by={{ order_field }}
|
||||
|
||||
Prefix the field name with a dash `-` to sort in reverse:
|
||||
|
||||
?order_by=-{{ order_field }}
|
||||
|
||||
Multiple sorting fields may be specified by separating the field names with a
|
||||
comma `,`:
|
||||
|
||||
?order_by={{ order_field }},some_other_field
|
||||
|
||||
## Pagination
|
||||
|
||||
Use the `page_size` query string parameter to change the number of results
|
||||
returned for each request. Use the `page` query string parameter to retrieve
|
||||
a particular page of results.
|
||||
|
||||
?page_size=100&page=2
|
||||
|
||||
The `previous` and `next` links returned with the results will set these query
|
||||
string parameters automatically.
|
||||
|
||||
## Searching
|
||||
|
||||
Use the `search` query string parameter to perform a case-insensitive search
|
||||
within all designated text fields of a model.
|
||||
|
||||
?search=findme
|
||||
|
||||
_New in AWX 1.4_
|
||||
|
||||
## Filtering
|
||||
|
||||
Any additional query string parameters may be used to filter the list of
|
||||
results returned to those matching a given value. Only fields and relations
|
||||
that exist in the database may be used for filtering. Any special characters
|
||||
in the specified value should be url-encoded. For example:
|
||||
|
||||
?field=value%20xyz
|
||||
|
||||
Fields may also span relations, only for fields and relationships defined in
|
||||
the database:
|
||||
|
||||
?other__field=value
|
||||
|
||||
To exclude results matching certain criteria, prefix the field parameter with
|
||||
`not__`:
|
||||
|
||||
?not__field=value
|
||||
|
||||
(_New in AWX 1.4_) By default, all query string filters are AND'ed together, so
|
||||
only the results matching *all* filters will be returned. To combine results
|
||||
matching *any* one of multiple criteria, prefix each query string parameter
|
||||
with `or__`:
|
||||
|
||||
?or__field=value&or__field=othervalue
|
||||
?or__not__field=value&or__field=othervalue
|
||||
|
||||
Field lookups may also be used for more advanced queries, by appending the
|
||||
lookup to the field name:
|
||||
|
||||
?field__lookup=value
|
||||
|
||||
The following field lookups are supported:
|
||||
|
||||
* `exact`: Exact match (default lookup if not specified).
|
||||
* `iexact`: Case-insensitive version of `exact`.
|
||||
* `contains`: Field contains value.
|
||||
* `icontains`: Case-insensitive version of `contains`.
|
||||
* `startswith`: Field starts with value.
|
||||
* `istartswith`: Case-insensitive version of `startswith`.
|
||||
* `endswith`: Field ends with value.
|
||||
* `iendswith`: Case-insensitive version of `endswith`.
|
||||
* `regex`: Field matches the given regular expression.
|
||||
* `iregex`: Case-insensitive version of `regex`.
|
||||
* `gt`: Greater than comparison.
|
||||
* `gte`: Greater than or equal to comparison.
|
||||
* `lt`: Less than comparison.
|
||||
* `lte`: Less than or equal to comparison.
|
||||
* `isnull`: Check whether the given field or related object is null; expects a
|
||||
boolean value.
|
||||
* `in`: Check whether the given field's value is present in the list provided;
|
||||
expects a list of items.
|
||||
|
||||
Boolean values may be specified as `True` or `1` for true, `False` or `0` for
|
||||
false (both case-insensitive).
|
||||
|
||||
Null values may be specified as `None` or `Null` (both case-insensitive),
|
||||
though it is preferred to use the `isnull` lookup to explicitly check for null
|
||||
values.
|
||||
|
||||
Lists (for the `in` lookup) may be specified as a comma-separated list of
|
||||
values.
|
||||
@@ -1,2 +0,0 @@
|
||||
{% if new_in_13 %}> _New in AWX 1.3_{% endif %}
|
||||
{% if new_in_14 %}> _New in AWX 1.4_{% endif %}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
||||
{% if not write_only or not fm.read_only %}
|
||||
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if fm.required %}, required{% endif %}{% if fm.read_only %}, read-only{% endif %})
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
{% endfor %}
|
||||
@@ -1,4 +0,0 @@
|
||||
The root of the AWX REST API.
|
||||
|
||||
Make a GET request to this resource to obtain information about the available
|
||||
API versions.
|
||||
@@ -1,12 +0,0 @@
|
||||
Site configuration settings and general information.
|
||||
|
||||
Make a GET request to this resource to retrieve the configuration containing
|
||||
the following fields (some fields may not be visible to all users):
|
||||
|
||||
* `project_base_dir`: Path on the server where projects and playbooks are \
|
||||
stored.
|
||||
* `project_local_paths`: List of directories beneath `project_base_dir` to
|
||||
use when creating/editing a project.
|
||||
* `time_zone`: The configured time zone for the server.
|
||||
* `license_info`: Information about the current license.
|
||||
* `version`: Version of AWX package installed.
|
||||
@@ -1,4 +0,0 @@
|
||||
Version 1 of the AWX REST API.
|
||||
|
||||
Make a GET request to this resource to obtain a list of all child resources
|
||||
available via the API.
|
||||
@@ -1,3 +0,0 @@
|
||||
{{ docstring }}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,31 +0,0 @@
|
||||
Make a POST request to this resource with `username` and `password` fields to
|
||||
obtain an authentication token to use for subsequent requests.
|
||||
|
||||
Example JSON to POST (content type is `application/json`):
|
||||
|
||||
{"username": "user", "password": "my pass"}
|
||||
|
||||
Example form data to post (content type is `application/x-www-form-urlencoded`):
|
||||
|
||||
username=user&password=my%20pass
|
||||
|
||||
If the username and password provided are valid, the response will contain a
|
||||
`token` field with the authentication token to use and an `expires` field with
|
||||
the timestamp when the token will expire:
|
||||
|
||||
{
|
||||
"token": "8f17825cf08a7efea124f2638f3896f6637f8745",
|
||||
"expires": "2013-09-05T21:46:35.729Z"
|
||||
}
|
||||
|
||||
Otherwise, the response will indicate the error that occurred and return a 4xx
|
||||
status code.
|
||||
|
||||
For subsequent requests, pass the token via the HTTP `Authorization` request
|
||||
header:
|
||||
|
||||
Authorization: Token 8f17825cf08a7efea124f2638f3896f6637f8745
|
||||
|
||||
Each request that uses the token for authentication will refresh its expiration
|
||||
timestamp and keep it from expiring. A token only expires when it is not used
|
||||
for the configured timeout interval (default 1800 seconds).
|
||||
@@ -1,9 +0,0 @@
|
||||
# Retrieve {{ model_verbose_name|title }} Variable Data:
|
||||
|
||||
Make a GET request to this resource to retrieve all variables defined for this
|
||||
{{ model_verbose_name }}.
|
||||
|
||||
# Update {{ model_verbose_name|title }} Variable Data:
|
||||
|
||||
Make a PUT request to this resource to update variables defined for this
|
||||
{{ model_verbose_name }}.
|
||||
@@ -1,7 +0,0 @@
|
||||
# List All {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of all
|
||||
{{ model_verbose_name_plural }} directly or indirectly belonging to this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
@@ -1,9 +0,0 @@
|
||||
# List Potential Child Groups for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} available to be added as children of the
|
||||
current {{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,7 +0,0 @@
|
||||
# List All {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of all
|
||||
{{ model_verbose_name_plural }} of which the selected
|
||||
{{ parent_model_verbose_name }} is directly or indirectly a member.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
@@ -1,7 +0,0 @@
|
||||
# List Root {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of root (top-level)
|
||||
{{ model_verbose_name_plural }} associated with this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
@@ -1,31 +0,0 @@
|
||||
Generate inventory group and host data as needed for an inventory script.
|
||||
|
||||
Refer to [External Inventory Scripts](http://www.ansibleworks.com/docs/api.html#external-inventory-scripts)
|
||||
for more information on inventory scripts.
|
||||
|
||||
## List Response
|
||||
|
||||
Make a GET request to this resource without query parameters to retrieve a JSON
|
||||
object containing groups, including the hosts, children and variables for each
|
||||
group. The response data is equivalent to that returned by passing the
|
||||
`--list` argument to an inventory script.
|
||||
|
||||
_(New in AWX 1.3)_ Specify a query string of `?hostvars=1` to retrieve the JSON
|
||||
object above including all host variables. The `['_meta']['hostvars']` object
|
||||
in the response contains an entry for each host with its variables. This
|
||||
response format can be used with Ansible 1.3 and later to avoid making a
|
||||
separate API request for each host. Refer to
|
||||
[Tuning the External Inventory Script](http://www.ansibleworks.com/docs/api.html#tuning-the-external-inventory-script)
|
||||
for more information on this feature.
|
||||
|
||||
_(New in AWX 1.4)_ By default, the inventory script will only return hosts that
|
||||
are enabled in the inventory. This feature allows disabled hosts to be skipped
|
||||
when running jobs without removing them from the inventory. Specify a query
|
||||
string of `?all=1` to return all hosts, including disabled ones.
|
||||
|
||||
## Host Response
|
||||
|
||||
Make a GET request to this resource with a query string similar to
|
||||
`?host=HOSTNAME` to retrieve a JSON object containing host variables for the
|
||||
specified host. The response data is equivalent to that returned by passing
|
||||
the `--host HOSTNAME` argument to an inventory script.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Cancel Inventory Update
|
||||
|
||||
Make a GET request to this resource to determine if the inventory update can be
|
||||
cancelled. The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this update can be canceled (boolean,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running inventory
|
||||
update. The response status code will be 202 if successful, or 405 if the
|
||||
update cannot be canceled.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,18 +0,0 @@
|
||||
# Update Inventory Source
|
||||
|
||||
Make a GET request to this resource to determine if the group can be updated
|
||||
from its inventory source and whether any passwords are required for the
|
||||
update. The response will include the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update from the
|
||||
inventory source (array, read-only)
|
||||
|
||||
Make a POST request to this resource to update the inventory source. If any
|
||||
passwords are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the inventory source
|
||||
is not defined or cannot be updated, a 405 status code will be returned.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,15 +0,0 @@
|
||||
# Group Tree for this {{ model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a hierarchical view of groups
|
||||
associated with the selected {{ model_verbose_name }}.
|
||||
|
||||
The resulting data structure contains a list of root groups, with each group
|
||||
also containing a list of its children.
|
||||
|
||||
## Results
|
||||
|
||||
Each group data structure includes the following fields:
|
||||
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,10 +0,0 @@
|
||||
# Cancel Job
|
||||
|
||||
Make a GET request to this resource to determine if the job can be cancelled.
|
||||
The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this job can be canceled (boolean, read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running job. The
|
||||
response status code will be 202 if successful, or 405 if the job cannot be
|
||||
canceled.
|
||||
@@ -1,5 +0,0 @@
|
||||
{% include "main/list_create_api_view.md" %}
|
||||
|
||||
If the `job_template` field is specified, any fields not explicitly provided
|
||||
for the new job (except `name` and `description`) will use the default values
|
||||
from the job template.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Start Job
|
||||
|
||||
Make a GET request to this resource to determine if the job can be started and
|
||||
whether any passwords are required to start the job. The response will include
|
||||
the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_start`: Password names required to start the job (array, read-only)
|
||||
|
||||
Make a POST request to this resource to start the job. If any passwords are
|
||||
required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the job cannot be
|
||||
started, a 405 status code will be returned.
|
||||
@@ -1,33 +0,0 @@
|
||||
The job template callback allows for empheral hosts to launch a new job.
|
||||
|
||||
Configure a host to POST to this resource, passing the `host_config_key`
|
||||
parameter, to start a new job limited to only the requesting host. In the
|
||||
examples below, replace the `N` parameter with the `id` of the job template
|
||||
and the `HOST_CONFIG_KEY` with the `host_config_key` associated with the
|
||||
job template.
|
||||
|
||||
For example, using curl:
|
||||
|
||||
curl --data-urlencode host_config_key=HOST_CONFIG_KEY http://server/api/v1/job_templates/N/callback/
|
||||
|
||||
Or using wget:
|
||||
|
||||
wget -O /dev/null --post-data="host_config_key=HOST_CONFIG_KEY" http://server/api/v1/job_templates/N/callback/
|
||||
|
||||
The response will return status 202 if the request is valid, 403 for an
|
||||
invalid host config key, or 400 if the host cannot be determined from the
|
||||
address making the request.
|
||||
|
||||
A GET request may be used to verify that the correct host will be selected.
|
||||
This request must authenticate as a valid user with permission to edit the
|
||||
job template. For example:
|
||||
|
||||
curl http://user:password@server/api/v1/job_templates/N/callback/
|
||||
|
||||
The response will include the host config key as well as the host name(s)
|
||||
that would match the request:
|
||||
|
||||
{
|
||||
"host_config_key": "HOST_CONFIG_KEY",
|
||||
"matching_hosts": ["hostname"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "main/sub_list_create_api_view.md" %}
|
||||
|
||||
{% block post_create %}
|
||||
Any fields not explicitly provided for the new job (except `name` and
|
||||
`description`) will use the default values from the job template.
|
||||
{% endblock %}
|
||||
@@ -1,8 +0,0 @@
|
||||
# List {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve the list of
|
||||
{{ model_verbose_name_plural }}.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,12 +0,0 @@
|
||||
{% include "main/list_api_view.md" %}
|
||||
|
||||
# Create {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a POST request to this resource with the following {{ model_verbose_name }}
|
||||
fields to create a new {{ model_verbose_name }}:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,3 +0,0 @@
|
||||
{% with model_verbose_name="admin user" model_verbose_name_plural="admin users" %}
|
||||
{% include "main/sub_list_create_api_view.md" %}
|
||||
{% endwith %}
|
||||
@@ -1,4 +0,0 @@
|
||||
# Retrieve {{ model_verbose_name|title }} Playbooks:
|
||||
|
||||
Make GET request to this resource to retrieve a list of playbooks available
|
||||
for this {{ model_verbose_name }}.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Cancel Project Update
|
||||
|
||||
Make a GET request to this resource to determine if the project update can be
|
||||
cancelled. The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this update can be canceled (boolean,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running project
|
||||
update. The response status code will be 202 if successful, or 405 if the
|
||||
update cannot be canceled.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,18 +0,0 @@
|
||||
# Update Project
|
||||
|
||||
Make a GET request to this resource to determine if the project can be updated
|
||||
from its SCM source and whether any passwords are required for the update. The
|
||||
response will include the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update the project
|
||||
(array, read-only)
|
||||
|
||||
Make a POST request to this resource to update the project. If any passwords
|
||||
are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the project cannot be
|
||||
updated, a 405 status code will be returned.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Retrieve {{ model_verbose_name|title }}:
|
||||
|
||||
Make GET request to this resource to retrieve a single {{ model_verbose_name }}
|
||||
record containing the following fields:
|
||||
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{% include "main/retrieve_api_view.md" %}
|
||||
|
||||
# Update {{ model_verbose_name|title }}:
|
||||
|
||||
Make a PUT or PATCH request to this resource to update this
|
||||
{{ model_verbose_name }}. The following fields may be modified:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
For a PUT request, include **all** fields in the request.
|
||||
|
||||
For a PATCH request, include only the fields that are being modified.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,20 +0,0 @@
|
||||
{% include "main/retrieve_api_view.md" %}
|
||||
|
||||
# Update {{ model_verbose_name|title }}:
|
||||
|
||||
Make a PUT or PATCH request to this resource to update this
|
||||
{{ model_verbose_name }}. The following fields may be modified:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
For a PUT request, include **all** fields in the request.
|
||||
|
||||
For a PATCH request, include only the fields that are being modified.
|
||||
|
||||
# Delete {{ model_verbose_name|title }}:
|
||||
|
||||
Make a DELETE request to this resource to delete this {{ model_verbose_name }}.
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,9 +0,0 @@
|
||||
# List {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} associated with the selected
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,39 +0,0 @@
|
||||
{% include "main/sub_list_api_view.md" %}
|
||||
|
||||
# Create {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with the following {{ model_verbose_name }}
|
||||
fields to create a new {{ model_verbose_name }} associated with this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
{% block post_create %}{% endblock %}
|
||||
|
||||
{% if parent_key %}
|
||||
# Remove {{ parent_model_verbose_name|title }} {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a POST request to this resource with `id` and `disassociate` fields to
|
||||
delete the associated {{ model_verbose_name }}.
|
||||
|
||||
{
|
||||
"id": 123,
|
||||
"disassociate": true
|
||||
}
|
||||
|
||||
{% else %}
|
||||
# Add {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with only an `id` field to associate an
|
||||
existing {{ model_verbose_name }} with this {{ parent_model_verbose_name }}.
|
||||
|
||||
# Remove {{ model_verbose_name_plural|title }} from this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with `id` and `disassociate` fields to
|
||||
remove the {{ model_verbose_name }} from this {{ parent_model_verbose_name }}
|
||||
without deleting the {{ model_verbose_name }}.
|
||||
{% endif %}
|
||||
|
||||
{% include "main/_new_in_awx.md" %}
|
||||
@@ -1,7 +0,0 @@
|
||||
# List {{ model_verbose_name_plural|title }} Administered by this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} of which the selected
|
||||
{{ parent_model_verbose_name }} is an admin.
|
||||
|
||||
{% include "main/_list_common.md" %}
|
||||
@@ -1,7 +0,0 @@
|
||||
Make a GET request to retrieve user information about the current user.
|
||||
|
||||
One result should be returned containing the following fields:
|
||||
|
||||
{% include "main/_result_fields_common.md" %}
|
||||
|
||||
Use the primary URL for the user (/api/v1/users/N/) to modify the user.
|
||||
@@ -44,7 +44,7 @@ class InventoryTest(BaseTest):
|
||||
)
|
||||
|
||||
def test_get_inventory_list(self):
|
||||
url = reverse('main:inventory_list')
|
||||
url = reverse('api:inventory_list')
|
||||
qs = Inventory.objects.filter(active=True).distinct()
|
||||
|
||||
# Check list view with invalid authentication.
|
||||
@@ -66,7 +66,7 @@ class InventoryTest(BaseTest):
|
||||
self.check_get_list(url, self.nobody_django_user, nobody_qs)
|
||||
|
||||
def test_post_inventory_list(self):
|
||||
url = reverse('main:inventory_list')
|
||||
url = reverse('api:inventory_list')
|
||||
|
||||
# Check post to list view with invalid authentication.
|
||||
new_inv_0 = dict(name='inventory-c', description='baz', organization=self.organizations[0].pk)
|
||||
@@ -95,8 +95,8 @@ class InventoryTest(BaseTest):
|
||||
data = self.post(url, data=new_inv_denied, expect=403)
|
||||
|
||||
def test_get_inventory_detail(self):
|
||||
url_a = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
url_a = reverse('api:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('api:inventory_detail', args=(self.inventory_b.pk,))
|
||||
|
||||
# Check detail view with invalid authentication.
|
||||
self.check_invalid_auth(url_a)
|
||||
@@ -125,8 +125,8 @@ class InventoryTest(BaseTest):
|
||||
data = self.get(url_b, expect=403)
|
||||
|
||||
def test_put_inventory_detail(self):
|
||||
url_a = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
url_a = reverse('api:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('api:inventory_detail', args=(self.inventory_b.pk,))
|
||||
|
||||
# Check put to detail view with invalid authentication.
|
||||
self.check_invalid_auth(url_a, methods=('put',))
|
||||
@@ -178,7 +178,7 @@ class InventoryTest(BaseTest):
|
||||
# Via AC-376:
|
||||
# Create an inventory. Leave the description empty.
|
||||
# Edit the new inventory, change the Name, click Save.
|
||||
list_url = reverse('main:inventory_list')
|
||||
list_url = reverse('api:inventory_list')
|
||||
new_data = dict(name='inventory-c', description='',
|
||||
organization=self.organizations[0].pk)
|
||||
new_id = max(Inventory.objects.values_list('pk', flat=True)) + 1
|
||||
@@ -186,7 +186,7 @@ class InventoryTest(BaseTest):
|
||||
data = self.post(list_url, data=new_data, expect=201)
|
||||
self.assertEqual(data['id'], new_id)
|
||||
self.assertEqual(data['description'], '')
|
||||
url_c = reverse('main:inventory_detail', args=(new_id,))
|
||||
url_c = reverse('api:inventory_detail', args=(new_id,))
|
||||
data = self.get(url_c, expect=200)
|
||||
self.assertEqual(data['description'], '')
|
||||
data['description'] = None
|
||||
@@ -194,8 +194,8 @@ class InventoryTest(BaseTest):
|
||||
self.put(url_c, data, expect=200)
|
||||
|
||||
def test_delete_inventory_detail(self):
|
||||
url_a = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
url_a = reverse('api:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('api:inventory_detail', args=(self.inventory_b.pk,))
|
||||
|
||||
# Create test hosts and groups within each inventory.
|
||||
self.inventory_a.hosts.create(name='host-a')
|
||||
@@ -246,11 +246,11 @@ class InventoryTest(BaseTest):
|
||||
def test_main_line(self):
|
||||
|
||||
# some basic URLs...
|
||||
inventories = reverse('main:inventory_list')
|
||||
inventories_1 = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
inventories_2 = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
hosts = reverse('main:host_list')
|
||||
groups = reverse('main:group_list')
|
||||
inventories = reverse('api:inventory_list')
|
||||
inventories_1 = reverse('api:inventory_detail', args=(self.inventory_a.pk,))
|
||||
inventories_2 = reverse('api:inventory_detail', args=(self.inventory_b.pk,))
|
||||
hosts = reverse('api:host_list')
|
||||
groups = reverse('api:group_list')
|
||||
|
||||
|
||||
# a super user can add hosts (but inventory ID is required)
|
||||
@@ -326,7 +326,7 @@ class InventoryTest(BaseTest):
|
||||
new_group_c = dict(name='web4', inventory=inv.pk)
|
||||
new_group_d = dict(name='web5', inventory=inv.pk)
|
||||
new_group_e = dict(name='web6', inventory=inv.pk)
|
||||
groups = reverse('main:group_list')
|
||||
groups = reverse('api:group_list')
|
||||
|
||||
data0 = self.post(groups, data=invalid, expect=400, auth=self.get_super_credentials())
|
||||
data0 = self.post(groups, data=new_group_a, expect=201, auth=self.get_super_credentials())
|
||||
@@ -360,7 +360,7 @@ class InventoryTest(BaseTest):
|
||||
#################################################
|
||||
# HOSTS->inventories POST via subcollection
|
||||
|
||||
url = reverse('main:inventory_hosts_list', args=(self.inventory_a.pk,))
|
||||
url = reverse('api:inventory_hosts_list', args=(self.inventory_a.pk,))
|
||||
new_host_a = dict(name='web100.example.com')
|
||||
new_host_b = dict(name='web101.example.com')
|
||||
new_host_c = dict(name='web102.example.com')
|
||||
@@ -377,7 +377,7 @@ class InventoryTest(BaseTest):
|
||||
added_by_collection_c = self.post(url, data=new_host_c, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# a normal user with edit permission on the inventory can associate hosts with inventories
|
||||
url5 = reverse('main:inventory_hosts_list', args=(inv.pk,))
|
||||
url5 = reverse('api:inventory_hosts_list', args=(inv.pk,))
|
||||
added_by_collection_d = self.post(url5, data=new_host_d, expect=201, auth=self.get_other_credentials())
|
||||
got = self.get(url5, expect=200, auth=self.get_other_credentials())
|
||||
self.assertEquals(got['count'], 4)
|
||||
@@ -392,9 +392,9 @@ class InventoryTest(BaseTest):
|
||||
##################################################
|
||||
# GROUPS->inventories POST via subcollection
|
||||
|
||||
root_groups = reverse('main:inventory_root_groups_list', args=(self.inventory_a.pk,))
|
||||
root_groups = reverse('api:inventory_root_groups_list', args=(self.inventory_a.pk,))
|
||||
|
||||
url = reverse('main:inventory_groups_list', args=(self.inventory_a.pk,))
|
||||
url = reverse('api:inventory_groups_list', args=(self.inventory_a.pk,))
|
||||
new_group_a = dict(name='web100')
|
||||
new_group_b = dict(name='web101')
|
||||
new_group_c = dict(name='web102')
|
||||
@@ -411,7 +411,7 @@ class InventoryTest(BaseTest):
|
||||
added_by_collection = self.post(url, data=new_group_c, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# a normal user with edit permissions on the inventory can associate groups with inventories
|
||||
url5 = reverse('main:inventory_groups_list', args=(inv.pk,))
|
||||
url5 = reverse('api:inventory_groups_list', args=(inv.pk,))
|
||||
added_by_collection = self.post(url5, data=new_group_d, expect=201, auth=self.get_other_credentials())
|
||||
# make sure duplicates give 400s
|
||||
self.post(url5, data=new_group_d, expect=400, auth=self.get_other_credentials())
|
||||
@@ -436,7 +436,7 @@ class InventoryTest(BaseTest):
|
||||
vars_c = dict(asdf=5555, dog='mouse', cat='mogwai', unstructured=dict(a=[3,0,3],b=dict(z=2600)))
|
||||
|
||||
# attempting to get a variable object creates it, even though it does not already exist
|
||||
vdata_url = reverse('main:host_variable_data', args=(added_by_collection_a['id'],))
|
||||
vdata_url = reverse('api:host_variable_data', args=(added_by_collection_a['id'],))
|
||||
|
||||
got = self.get(vdata_url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(got, {})
|
||||
@@ -467,8 +467,8 @@ class InventoryTest(BaseTest):
|
||||
vars_c = dict(asdf=9999, dog='pluto', cat='five', unstructured=dict(a=[3,3,3],b=dict(z=5)))
|
||||
groups = Group.objects.all()
|
||||
|
||||
vdata1_url = reverse('main:group_variable_data', args=(groups[0].pk,))
|
||||
vdata2_url = reverse('main:group_variable_data', args=(groups[1].pk,))
|
||||
vdata1_url = reverse('api:group_variable_data', args=(groups[0].pk,))
|
||||
vdata2_url = reverse('api:group_variable_data', args=(groups[1].pk,))
|
||||
|
||||
# a super user can associate variable objects with groups
|
||||
got = self.get(vdata1_url, expect=200, auth=self.get_super_credentials())
|
||||
@@ -493,7 +493,7 @@ class InventoryTest(BaseTest):
|
||||
vars_b = dict(asdf=2736, dog='benji', cat='garfield', unstructured=dict(a=[2,2,2],b=dict(x=3,y=4)))
|
||||
vars_c = dict(asdf=7692, dog='buck', cat='sylvester', unstructured=dict(a=[3,3,3],b=dict(z=5)))
|
||||
|
||||
vdata_url = reverse('main:inventory_variable_data', args=(self.inventory_a.pk,))
|
||||
vdata_url = reverse('api:inventory_variable_data', args=(self.inventory_a.pk,))
|
||||
|
||||
# a super user can associate variable objects with inventory
|
||||
got = self.get(vdata_url, expect=200, auth=self.get_super_credentials())
|
||||
@@ -536,14 +536,14 @@ class InventoryTest(BaseTest):
|
||||
groups[0].save()
|
||||
|
||||
# access
|
||||
url1 = reverse('main:group_hosts_list', args=(groups[0].pk,))
|
||||
url1 = reverse('api:group_hosts_list', args=(groups[0].pk,))
|
||||
data = self.get(url1, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 2)
|
||||
self.assertTrue(host1.pk in [x['id'] for x in data['results']])
|
||||
self.assertTrue(host3.pk in [x['id'] for x in data['results']])
|
||||
|
||||
# addition
|
||||
url = reverse('main:host_detail', args=(host2.pk,))
|
||||
url = reverse('api:host_detail', args=(host2.pk,))
|
||||
got = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(got['id'], host2.pk)
|
||||
posted = self.post(url1, data=got, expect=204, auth=self.get_normal_credentials())
|
||||
@@ -596,15 +596,15 @@ class InventoryTest(BaseTest):
|
||||
g2.save()
|
||||
|
||||
# a super user can set subgroups
|
||||
subgroups_url = reverse('main:group_children_list',
|
||||
subgroups_url = reverse('api:group_children_list',
|
||||
args=(Group.objects.get(name='web2').pk,))
|
||||
child_url = reverse('main:group_detail',
|
||||
child_url = reverse('api:group_detail',
|
||||
args=(Group.objects.get(name='web4').pk,))
|
||||
subgroups_url2 = reverse('main:group_children_list',
|
||||
subgroups_url2 = reverse('api:group_children_list',
|
||||
args=(Group.objects.get(name='web6').pk,))
|
||||
subgroups_url3 = reverse('main:group_children_list',
|
||||
subgroups_url3 = reverse('api:group_children_list',
|
||||
args=(Group.objects.get(name='web100').pk,))
|
||||
subgroups_url4 = reverse('main:group_children_list',
|
||||
subgroups_url4 = reverse('api:group_children_list',
|
||||
args=(Group.objects.get(name='web101').pk,))
|
||||
got = self.get(child_url, expect=200, auth=self.get_super_credentials())
|
||||
self.post(subgroups_url, data=got, expect=204, auth=self.get_super_credentials())
|
||||
@@ -625,7 +625,7 @@ class InventoryTest(BaseTest):
|
||||
# a group can't be it's own grandparent
|
||||
subsub = posted2['related']['children']
|
||||
# this is the grandparent
|
||||
original_url = reverse('main:group_detail', args=(Group.objects.get(name='web6').pk,))
|
||||
original_url = reverse('api:group_detail', args=(Group.objects.get(name='web6').pk,))
|
||||
parent_data = self.get(original_url, expect=200, auth=self.get_super_credentials())
|
||||
# now posting to kid's children collection...
|
||||
self.post(subsub, data=parent_data, expect=403, auth=self.get_super_credentials())
|
||||
@@ -648,7 +648,7 @@ class InventoryTest(BaseTest):
|
||||
# slight detour
|
||||
# can see all hosts under a group, even if it has subgroups
|
||||
# this URL is NOT postable
|
||||
all_hosts = reverse('main:group_all_hosts_list',
|
||||
all_hosts = reverse('api:group_all_hosts_list',
|
||||
args=(Group.objects.get(name='web2').pk,))
|
||||
self.assertEqual(Group.objects.get(name='web2').hosts.count(), 3)
|
||||
data = self.get(all_hosts, expect=200, auth=self.get_normal_credentials())
|
||||
@@ -669,7 +669,7 @@ class InventoryTest(BaseTest):
|
||||
self.assertTrue(removed_group.parents.count())
|
||||
self.assertTrue(removed_group.active)
|
||||
for parent in removed_group.parents.all():
|
||||
parent_children_url = reverse('main:group_children_list', args=(parent.pk,))
|
||||
parent_children_url = reverse('api:group_children_list', args=(parent.pk,))
|
||||
data = {'id': removed_group.pk, 'disassociate': 1}
|
||||
self.post(parent_children_url, data, expect=204, auth=self.get_super_credentials())
|
||||
removed_group = Group.objects.get(pk=result['id'])
|
||||
@@ -678,7 +678,7 @@ class InventoryTest(BaseTest):
|
||||
# Removing a group from a hierarchy should migrate its children to the
|
||||
# parent. The group itself will be deleted (marked inactive), and all
|
||||
# relationships removed.
|
||||
url = reverse('main:group_children_list', args=(gx2.pk,))
|
||||
url = reverse('api:group_children_list', args=(gx2.pk,))
|
||||
data = {
|
||||
'id': gx3.pk,
|
||||
'disassociate': 1,
|
||||
@@ -691,7 +691,7 @@ class InventoryTest(BaseTest):
|
||||
#self.assertTrue(gx4 in gx2.children.all())
|
||||
|
||||
# Try with invalid hostnames and invalid IPs.
|
||||
hosts = reverse('main:host_list')
|
||||
hosts = reverse('api:host_list')
|
||||
invalid_expect = 400 # hostname validation is disabled for now.
|
||||
data = dict(name='', inventory=inv.pk)
|
||||
with self.current_user(self.super_django_user):
|
||||
@@ -752,7 +752,7 @@ class InventoryTest(BaseTest):
|
||||
h_d.groups.add(g_d)
|
||||
|
||||
# Old, slow 1.2 way.
|
||||
url = reverse('main:inventory_script_view', args=(i_a.pk,))
|
||||
url = reverse('api:inventory_script_view', args=(i_a.pk,))
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue('all' in response)
|
||||
@@ -772,7 +772,7 @@ class InventoryTest(BaseTest):
|
||||
self.assertEqual(response, h.variables_dict)
|
||||
|
||||
# New 1.3 way.
|
||||
url = reverse('main:inventory_script_view', args=(i_a.pk,))
|
||||
url = reverse('api:inventory_script_view', args=(i_a.pk,))
|
||||
url = '%s?hostvars=1' % url
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
@@ -792,7 +792,7 @@ class InventoryTest(BaseTest):
|
||||
g_d = self.inventory_a.groups.create(name='D')
|
||||
g_d.parents.add(g_c)
|
||||
|
||||
url = reverse('main:inventory_tree_view', args=(self.inventory_a.pk,))
|
||||
url = reverse('api:inventory_tree_view', args=(self.inventory_a.pk,))
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
|
||||
|
||||
@@ -436,7 +436,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
'job_tags', 'host_config_key',)
|
||||
|
||||
def test_get_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
url = reverse('api:job_template_list')
|
||||
qs = JobTemplate.objects.distinct()
|
||||
fields = self.JOB_TEMPLATE_FIELDS
|
||||
|
||||
@@ -474,7 +474,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
# FIXME: Check with other credentials.
|
||||
|
||||
def test_post_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
url = reverse('api:job_template_list')
|
||||
data = dict(
|
||||
name = 'new job template',
|
||||
job_type = PERM_INVENTORY_DEPLOY,
|
||||
@@ -489,7 +489,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
# sue can always add job templates.
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.post(url, data, expect=201)
|
||||
detail_url = reverse('main:job_template_detail',
|
||||
detail_url = reverse('api:job_template_detail',
|
||||
args=(response['id'],))
|
||||
self.assertEquals(response['url'], detail_url)
|
||||
|
||||
@@ -530,7 +530,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_get_job_template_detail(self):
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
url = reverse('api:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -551,7 +551,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_put_job_template_detail(self):
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
url = reverse('api:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url, methods=('put',))# 'patch'))
|
||||
@@ -568,7 +568,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_get_job_template_job_list(self):
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_jobs_list', args=(jt.pk,))
|
||||
url = reverse('api:job_template_jobs_list', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -586,7 +586,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_post_job_template_job_list(self):
|
||||
jt = self.jt_eng_run
|
||||
url = reverse('main:job_template_jobs_list', args=(jt.pk,))
|
||||
url = reverse('api:job_template_jobs_list', args=(jt.pk,))
|
||||
data = dict(
|
||||
name='new job from template',
|
||||
credential=self.cred_bob.pk,
|
||||
@@ -604,7 +604,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_get_job_list(self):
|
||||
url = reverse('main:job_list')
|
||||
url = reverse('api:job_list')
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -622,7 +622,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
# FIXME: Check with other credentials.
|
||||
|
||||
def test_post_job_list(self):
|
||||
url = reverse('main:job_list')
|
||||
url = reverse('api:job_list')
|
||||
data = dict(
|
||||
name='new job without template',
|
||||
job_type=PERM_INVENTORY_DEPLOY,
|
||||
@@ -652,7 +652,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_get_job_detail(self):
|
||||
job = self.job_ops_east_run
|
||||
url = reverse('main:job_detail', args=(job.pk,))
|
||||
url = reverse('api:job_detail', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -668,7 +668,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
def test_put_job_detail(self):
|
||||
job = self.job_ops_west_run
|
||||
url = reverse('main:job_detail', args=(job.pk,))
|
||||
url = reverse('api:job_detail', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url, methods=('put',))# 'patch'))
|
||||
@@ -697,7 +697,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
# FIXME: Check with other credentials and readonly fields.
|
||||
|
||||
def _test_mainline(self):
|
||||
url = reverse('main:job_list')
|
||||
url = reverse('api:job_list')
|
||||
|
||||
# job templates
|
||||
data = self.get('/api/v1/job_templates/', expect=401)
|
||||
@@ -769,7 +769,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
def test_job_start(self):
|
||||
job = self.job_ops_east_run
|
||||
url = reverse('main:job_start', args=(job.pk,))
|
||||
url = reverse('api:job_start', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -797,7 +797,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
# Test with a job that prompts for SSH and sudo passwords.
|
||||
job = self.job_sup_run
|
||||
url = reverse('main:job_start', args=(job.pk,))
|
||||
url = reverse('api:job_start', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
self.assertTrue(response['can_start'])
|
||||
@@ -821,7 +821,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
credential=self.cred_greg,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
url = reverse('main:job_start', args=(job.pk,))
|
||||
url = reverse('api:job_start', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
self.assertTrue(response['can_start'])
|
||||
@@ -841,7 +841,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
credential=self.cred_greg,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
url = reverse('main:job_start', args=(job.pk,))
|
||||
url = reverse('api:job_start', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
self.assertTrue(response['can_start'])
|
||||
@@ -858,7 +858,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
def test_job_cancel(self):
|
||||
job = self.job_ops_east_run
|
||||
url = reverse('main:job_cancel', args=(job.pk,))
|
||||
url = reverse('api:job_cancel', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self.check_invalid_auth(url)
|
||||
@@ -887,7 +887,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
job.start()
|
||||
|
||||
# Check that the job detail has been updated.
|
||||
url = reverse('main:job_detail', args=(job.pk,))
|
||||
url = reverse('api:job_detail', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
self.assertEqual(response['status'], 'successful',
|
||||
@@ -895,7 +895,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
self.assertTrue(response['result_stdout'])
|
||||
|
||||
# Test job events for completed job.
|
||||
url = reverse('main:job_job_events_list', args=(job.pk,))
|
||||
url = reverse('api:job_job_events_list', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = job.job_events.all()
|
||||
@@ -908,13 +908,13 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
for job_event in job.job_events.all():
|
||||
if job_event.host:
|
||||
host_ids.add(job_event.host.pk)
|
||||
url = reverse('main:job_event_detail', args=(job_event.pk,))
|
||||
url = reverse('api:job_event_detail', args=(job_event.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
|
||||
# Also test job event list for each host.
|
||||
for host in Host.objects.filter(pk__in=host_ids):
|
||||
url = reverse('main:host_job_events_list', args=(host.pk,))
|
||||
url = reverse('api:host_job_events_list', args=(host.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = host.job_events.all()
|
||||
@@ -924,7 +924,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
# Test job event list for groups.
|
||||
for group in self.inv_ops_east.groups.all():
|
||||
url = reverse('main:group_job_events_list', args=(group.pk,))
|
||||
url = reverse('api:group_job_events_list', args=(group.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = group.job_events.all()
|
||||
@@ -933,7 +933,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
self.check_list_ids(response, qs)
|
||||
|
||||
# Test global job event list.
|
||||
url = reverse('main:job_event_list')
|
||||
url = reverse('api:job_event_list')
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = JobEvent.objects.all()
|
||||
@@ -942,7 +942,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
self.check_list_ids(response, qs)
|
||||
|
||||
# Test job host summaries for completed job.
|
||||
url = reverse('main:job_job_host_summaries_list', args=(job.pk,))
|
||||
url = reverse('api:job_job_host_summaries_list', args=(job.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = job.job_host_summaries.all()
|
||||
@@ -956,14 +956,14 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
# Test individual job host summary records.
|
||||
for job_host_summary in job.job_host_summaries.all():
|
||||
url = reverse('main:job_host_summary_detail',
|
||||
url = reverse('api:job_host_summary_detail',
|
||||
args=(job_host_summary.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
|
||||
# Test job host summaries for each host.
|
||||
for host in Host.objects.filter(pk__in=host_ids):
|
||||
url = reverse('main:host_job_host_summaries_list', args=(host.pk,))
|
||||
url = reverse('api:host_job_host_summaries_list', args=(host.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = host.job_host_summaries.all()
|
||||
@@ -973,7 +973,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
# Test job host summaries for groups.
|
||||
for group in self.inv_ops_east.groups.all():
|
||||
url = reverse('main:group_job_host_summaries_list', args=(group.pk,))
|
||||
url = reverse('api:group_job_host_summaries_list', args=(group.pk,))
|
||||
with self.current_user(self.user_sue):
|
||||
response = self.get(url)
|
||||
qs = group.job_host_summaries.all()
|
||||
@@ -1115,7 +1115,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
job_template = jt
|
||||
break
|
||||
self.assertTrue(job_template)
|
||||
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||
url = reverse('api:job_template_callback', args=(job_template.pk,))
|
||||
data = dict(host_config_key=job_template.host_config_key)
|
||||
|
||||
# Test a POST to start a new job.
|
||||
@@ -1254,7 +1254,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
job_template = jt
|
||||
break
|
||||
self.assertTrue(job_template)
|
||||
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||
url = reverse('api:job_template_callback', args=(job_template.pk,))
|
||||
data = dict(host_config_key=job_template.host_config_key)
|
||||
|
||||
# Should get an error when multiple hosts match to the same IP.
|
||||
@@ -1278,7 +1278,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
job_template = jt
|
||||
break
|
||||
self.assertTrue(job_template)
|
||||
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||
url = reverse('api:job_template_callback', args=(job_template.pk,))
|
||||
data = dict(host_config_key=job_template.host_config_key)
|
||||
|
||||
# Test POST to start a new job when the template has no credential.
|
||||
@@ -1300,7 +1300,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
job_template = jt
|
||||
break
|
||||
self.assertTrue(job_template)
|
||||
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||
url = reverse('api:job_template_callback', args=(job_template.pk,))
|
||||
data = dict(host_config_key=job_template.host_config_key)
|
||||
|
||||
# Test POST to start a new job when the credential would require user
|
||||
|
||||
@@ -14,7 +14,7 @@ from awx.main.tests.base import BaseTest
|
||||
class OrganizationsTest(BaseTest):
|
||||
|
||||
def collection(self):
|
||||
return reverse('main:organization_list')
|
||||
return reverse('api:organization_list')
|
||||
|
||||
def setUp(self):
|
||||
super(OrganizationsTest, self).setUp()
|
||||
@@ -53,7 +53,7 @@ class OrganizationsTest(BaseTest):
|
||||
self.organizations[1].admins.add(self.normal_django_user)
|
||||
|
||||
def test_get_organization_list(self):
|
||||
url = reverse('main:organization_list')
|
||||
url = reverse('api:organization_list')
|
||||
|
||||
# no credentials == 401
|
||||
self.options(url, expect=401)
|
||||
@@ -280,7 +280,7 @@ class OrganizationsTest(BaseTest):
|
||||
|
||||
def test_post_item_subobjects_users(self):
|
||||
|
||||
url = reverse('main:organization_users_list', args=(self.organizations[1].pk,))
|
||||
url = reverse('api:organization_users_list', args=(self.organizations[1].pk,))
|
||||
users = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEqual(users['count'], 2)
|
||||
self.post(url, dict(id=self.normal_django_user.pk), expect=204, auth=self.get_normal_credentials())
|
||||
@@ -293,7 +293,7 @@ class OrganizationsTest(BaseTest):
|
||||
# post a completely new user to verify we can add users to the subcollection directly
|
||||
new_user = dict(username='NewUser9000')
|
||||
which_org = self.normal_django_user.admin_of_organizations.all()[0]
|
||||
url = reverse('main:organization_users_list', args=(which_org.pk,))
|
||||
url = reverse('api:organization_users_list', args=(which_org.pk,))
|
||||
posted = self.post(url, new_user, expect=201, auth=self.get_normal_credentials())
|
||||
|
||||
all_users = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
@@ -301,7 +301,7 @@ class OrganizationsTest(BaseTest):
|
||||
|
||||
def test_post_item_subobjects_admins(self):
|
||||
|
||||
url = reverse('main:organization_admins_list', args=(self.organizations[1].pk,))
|
||||
url = reverse('api:organization_admins_list', args=(self.organizations[1].pk,))
|
||||
admins = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEqual(admins['count'], 2)
|
||||
self.post(url, dict(id=self.other_django_user.pk), expect=204, auth=self.get_normal_credentials())
|
||||
@@ -395,7 +395,7 @@ class OrganizationsTest(BaseTest):
|
||||
self.delete(self.collection(), expect=405, auth=self.get_super_credentials())
|
||||
|
||||
def test_invalid_post_data(self):
|
||||
url = reverse('main:organization_list')
|
||||
url = reverse('api:organization_list')
|
||||
# API should gracefully handle data of an invalid type.
|
||||
self.post(url, expect=400, data=None, auth=self.get_super_credentials())
|
||||
self.post(url, expect=400, data=99, auth=self.get_super_credentials())
|
||||
@@ -403,7 +403,7 @@ class OrganizationsTest(BaseTest):
|
||||
self.post(url, expect=400, data=3.14, auth=self.get_super_credentials())
|
||||
self.post(url, expect=400, data=True, auth=self.get_super_credentials())
|
||||
self.post(url, expect=400, data=[1,2,3], auth=self.get_super_credentials())
|
||||
url = reverse('main:organization_users_list', args=(self.organizations[0].pk,))
|
||||
url = reverse('api:organization_users_list', args=(self.organizations[0].pk,))
|
||||
self.post(url, expect=400, data=None, auth=self.get_super_credentials())
|
||||
self.post(url, expect=400, data=99, auth=self.get_super_credentials())
|
||||
self.post(url, expect=400, data='abcd', auth=self.get_super_credentials())
|
||||
|
||||
@@ -38,7 +38,7 @@ class ProjectsTest(BaseTest):
|
||||
# tests for users, projects, and teams
|
||||
|
||||
def collection(self):
|
||||
return reverse('main:project_list')
|
||||
return reverse('api:project_list')
|
||||
|
||||
def setUp(self):
|
||||
super(ProjectsTest, self).setUp()
|
||||
@@ -148,7 +148,7 @@ class ProjectsTest(BaseTest):
|
||||
|
||||
def test_api_config(self):
|
||||
# superuser can read all config data.
|
||||
url = reverse('main:api_v1_config_view')
|
||||
url = reverse('api:api_v1_config_view')
|
||||
response = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertTrue('project_base_dir' in response)
|
||||
self.assertEqual(response['project_base_dir'], settings.PROJECTS_ROOT)
|
||||
@@ -188,7 +188,7 @@ class ProjectsTest(BaseTest):
|
||||
# PROJECTS - LISTING
|
||||
|
||||
# can get projects list
|
||||
projects = reverse('main:project_list')
|
||||
projects = reverse('api:project_list')
|
||||
# invalid auth
|
||||
self.get(projects, expect=401)
|
||||
self.get(projects, expect=401, auth=self.get_invalid_credentials())
|
||||
@@ -217,7 +217,7 @@ class ProjectsTest(BaseTest):
|
||||
auth=self.get_super_credentials())
|
||||
|
||||
# can edit project using same local path.
|
||||
project_detail = reverse('main:project_detail', args=(response['id'],))
|
||||
project_detail = reverse('api:project_detail', args=(response['id'],))
|
||||
project_data = self.get(project_detail, expect=200,
|
||||
auth=self.get_super_credentials())
|
||||
response = self.put(project_detail, project_data, expect=200,
|
||||
@@ -235,7 +235,7 @@ class ProjectsTest(BaseTest):
|
||||
|
||||
# =====================================================================
|
||||
# PROJECTS - ACCESS
|
||||
project = reverse('main:project_detail', args=(self.projects[3].pk,))
|
||||
project = reverse('api:project_detail', args=(self.projects[3].pk,))
|
||||
self.get(project, expect=200, auth=self.get_super_credentials())
|
||||
self.get(project, expect=200, auth=self.get_normal_credentials())
|
||||
self.get(project, expect=200, auth=self.get_other_credentials())
|
||||
@@ -246,18 +246,18 @@ class ProjectsTest(BaseTest):
|
||||
self.get(project, expect=404, auth=self.get_normal_credentials())
|
||||
|
||||
# can list playbooks for projects
|
||||
proj_playbooks = reverse('main:project_playbooks', args=(self.projects[2].pk,))
|
||||
proj_playbooks = reverse('api:project_playbooks', args=(self.projects[2].pk,))
|
||||
got = self.get(proj_playbooks, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEqual(got, self.projects[2].playbooks)
|
||||
|
||||
# can list member organizations for projects
|
||||
proj_orgs = reverse('main:project_organizations_list', args=(self.projects[0].pk,))
|
||||
proj_orgs = reverse('api:project_organizations_list', args=(self.projects[0].pk,))
|
||||
# only usable as superuser
|
||||
got = self.get(proj_orgs, expect=200, auth=self.get_normal_credentials())
|
||||
got = self.get(proj_orgs, expect=200, auth=self.get_super_credentials())
|
||||
self.get(proj_orgs, expect=403, auth=self.get_other_credentials())
|
||||
self.assertEquals(got['count'], 1)
|
||||
self.assertEquals(got['results'][0]['url'], reverse('main:organization_detail', args=(self.organizations[0].pk,)))
|
||||
self.assertEquals(got['results'][0]['url'], reverse('api:organization_detail', args=(self.organizations[0].pk,)))
|
||||
|
||||
# post to create new org associated with this project.
|
||||
self.post(proj_orgs, data={'name': 'New Org'}, expect=201, auth=self.get_super_credentials())
|
||||
@@ -267,8 +267,8 @@ class ProjectsTest(BaseTest):
|
||||
# =====================================================================
|
||||
# TEAMS
|
||||
|
||||
all_teams = reverse('main:team_list')
|
||||
team1 = reverse('main:team_detail', args=(self.team1.pk,))
|
||||
all_teams = reverse('api:team_list')
|
||||
team1 = reverse('api:team_detail', args=(self.team1.pk,))
|
||||
|
||||
# can list teams
|
||||
got = self.get(all_teams, expect=200, auth=self.get_super_credentials())
|
||||
@@ -277,7 +277,7 @@ class ProjectsTest(BaseTest):
|
||||
|
||||
# can get teams
|
||||
got = self.get(team1, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(got['url'], reverse('main:team_detail', args=(self.team1.pk,)))
|
||||
self.assertEquals(got['url'], reverse('api:team_detail', args=(self.team1.pk,)))
|
||||
got = self.get(team1, expect=200, auth=self.get_normal_credentials())
|
||||
got = self.get(team1, expect=403, auth=self.get_other_credentials())
|
||||
self.team1.users.add(User.objects.get(username='other'))
|
||||
@@ -303,7 +303,7 @@ class ProjectsTest(BaseTest):
|
||||
url5 = posted1['url']
|
||||
|
||||
new_team = Team.objects.create(name='newTeam4', organization=self.organizations[1])
|
||||
url = reverse('main:team_detail', args=(new_team.pk,))
|
||||
url = reverse('api:team_detail', args=(new_team.pk,))
|
||||
|
||||
# can delete teams
|
||||
self.delete(url, expect=401)
|
||||
@@ -316,7 +316,7 @@ class ProjectsTest(BaseTest):
|
||||
# ORGANIZATION TEAMS
|
||||
|
||||
# can list organization teams (filtered by user) -- this is an org admin function
|
||||
org_teams = reverse('main:organization_teams_list', args=(self.organizations[1].pk,))
|
||||
org_teams = reverse('api:organization_teams_list', args=(self.organizations[1].pk,))
|
||||
data1 = self.get(org_teams, expect=401)
|
||||
data2 = self.get(org_teams, expect=403, auth=self.get_nobody_credentials())
|
||||
data3 = self.get(org_teams, expect=403, auth=self.get_other_credentials())
|
||||
@@ -346,7 +346,7 @@ class ProjectsTest(BaseTest):
|
||||
# TEAM PROJECTS
|
||||
|
||||
team = Team.objects.filter(active=True, organization__pk=self.organizations[1].pk)[0]
|
||||
team_projects = reverse('main:team_projects_list', args=(team.pk,))
|
||||
team_projects = reverse('api:team_projects_list', args=(team.pk,))
|
||||
|
||||
p1 = self.projects[0]
|
||||
team.projects.add(p1)
|
||||
@@ -363,7 +363,7 @@ class ProjectsTest(BaseTest):
|
||||
# TEAMS USER MEMBERSHIP
|
||||
|
||||
team = Team.objects.filter(active=True, organization__pk=self.organizations[1].pk)[0]
|
||||
team_users = reverse('main:team_users_list', args=(team.pk,))
|
||||
team_users = reverse('api:team_users_list', args=(team.pk,))
|
||||
for x in team.users.all():
|
||||
team.users.remove(x)
|
||||
team.save()
|
||||
@@ -377,7 +377,7 @@ class ProjectsTest(BaseTest):
|
||||
self.get(team_users, expect=200, auth=self.get_super_credentials())
|
||||
|
||||
# can add users to teams (but only users I can see)
|
||||
all_users = self.get(reverse('main:user_list'), expect=200, auth=self.get_normal_credentials())
|
||||
all_users = self.get(reverse('api:user_list'), expect=200, auth=self.get_normal_credentials())
|
||||
for x in all_users['results']:
|
||||
self.post(team_users, data=x, expect=403, auth=self.get_nobody_credentials())
|
||||
self.post(team_users, data=x, expect=204, auth=self.get_normal_credentials())
|
||||
@@ -397,7 +397,7 @@ class ProjectsTest(BaseTest):
|
||||
|
||||
# from a user, can see what teams they are on (related resource)
|
||||
other = User.objects.get(username = 'other')
|
||||
url = reverse('main:user_teams_list', args=(other.pk,))
|
||||
url = reverse('api:user_teams_list', args=(other.pk,))
|
||||
self.get(url, expect=401)
|
||||
self.get(url, expect=401, auth=self.get_invalid_credentials())
|
||||
self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
@@ -413,7 +413,7 @@ class ProjectsTest(BaseTest):
|
||||
# =====================================================================
|
||||
# USER PROJECTS
|
||||
|
||||
url = reverse('main:user_projects_list', args=(other.pk,))
|
||||
url = reverse('api:user_projects_list', args=(other.pk,))
|
||||
|
||||
# from a user, can see what projects they can see based on team association
|
||||
# though this resource doesn't do anything else
|
||||
@@ -427,8 +427,8 @@ class ProjectsTest(BaseTest):
|
||||
# =====================================================================
|
||||
# CREDENTIALS
|
||||
|
||||
other_creds = reverse('main:user_credentials_list', args=(other.pk,))
|
||||
team_creds = reverse('main:team_credentials_list', args=(team.pk,))
|
||||
other_creds = reverse('api:user_credentials_list', args=(other.pk,))
|
||||
team_creds = reverse('api:team_credentials_list', args=(team.pk,))
|
||||
|
||||
new_credentials = dict(
|
||||
name = 'credential',
|
||||
@@ -476,7 +476,7 @@ class ProjectsTest(BaseTest):
|
||||
self.get(team_creds, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# Check /api/v1/credentials (GET)
|
||||
url = reverse('main:credential_list')
|
||||
url = reverse('api:credential_list')
|
||||
with self.current_user(self.super_django_user):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
@@ -498,8 +498,8 @@ class ProjectsTest(BaseTest):
|
||||
d_cred_user = dict(id=cred_user.pk, name='x', sudo_password='blippy', user=cred_user.user.pk)
|
||||
d_cred_user2 = dict(id=cred_user.pk, name='x', sudo_password='blippy', user=self.super_django_user.pk)
|
||||
d_cred_team = dict(id=cred_team.pk, name='x', sudo_password='blippy', team=cred_team.team.pk)
|
||||
edit_creds1 = reverse('main:credential_detail', args=(cred_user.pk,))
|
||||
edit_creds2 = reverse('main:credential_detail', args=(cred_team.pk,))
|
||||
edit_creds1 = reverse('api:credential_detail', args=(cred_user.pk,))
|
||||
edit_creds2 = reverse('api:credential_detail', args=(cred_team.pk,))
|
||||
|
||||
self.put(edit_creds1, data=d_cred_user, expect=401)
|
||||
self.put(edit_creds1, data=d_cred_user, expect=401, auth=self.get_invalid_credentials())
|
||||
@@ -517,13 +517,13 @@ class ProjectsTest(BaseTest):
|
||||
self.put(edit_creds2, data=d_cred_team, expect=403, auth=self.get_other_credentials())
|
||||
|
||||
cred_put_t['disassociate'] = 1
|
||||
team_url = reverse('main:team_credentials_list', args=(cred_put_t['team'],))
|
||||
team_url = reverse('api:team_credentials_list', args=(cred_put_t['team'],))
|
||||
self.post(team_url, data=cred_put_t, expect=204, auth=self.get_normal_credentials())
|
||||
|
||||
# can remove credentials from a user (via disassociate) - this will delete the credential.
|
||||
cred_put_u['disassociate'] = 1
|
||||
url = cred_put_u['url']
|
||||
user_url = reverse('main:user_credentials_list', args=(cred_put_u['user'],))
|
||||
user_url = reverse('api:user_credentials_list', args=(cred_put_u['user'],))
|
||||
self.post(user_url, data=cred_put_u, expect=204, auth=self.get_normal_credentials())
|
||||
|
||||
# can delete a credential directly -- probably won't be used too often
|
||||
@@ -560,13 +560,13 @@ class ProjectsTest(BaseTest):
|
||||
permission_type=PERM_INVENTORY_DEPLOY
|
||||
)
|
||||
|
||||
url = reverse('main:user_permissions_list', args=(user.pk,))
|
||||
url = reverse('api:user_permissions_list', args=(user.pk,))
|
||||
posted = self.post(url, user_permission, expect=201, auth=self.get_super_credentials())
|
||||
url2 = posted['url']
|
||||
got = self.get(url2, expect=200, auth=self.get_other_credentials())
|
||||
|
||||
# cannot add permissions that apply to both team and user
|
||||
url = reverse('main:user_permissions_list', args=(user.pk,))
|
||||
url = reverse('api:user_permissions_list', args=(user.pk,))
|
||||
user_permission['name'] = 'user permission 2'
|
||||
user_permission['team'] = team.pk
|
||||
self.post(url, user_permission, expect=400, auth=self.get_super_credentials())
|
||||
@@ -584,26 +584,26 @@ class ProjectsTest(BaseTest):
|
||||
self.post(url, user_permission, expect=400, auth=self.get_super_credentials())
|
||||
|
||||
# can add permissions on a team
|
||||
url = reverse('main:team_permissions_list', args=(team.pk,))
|
||||
url = reverse('api:team_permissions_list', args=(team.pk,))
|
||||
posted = self.post(url, team_permission, expect=201, auth=self.get_super_credentials())
|
||||
url2 = posted['url']
|
||||
# check we can get that permission back
|
||||
got = self.get(url2, expect=200, auth=self.get_other_credentials())
|
||||
|
||||
# cannot add permissions that apply to both team and user
|
||||
url = reverse('main:team_permissions_list', args=(team.pk,))
|
||||
url = reverse('api:team_permissions_list', args=(team.pk,))
|
||||
team_permission['name'] += '2'
|
||||
team_permission['user'] = user.pk
|
||||
self.post(url, team_permission, expect=400, auth=self.get_super_credentials())
|
||||
|
||||
# can list permissions on a user
|
||||
url = reverse('main:user_permissions_list', args=(user.pk,))
|
||||
url = reverse('api:user_permissions_list', args=(user.pk,))
|
||||
got = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
got = self.get(url, expect=200, auth=self.get_other_credentials())
|
||||
got = self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# can list permissions on a team
|
||||
url = reverse('main:team_permissions_list', args=(team.pk,))
|
||||
url = reverse('api:team_permissions_list', args=(team.pk,))
|
||||
got = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
got = self.get(url, expect=200, auth=self.get_other_credentials())
|
||||
got = self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
@@ -1335,7 +1335,7 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
||||
scm_username='nobody',
|
||||
scm_password='ASK',
|
||||
)
|
||||
url = reverse('main:project_update_view', args=(project.pk,))
|
||||
url = reverse('api:project_update_view', args=(project.pk,))
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue(response['can_update'])
|
||||
@@ -1359,7 +1359,7 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
||||
scm_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
||||
scm_key_unlock='ASK',
|
||||
)
|
||||
url = reverse('main:project_update_view', args=(project.pk,))
|
||||
url = reverse('api:project_update_view', args=(project.pk,))
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue(response['can_update'])
|
||||
|
||||
@@ -23,7 +23,7 @@ __all__ = ['UsersTest', 'LdapTest']
|
||||
class UsersTest(BaseTest):
|
||||
|
||||
def collection(self):
|
||||
return reverse('main:user_list')
|
||||
return reverse('api:user_list')
|
||||
|
||||
def setUp(self):
|
||||
super(UsersTest, self).setUp()
|
||||
@@ -34,7 +34,7 @@ class UsersTest(BaseTest):
|
||||
self.organizations[0].users.add(self.normal_django_user)
|
||||
|
||||
def test_only_super_user_or_org_admin_can_add_users(self):
|
||||
url = reverse('main:user_list')
|
||||
url = reverse('api:user_list')
|
||||
new_user = dict(username='blippy')
|
||||
new_user2 = dict(username='blippy2')
|
||||
self.post(url, expect=401, data=new_user, auth=None)
|
||||
@@ -46,7 +46,7 @@ class UsersTest(BaseTest):
|
||||
self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials())
|
||||
|
||||
def test_auth_token_login(self):
|
||||
auth_token_url = reverse('main:auth_token_view')
|
||||
auth_token_url = reverse('api:auth_token_view')
|
||||
|
||||
# Always returns a 405 for any GET request, regardless of credentials.
|
||||
self.get(auth_token_url, expect=405, auth=None)
|
||||
@@ -69,7 +69,7 @@ class UsersTest(BaseTest):
|
||||
auth_token = response['token']
|
||||
|
||||
# Verify we can access our own user information with the auth token.
|
||||
response = self.get(reverse('main:user_me_list'), expect=200,
|
||||
response = self.get(reverse('api:user_me_list'), expect=200,
|
||||
auth=auth_token)
|
||||
self.assertEquals(response['results'][0]['username'], 'normal')
|
||||
self.assertEquals(response['count'], 1)
|
||||
@@ -77,7 +77,7 @@ class UsersTest(BaseTest):
|
||||
# If we simulate a different remote address, should not be able to use
|
||||
# the first auth token.
|
||||
remote_addr = '127.0.0.2'
|
||||
response = self.get(reverse('main:user_me_list'), expect=401,
|
||||
response = self.get(reverse('api:user_me_list'), expect=401,
|
||||
auth=auth_token, remote_addr=remote_addr)
|
||||
self.assertEqual(response['detail'], 'Invalid token')
|
||||
|
||||
@@ -97,28 +97,28 @@ class UsersTest(BaseTest):
|
||||
|
||||
# Verify we can access our own user information with the second auth
|
||||
# token from the other remote address.
|
||||
response = self.get(reverse('main:user_me_list'), expect=200,
|
||||
response = self.get(reverse('api:user_me_list'), expect=200,
|
||||
auth=auth_token2, remote_addr=remote_addr)
|
||||
self.assertEquals(response['results'][0]['username'], 'normal')
|
||||
self.assertEquals(response['count'], 1)
|
||||
|
||||
# The second auth token also can't be used from the first address, but
|
||||
# the first auth token is still valid from its address.
|
||||
response = self.get(reverse('main:user_me_list'), expect=401,
|
||||
response = self.get(reverse('api:user_me_list'), expect=401,
|
||||
auth=auth_token2)
|
||||
self.assertEqual(response['detail'], 'Invalid token')
|
||||
response_header = response.response.get('WWW-Authenticate', '')
|
||||
self.assertEqual(response_header.split()[0], 'Token')
|
||||
response = self.get(reverse('main:user_me_list'), expect=200,
|
||||
response = self.get(reverse('api:user_me_list'), expect=200,
|
||||
auth=auth_token)
|
||||
|
||||
# A request without authentication should ask for Basic by default.
|
||||
response = self.get(reverse('main:user_me_list'), expect=401)
|
||||
response = self.get(reverse('api:user_me_list'), expect=401)
|
||||
response_header = response.response.get('WWW-Authenticate', '')
|
||||
self.assertEqual(response_header.split()[0], 'Basic')
|
||||
|
||||
# A request that attempts Basic auth should request Basic auth again.
|
||||
response = self.get(reverse('main:user_me_list'), expect=401,
|
||||
response = self.get(reverse('api:user_me_list'), expect=401,
|
||||
auth=('invalid', 'password'))
|
||||
response_header = response.response.get('WWW-Authenticate', '')
|
||||
self.assertEqual(response_header.split()[0], 'Basic')
|
||||
@@ -126,21 +126,21 @@ class UsersTest(BaseTest):
|
||||
# Invalidate a key (simulate expiration), now token auth should fail
|
||||
# with the first token, but still work with the second.
|
||||
self.normal_django_user.auth_tokens.get(key=auth_token).invalidate()
|
||||
response = self.get(reverse('main:user_me_list'), expect=401,
|
||||
response = self.get(reverse('api:user_me_list'), expect=401,
|
||||
auth=auth_token)
|
||||
self.assertEqual(response['detail'], 'Token is expired')
|
||||
response = self.get(reverse('main:user_me_list'), expect=200,
|
||||
response = self.get(reverse('api:user_me_list'), expect=200,
|
||||
auth=auth_token2, remote_addr=remote_addr)
|
||||
|
||||
# Token auth should be denied if the user is inactive.
|
||||
self.normal_django_user.mark_inactive()
|
||||
response = self.get(reverse('main:user_me_list'), expect=401,
|
||||
response = self.get(reverse('api:user_me_list'), expect=401,
|
||||
auth=auth_token2, remote_addr=remote_addr)
|
||||
self.assertEqual(response['detail'], 'User inactive or deleted')
|
||||
|
||||
def test_ordinary_user_can_modify_some_fields_about_himself_but_not_all_and_passwords_work(self):
|
||||
|
||||
detail_url = reverse('main:user_detail', args=(self.other_django_user.pk,))
|
||||
detail_url = reverse('api:user_detail', args=(self.other_django_user.pk,))
|
||||
data = self.get(detail_url, expect=200, auth=self.get_other_credentials())
|
||||
|
||||
# can't change first_name, last_name, etc
|
||||
@@ -182,7 +182,7 @@ class UsersTest(BaseTest):
|
||||
def test_user_created_with_password_can_login(self):
|
||||
|
||||
# this is something an org admin can do...
|
||||
url = reverse('main:user_list')
|
||||
url = reverse('api:user_list')
|
||||
data = dict(username='username', password='password')
|
||||
data2 = dict(username='username2', password='password2')
|
||||
data = self.post(url, expect=201, data=data, auth=self.get_normal_credentials())
|
||||
@@ -206,16 +206,16 @@ class UsersTest(BaseTest):
|
||||
self.assertTrue(orig.username != 'change')
|
||||
|
||||
def test_password_not_shown_in_get_operations_for_list_or_detail(self):
|
||||
url = reverse('main:user_detail', args=(self.super_django_user.pk,))
|
||||
url = reverse('api:user_detail', args=(self.super_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertTrue('password' not in data)
|
||||
|
||||
url = reverse('main:user_list')
|
||||
url = reverse('api:user_list')
|
||||
data = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertTrue('password' not in data['results'][0])
|
||||
|
||||
def test_user_list_filtered(self):
|
||||
url = reverse('main:user_list')
|
||||
url = reverse('api:user_list')
|
||||
data3 = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(data3['count'], 4)
|
||||
data2 = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
@@ -225,22 +225,22 @@ class UsersTest(BaseTest):
|
||||
|
||||
def test_super_user_can_delete_a_user_but_only_marked_inactive(self):
|
||||
user_pk = self.normal_django_user.pk
|
||||
url = reverse('main:user_detail', args=(user_pk,))
|
||||
url = reverse('api:user_detail', args=(user_pk,))
|
||||
data = self.delete(url, expect=204, auth=self.get_super_credentials())
|
||||
data = self.get(url, expect=404, auth=self.get_super_credentials())
|
||||
obj = User.objects.get(pk=user_pk)
|
||||
self.assertEquals(obj.is_active, False)
|
||||
|
||||
def test_non_org_admin_user_cannot_delete_any_user_including_himself(self):
|
||||
url1 = reverse('main:user_detail', args=(self.super_django_user.pk,))
|
||||
url2 = reverse('main:user_detail', args=(self.normal_django_user.pk,))
|
||||
url3 = reverse('main:user_detail', args=(self.other_django_user.pk,))
|
||||
url1 = reverse('api:user_detail', args=(self.super_django_user.pk,))
|
||||
url2 = reverse('api:user_detail', args=(self.normal_django_user.pk,))
|
||||
url3 = reverse('api:user_detail', args=(self.other_django_user.pk,))
|
||||
data = self.delete(url1, expect=403, auth=self.get_other_credentials())
|
||||
data = self.delete(url2, expect=403, auth=self.get_other_credentials())
|
||||
data = self.delete(url3, expect=403, auth=self.get_other_credentials())
|
||||
|
||||
def test_there_exists_an_obvious_url_where_a_user_may_find_his_user_record(self):
|
||||
url = reverse('main:user_me_list')
|
||||
url = reverse('api:user_me_list')
|
||||
data = self.get(url, expect=401, auth=None)
|
||||
data = self.get(url, expect=401, auth=self.get_invalid_credentials())
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
@@ -254,7 +254,7 @@ class UsersTest(BaseTest):
|
||||
self.assertEquals(data['count'], 1)
|
||||
|
||||
def test_superuser_can_change_admin_only_fields_about_himself(self):
|
||||
url = reverse('main:user_detail', args=(self.super_django_user.pk,))
|
||||
url = reverse('api:user_detail', args=(self.super_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
data['username'] += '2'
|
||||
data['first_name'] += ' Awesome'
|
||||
@@ -267,7 +267,7 @@ class UsersTest(BaseTest):
|
||||
def test_user_related_resources(self):
|
||||
|
||||
# organizations the user is a member of, should be 1
|
||||
url = reverse('main:user_organizations_list',
|
||||
url = reverse('api:user_organizations_list',
|
||||
args=(self.normal_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 1)
|
||||
@@ -280,7 +280,7 @@ class UsersTest(BaseTest):
|
||||
data = self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# organizations the user is an admin of, should be 1
|
||||
url = reverse('main:user_admin_of_organizations_list',
|
||||
url = reverse('api:user_admin_of_organizations_list',
|
||||
args=(self.normal_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 1)
|
||||
@@ -293,7 +293,7 @@ class UsersTest(BaseTest):
|
||||
data = self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# teams the user is on, should be 0
|
||||
url = reverse('main:user_teams_list', args=(self.normal_django_user.pk,))
|
||||
url = reverse('api:user_teams_list', args=(self.normal_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 0)
|
||||
# also accessible via superuser
|
||||
@@ -305,15 +305,15 @@ class UsersTest(BaseTest):
|
||||
data = self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# verify org admin can still read other user data too
|
||||
url = reverse('main:user_organizations_list',
|
||||
url = reverse('api:user_organizations_list',
|
||||
args=(self.other_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 1)
|
||||
url = reverse('main:user_admin_of_organizations_list',
|
||||
url = reverse('api:user_admin_of_organizations_list',
|
||||
args=(self.other_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 0)
|
||||
url = reverse('main:user_teams_list',
|
||||
url = reverse('api:user_teams_list',
|
||||
args=(self.other_django_user.pk,))
|
||||
data = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 0)
|
||||
@@ -323,7 +323,7 @@ class UsersTest(BaseTest):
|
||||
# FIXME: add test that shows posting a projects w/o id to /organizations/2/projects/ can create a new one & associate
|
||||
|
||||
def test_user_list_ordering(self):
|
||||
base_url = reverse('main:user_list')
|
||||
base_url = reverse('api:user_list')
|
||||
base_qs = User.objects.distinct()
|
||||
|
||||
# Check list view with ordering by name.
|
||||
@@ -351,7 +351,7 @@ class UsersTest(BaseTest):
|
||||
|
||||
def test_user_list_filtering(self):
|
||||
# Also serves as general-purpose testing for custom API filters.
|
||||
base_url = reverse('main:user_list')
|
||||
base_url = reverse('api:user_list')
|
||||
base_qs = User.objects.distinct()
|
||||
|
||||
# Filter by username.
|
||||
@@ -651,7 +651,7 @@ class UsersTest(BaseTest):
|
||||
self.check_get_list(url, self.super_django_user, base_qs, expect=400)
|
||||
|
||||
def test_user_list_pagination(self):
|
||||
base_url = reverse('main:user_list')
|
||||
base_url = reverse('api:user_list')
|
||||
base_qs = User.objects.distinct()
|
||||
|
||||
# Check list view with page size of 1.
|
||||
@@ -682,7 +682,7 @@ class UsersTest(BaseTest):
|
||||
# limit=0)
|
||||
|
||||
def test_user_list_searching(self):
|
||||
base_url = reverse('main:user_list')
|
||||
base_url = reverse('api:user_list')
|
||||
base_qs = User.objects.distinct()
|
||||
|
||||
# Check search query parameter.
|
||||
@@ -826,12 +826,12 @@ class LdapTest(BaseTest):
|
||||
self.use_test_setting(name)
|
||||
user = self.check_login()
|
||||
self.setup_users()
|
||||
url = reverse('main:api_v1_config_view')
|
||||
url = reverse('api:api_v1_config_view')
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
user_ldap_fields = response.get('user_ldap_fields', [])
|
||||
self.assertTrue(user_ldap_fields)
|
||||
url = reverse('main:user_detail', args=(user.pk,))
|
||||
url = reverse('api:user_detail', args=(user.pk,))
|
||||
for user_field in user_ldap_fields:
|
||||
with self.current_user(self.super_django_user):
|
||||
data = self.get(url, expect=200)
|
||||
|
||||
169
awx/main/urls.py
169
awx/main/urls.py
@@ -1,170 +1,7 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import include, patterns, url as original_url
|
||||
from django.conf import settings
|
||||
from django.conf.urls import *
|
||||
|
||||
def url(regex, view, kwargs=None, name=None, prefix=''):
|
||||
# Set default name from view name (if a string).
|
||||
if isinstance(view, basestring) and name is None:
|
||||
name = view
|
||||
return original_url(regex, view, kwargs, name, prefix)
|
||||
|
||||
organization_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'organization_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'organization_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/users/$', 'organization_users_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/admins/$', 'organization_admins_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventories/$', 'organization_inventories_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'organization_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'organization_teams_list'),
|
||||
)
|
||||
|
||||
user_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'user_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'user_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'user_teams_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/organizations/$', 'user_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/admin_of_organizations/$', 'user_admin_of_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'user_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/credentials/$', 'user_credentials_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/permissions/$', 'user_permissions_list'),
|
||||
)
|
||||
|
||||
project_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'project_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'project_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/playbooks/$', 'project_playbooks'),
|
||||
url(r'^(?P<pk>[0-9]+)/organizations/$', 'project_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'project_teams_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/update/$', 'project_update_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/project_updates/$', 'project_updates_list'),
|
||||
)
|
||||
|
||||
project_update_urls = patterns('awx.main.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'project_update_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'project_update_cancel'),
|
||||
)
|
||||
|
||||
team_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'team_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'team_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'team_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/users/$', 'team_users_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/credentials/$', 'team_credentials_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/permissions/$', 'team_permissions_list'),
|
||||
)
|
||||
|
||||
inventory_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'inventory_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/root_groups/$', 'inventory_root_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'inventory_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/script/$', 'inventory_script_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/tree/$', 'inventory_tree_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'inventory_inventory_sources_list'),
|
||||
)
|
||||
|
||||
host_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'host_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'host_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'host_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/groups/$', 'host_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/all_groups/$', 'host_all_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/', 'host_job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summaries_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'host_inventory_sources_list'),
|
||||
)
|
||||
|
||||
group_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'group_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'group_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/children/$', 'group_children_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'group_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/all_hosts/$', 'group_all_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'group_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/$', 'group_job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'group_job_host_summaries_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/potential_children/$', 'group_potential_children_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'group_inventory_sources_list'),
|
||||
)
|
||||
|
||||
inventory_source_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'inventory_source_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_source_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/update/$', 'inventory_source_update_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventory_updates/$', 'inventory_source_updates_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_source_groups_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_source_hosts_list'),
|
||||
)
|
||||
|
||||
inventory_update_urls = patterns('awx.main.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_update_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'inventory_update_cancel'),
|
||||
)
|
||||
|
||||
credential_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'credential_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'credential_detail'),
|
||||
# See also credentials resources on users/teams.
|
||||
)
|
||||
|
||||
permission_urls = patterns('awx.main.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'permission_detail'),
|
||||
)
|
||||
|
||||
job_template_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'job_template_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
|
||||
)
|
||||
|
||||
job_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'job_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/start/$', 'job_start'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'job_cancel'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'job_job_host_summaries_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/$', 'job_job_events_list'),
|
||||
)
|
||||
|
||||
job_host_summary_urls = patterns('awx.main.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_host_summary_detail'),
|
||||
)
|
||||
|
||||
job_event_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'job_event_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_event_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/children/$', 'job_event_children_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'job_event_hosts_list'),
|
||||
)
|
||||
|
||||
v1_urls = patterns('awx.main.views',
|
||||
url(r'^$', 'api_v1_root_view'),
|
||||
url(r'^config/$', 'api_v1_config_view'),
|
||||
url(r'^authtoken/$', 'auth_token_view'),
|
||||
url(r'^me/$', 'user_me_list'),
|
||||
url(r'^organizations/', include(organization_urls)),
|
||||
url(r'^users/', include(user_urls)),
|
||||
url(r'^projects/', include(project_urls)),
|
||||
url(r'^project_updates/', include(project_update_urls)),
|
||||
url(r'^teams/', include(team_urls)),
|
||||
url(r'^inventories/', include(inventory_urls)),
|
||||
url(r'^hosts/', include(host_urls)),
|
||||
url(r'^groups/', include(group_urls)),
|
||||
url(r'^inventory_sources/', include(inventory_source_urls)),
|
||||
url(r'^inventory_updates/', include(inventory_update_urls)),
|
||||
url(r'^credentials/', include(credential_urls)),
|
||||
url(r'^permissions/', include(permission_urls)),
|
||||
url(r'^job_templates/', include(job_template_urls)),
|
||||
url(r'^jobs/', include(job_urls)),
|
||||
url(r'^job_host_summaries/', include(job_host_summary_urls)),
|
||||
url(r'^job_events/', include(job_event_urls)),
|
||||
)
|
||||
|
||||
urlpatterns = patterns('awx.main.views',
|
||||
url(r'^$', 'api_root_view'),
|
||||
url(r'^v1/', include(v1_urls)),
|
||||
)
|
||||
urlpatterns = patterns()
|
||||
|
||||
1055
awx/main/views.py
1055
awx/main/views.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user