mirror of
https://github.com/ansible/awx.git
synced 2026-01-09 23:12:08 -03:30
Update Django to 1.8 and DRF to 3.3, add new Django migrations, update serializers/pagination/metadata, update browsable API styling.
This commit is contained in:
parent
6242df1a07
commit
60224cdbe4
2
.gitignore
vendored
2
.gitignore
vendored
@ -53,6 +53,7 @@ npm-debug.log
|
||||
/DEBUG
|
||||
|
||||
# Testing
|
||||
.cache
|
||||
.coverage
|
||||
.tox
|
||||
coverage.xml
|
||||
@ -60,6 +61,7 @@ htmlcov
|
||||
pep8.txt
|
||||
scratch
|
||||
testem.log
|
||||
awx/awx_test.sqlite3-journal
|
||||
|
||||
# Mac OS X
|
||||
*.DS_Store
|
||||
|
||||
10
Makefile
10
Makefile
@ -154,7 +154,7 @@ endif
|
||||
|
||||
.PHONY: clean rebase push requirements requirements_dev requirements_jenkins \
|
||||
real-requirements real-requirements_dev real-requirements_jenkins \
|
||||
develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \
|
||||
develop refresh adduser migrate dbchange dbshell runserver celeryd \
|
||||
receiver test test_unit test_coverage coverage_html test_jenkins dev_build \
|
||||
release_build release_clean sdist rpmtar mock-rpm mock-srpm rpm-sign \
|
||||
build-ui sync-ui test-ui build-ui-for-coverage test-ui-for-coverage \
|
||||
@ -280,13 +280,9 @@ refresh: clean requirements_dev version_file develop migrate
|
||||
adduser:
|
||||
$(PYTHON) manage.py createsuperuser
|
||||
|
||||
# Create initial database tables (excluding migrations).
|
||||
syncdb:
|
||||
$(PYTHON) manage.py syncdb --noinput
|
||||
|
||||
# Create database tables and apply any new migrations.
|
||||
migrate: syncdb
|
||||
$(PYTHON) manage.py migrate --noinput
|
||||
migrate:
|
||||
$(PYTHON) manage.py migrate --noinput --fake-initial
|
||||
|
||||
# Run after making changes to the models to create a new migration.
|
||||
dbchange:
|
||||
|
||||
@ -75,10 +75,26 @@ def prepare_env():
|
||||
settings.DATABASES['default'][opt] = os.environ['AWX_TEST_DATABASE_%s' % opt]
|
||||
# Disable capturing all SQL queries in memory when in DEBUG mode.
|
||||
if settings.DEBUG and not getattr(settings, 'SQL_DEBUG', True):
|
||||
from django.db.backends import BaseDatabaseWrapper
|
||||
from django.db.backends.util import CursorWrapper
|
||||
from django.db.backends.base.base import BaseDatabaseWrapper
|
||||
from django.db.backends.utils import CursorWrapper
|
||||
BaseDatabaseWrapper.make_debug_cursor = lambda self, cursor: CursorWrapper(cursor, self)
|
||||
|
||||
# Use the default devserver addr/port defined in settings for runserver.
|
||||
default_addr = getattr(settings, 'DEVSERVER_DEFAULT_ADDR', '127.0.0.1')
|
||||
default_port = getattr(settings, 'DEVSERVER_DEFAULT_PORT', 8000)
|
||||
from django.core.management.commands import runserver as core_runserver
|
||||
original_handle = core_runserver.Command.handle
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not options.get('addrport'):
|
||||
options['addrport'] = '%s:%d' % (default_addr, int(default_port))
|
||||
elif options.get('addrport').isdigit():
|
||||
options['addrport'] = '%s:%d' % (default_addr, int(options['addrport']))
|
||||
return original_handle(self, *args, **options)
|
||||
|
||||
core_runserver.Command.handle = handle
|
||||
|
||||
|
||||
def manage():
|
||||
# Prepare the AWX environment.
|
||||
prepare_env()
|
||||
|
||||
@ -8,9 +8,10 @@ import re
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.fields.related import ForeignObjectRel
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError
|
||||
@ -46,7 +47,7 @@ class TypeFilterBackend(BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
types = None
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
for key, value in request.query_params.items():
|
||||
if key == 'type':
|
||||
if ',' in value:
|
||||
types = value.split(',')
|
||||
@ -107,23 +108,21 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
'last_updated': 'last_job_run',
|
||||
}.get(name, name)
|
||||
|
||||
new_parts.append(name)
|
||||
|
||||
if name == 'pk':
|
||||
field = model._meta.pk
|
||||
else:
|
||||
field = model._meta.get_field_by_name(name)[0]
|
||||
if n < (len(parts) - 2):
|
||||
if getattr(field, 'rel', None):
|
||||
model = field.rel.to
|
||||
else:
|
||||
model = field.model
|
||||
new_parts.append(name)
|
||||
model = getattr(field, 'related_model', None) or field.model
|
||||
|
||||
if parts:
|
||||
new_parts.append(parts[-1])
|
||||
new_lookup = '__'.join(new_parts)
|
||||
return field, new_lookup
|
||||
|
||||
def to_python_related(self, value):
|
||||
value = unicode(value)
|
||||
value = force_text(value)
|
||||
if value.lower() in ('none', 'null'):
|
||||
return None
|
||||
else:
|
||||
@ -134,7 +133,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
return to_python_boolean(value, allow_none=True)
|
||||
elif isinstance(field, models.BooleanField):
|
||||
return to_python_boolean(value)
|
||||
elif isinstance(field, RelatedObject):
|
||||
elif isinstance(field, ForeignObjectRel):
|
||||
return self.to_python_related(value)
|
||||
else:
|
||||
return field.to_python(value)
|
||||
@ -159,12 +158,12 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
# Apply filters specified via QUERY_PARAMS. Each entry in the lists
|
||||
# Apply filters specified via query_params. Each entry in the lists
|
||||
# below is (negate, field, value).
|
||||
and_filters = []
|
||||
or_filters = []
|
||||
chain_filters = []
|
||||
for key, values in request.QUERY_PARAMS.lists():
|
||||
for key, values in request.query_params.lists():
|
||||
if key in self.RESERVED_NAMES:
|
||||
continue
|
||||
|
||||
@ -246,7 +245,7 @@ class OrderByBackend(BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
order_by = None
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
for key, value in request.query_params.items():
|
||||
if key in ('order', 'order_by'):
|
||||
order_by = value
|
||||
if ',' in value:
|
||||
|
||||
@ -12,6 +12,7 @@ from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
# Django REST Framework
|
||||
@ -19,7 +20,6 @@ from rest_framework.authentication import get_authorization_header
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.request import clone_request
|
||||
from rest_framework import status
|
||||
from rest_framework import views
|
||||
|
||||
@ -155,18 +155,6 @@ class APIView(views.APIView):
|
||||
context = self.get_description_context()
|
||||
return render_to_string(template_list, context)
|
||||
|
||||
def metadata(self, request):
|
||||
'''
|
||||
Add version number where view was added to Tower.
|
||||
'''
|
||||
ret = super(APIView, self).metadata(request)
|
||||
added_in_version = '1.2'
|
||||
for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
|
||||
added_in_version = version
|
||||
break
|
||||
ret['added_in_version'] = added_in_version
|
||||
return ret
|
||||
|
||||
class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
# Base class for all model-based views.
|
||||
@ -188,8 +176,12 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
def get_queryset(self):
|
||||
#if hasattr(self.request.user, 'get_queryset'):
|
||||
# return self.request.user.get_queryset(self.model)
|
||||
#else:
|
||||
return super(GenericAPIView, self).get_queryset()
|
||||
if self.queryset is not None:
|
||||
return self.queryset._clone()
|
||||
elif self.model is not None:
|
||||
return self.model._default_manager.all()
|
||||
else:
|
||||
return super(GenericAPIView, self).get_queryset()
|
||||
|
||||
def get_description_context(self):
|
||||
# Set instance attributes needed to get serializer metadata.
|
||||
@ -201,69 +193,13 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
if hasattr(self.model, "_meta"):
|
||||
if hasattr(self.model._meta, "verbose_name"):
|
||||
d.update({
|
||||
'model_verbose_name': unicode(self.model._meta.verbose_name),
|
||||
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
||||
'model_verbose_name': smart_text(self.model._meta.verbose_name),
|
||||
'model_verbose_name_plural': smart_text(self.model._meta.verbose_name_plural),
|
||||
})
|
||||
d.update({'serializer_fields': self.get_serializer().metadata()})
|
||||
d['serializer_fields'] = self.metadata_class().get_serializer_info(self.get_serializer())
|
||||
d['settings'] = settings
|
||||
return d
|
||||
|
||||
def metadata(self, request):
|
||||
'''
|
||||
Add field information for GET requests (so field names/labels are
|
||||
available even when we can't POST/PUT).
|
||||
'''
|
||||
ret = super(GenericAPIView, self).metadata(request)
|
||||
actions = ret.get('actions', {})
|
||||
# Remove read only fields from PUT/POST data.
|
||||
for method in ('POST', 'PUT'):
|
||||
fields = actions.get(method, {})
|
||||
for field, meta in fields.items():
|
||||
if not isinstance(meta, dict):
|
||||
continue
|
||||
if meta.pop('read_only', False):
|
||||
fields.pop(field)
|
||||
if 'GET' in self.allowed_methods:
|
||||
cloned_request = clone_request(request, 'GET')
|
||||
try:
|
||||
# Test global permissions
|
||||
self.check_permissions(cloned_request)
|
||||
# Test object permissions
|
||||
if hasattr(self, 'retrieve'):
|
||||
try:
|
||||
self.get_object()
|
||||
except Http404:
|
||||
# Http404 should be acceptable and the serializer
|
||||
# metadata should be populated. Except this so the
|
||||
# outer "else" clause of the try-except-else block
|
||||
# will be executed.
|
||||
pass
|
||||
except (exceptions.APIException, PermissionDenied):
|
||||
pass
|
||||
else:
|
||||
# If user has appropriate permissions for the view, include
|
||||
# appropriate metadata about the fields that should be supplied.
|
||||
serializer = self.get_serializer()
|
||||
actions['GET'] = serializer.metadata()
|
||||
if hasattr(serializer, 'get_types'):
|
||||
ret['types'] = serializer.get_types()
|
||||
# Remove fields labeled as write_only, remove field attributes
|
||||
# that aren't relevant for retrieving data.
|
||||
for field, meta in actions['GET'].items():
|
||||
if not isinstance(meta, dict):
|
||||
continue
|
||||
meta.pop('required', None)
|
||||
meta.pop('read_only', None)
|
||||
meta.pop('default', None)
|
||||
meta.pop('min_length', None)
|
||||
meta.pop('max_length', None)
|
||||
if meta.pop('write_only', False):
|
||||
actions['GET'].pop(field)
|
||||
if actions:
|
||||
ret['actions'] = actions
|
||||
if getattr(self, 'search_fields', None):
|
||||
ret['search_fields'] = self.search_fields
|
||||
return ret
|
||||
|
||||
class MongoAPIView(GenericAPIView):
|
||||
|
||||
@ -337,8 +273,8 @@ class SubListAPIView(ListAPIView):
|
||||
def get_description_context(self):
|
||||
d = super(SubListAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'parent_model_verbose_name': unicode(self.parent_model._meta.verbose_name),
|
||||
'parent_model_verbose_name_plural': unicode(self.parent_model._meta.verbose_name_plural),
|
||||
'parent_model_verbose_name': smart_text(self.parent_model._meta.verbose_name),
|
||||
'parent_model_verbose_name_plural': smart_text(self.parent_model._meta.verbose_name_plural),
|
||||
})
|
||||
return d
|
||||
|
||||
@ -388,10 +324,10 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
|
||||
# Make a copy of the data provided (since it's readonly) in order to
|
||||
# inject additional data.
|
||||
if hasattr(request.DATA, 'dict'):
|
||||
data = request.DATA.dict()
|
||||
if hasattr(request.data, 'dict'):
|
||||
data = request.data.dict()
|
||||
else:
|
||||
data = request.DATA
|
||||
data = request.data
|
||||
|
||||
# add the parent key to the post data using the pk from the URL
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
@ -405,7 +341,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Verify we have permission to add the object as given.
|
||||
if not request.user.can_access(self.model, 'add', serializer.init_data):
|
||||
if not request.user.can_access(self.model, 'add', serializer.initial_data):
|
||||
raise PermissionDenied()
|
||||
|
||||
# save the object through the serializer, reload and returned the saved
|
||||
@ -424,8 +360,8 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
||||
created = False
|
||||
parent = self.get_parent_object()
|
||||
relationship = getattr(parent, self.relationship)
|
||||
sub_id = request.DATA.get('id', None)
|
||||
data = request.DATA
|
||||
sub_id = request.data.get('id', None)
|
||||
data = request.data
|
||||
|
||||
# Create the sub object if an ID is not provided.
|
||||
if not sub_id:
|
||||
@ -462,7 +398,7 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unattach(self, request, *args, **kwargs):
|
||||
sub_id = request.DATA.get('id', None)
|
||||
sub_id = request.data.get('id', None)
|
||||
if not sub_id:
|
||||
data = dict(msg='"id" is required to disassociate')
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -486,10 +422,10 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not isinstance(request.DATA, dict):
|
||||
if not isinstance(request.data, dict):
|
||||
return Response('invalid type for post data',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'disassociate' in request.DATA:
|
||||
if 'disassociate' in request.data:
|
||||
return self.unattach(request, *args, **kwargs)
|
||||
else:
|
||||
return self.attach(request, *args, **kwargs)
|
||||
@ -499,9 +435,6 @@ class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
|
||||
|
||||
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
||||
|
||||
def pre_save(self, obj):
|
||||
super(RetrieveUpdateAPIView, self).pre_save(obj)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
self.update_filter(request, *args, **kwargs)
|
||||
return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs)
|
||||
|
||||
139
awx/api/metadata.py
Normal file
139
awx/api/metadata.py
Normal file
@ -0,0 +1,139 @@
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import metadata
|
||||
from rest_framework import serializers
|
||||
from rest_framework.request import clone_request
|
||||
|
||||
# Ansible Tower
|
||||
from awx.main.models import InventorySource
|
||||
|
||||
|
||||
class Metadata(metadata.SimpleMetadata):
|
||||
|
||||
def get_field_info(self, field):
|
||||
field_info = super(Metadata, self).get_field_info(field)
|
||||
|
||||
# Indicate if a field has a default value.
|
||||
# FIXME: Still isn't showing all default values?
|
||||
try:
|
||||
field_info['default'] = field.get_default()
|
||||
except serializers.SkipField:
|
||||
pass
|
||||
|
||||
# Indicate if a field is write-only.
|
||||
if getattr(field, 'write_only', False):
|
||||
field_info['write_only'] = True
|
||||
|
||||
# Update choices to be a list of 2-tuples instead of list of dicts with
|
||||
# value/display_name.
|
||||
if 'choices' in field_info:
|
||||
choices = []
|
||||
for choice in field_info['choices']:
|
||||
if isinstance(choice, dict):
|
||||
choices.append((choice.get('value'), choice.get('display_name')))
|
||||
else:
|
||||
choices.append(choice)
|
||||
field_info['choices'] = choices
|
||||
|
||||
# Special handling of inventory source_region choices that vary based on
|
||||
# selected inventory source.
|
||||
if field.field_name == 'source_regions':
|
||||
for cp in ('azure', 'ec2', 'gce', 'rax'):
|
||||
get_regions = getattr(InventorySource, 'get_%s_region_choices' % cp)
|
||||
field_info['%s_region_choices' % cp] = get_regions()
|
||||
|
||||
# Special handling of group_by choices for EC2.
|
||||
if field.field_name == 'group_by':
|
||||
for cp in ('ec2',):
|
||||
get_group_by_choices = getattr(InventorySource, 'get_%s_group_by_choices' % cp)
|
||||
field_info['%s_group_by_choices' % cp] = get_group_by_choices()
|
||||
|
||||
# Update type of fields returned...
|
||||
if field.field_name == 'type':
|
||||
field_info['type'] = 'multiple choice'
|
||||
elif field.field_name == 'url':
|
||||
field_info['type'] = 'string'
|
||||
elif field.field_name in ('related', 'summary_fields'):
|
||||
field_info['type'] = 'object'
|
||||
elif field.field_name in ('created', 'modified'):
|
||||
field_info['type'] = 'datetime'
|
||||
|
||||
return field_info
|
||||
|
||||
def determine_actions(self, request, view):
|
||||
# Add field information for GET requests (so field names/labels are
|
||||
# available even when we can't POST/PUT).
|
||||
actions = {}
|
||||
for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):
|
||||
view.request = clone_request(request, method)
|
||||
try:
|
||||
# Test global permissions
|
||||
if hasattr(view, 'check_permissions'):
|
||||
view.check_permissions(view.request)
|
||||
# Test object permissions
|
||||
if method == 'PUT' and hasattr(view, 'get_object'):
|
||||
view.get_object()
|
||||
except (exceptions.APIException, PermissionDenied, Http404):
|
||||
continue
|
||||
else:
|
||||
# If user has appropriate permissions for the view, include
|
||||
# appropriate metadata about the fields that should be supplied.
|
||||
serializer = view.get_serializer()
|
||||
actions[method] = self.get_serializer_info(serializer)
|
||||
finally:
|
||||
view.request = request
|
||||
|
||||
for field, meta in actions[method].items():
|
||||
if not isinstance(meta, dict):
|
||||
continue
|
||||
|
||||
# Add type choices if available from the serializer.
|
||||
if field == 'type' and hasattr(serializer, 'get_type_choices'):
|
||||
meta['choices'] = serializer.get_type_choices()
|
||||
|
||||
# For GET method, remove meta attributes that aren't relevant
|
||||
# when reading a field and remove write-only fields.
|
||||
if method == 'GET':
|
||||
meta.pop('required', None)
|
||||
meta.pop('read_only', None)
|
||||
meta.pop('default', None)
|
||||
meta.pop('min_length', None)
|
||||
meta.pop('max_length', None)
|
||||
if meta.pop('write_only', False):
|
||||
actions['GET'].pop(field)
|
||||
|
||||
# For PUT/POST methods, remove read-only fields.
|
||||
if method in ('PUT', 'POST'):
|
||||
if meta.pop('read_only', False):
|
||||
actions[method].pop(field)
|
||||
|
||||
return actions
|
||||
|
||||
def determine_metadata(self, request, view):
|
||||
metadata = super(Metadata, self).determine_metadata(request, view)
|
||||
|
||||
# Add version number in which view was added to Tower.
|
||||
added_in_version = '1.2'
|
||||
for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
if getattr(view, 'new_in_%s' % version.replace('.', ''), False):
|
||||
added_in_version = version
|
||||
break
|
||||
metadata['added_in_version'] = added_in_version
|
||||
|
||||
# Add type(s) handled by this view/serializer.
|
||||
serializer = view.get_serializer()
|
||||
if hasattr(serializer, 'get_types'):
|
||||
metadata['types'] = serializer.get_types()
|
||||
|
||||
# Add search fields if available from the view.
|
||||
if getattr(view, 'search_fields', None):
|
||||
metadata['search_fields'] = view.search_fields
|
||||
|
||||
return metadata
|
||||
@ -2,36 +2,26 @@
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import serializers, pagination
|
||||
from rest_framework.templatetags.rest_framework import replace_query_param
|
||||
from rest_framework import pagination
|
||||
from rest_framework.utils.urls import remove_query_param, replace_query_param
|
||||
|
||||
class NextPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_next():
|
||||
class Pagination(pagination.PageNumberPagination):
|
||||
|
||||
page_size_query_param = 'page_size'
|
||||
|
||||
def get_next_link(self):
|
||||
if not self.page.has_next():
|
||||
return None
|
||||
page = value.next_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
url = self.request and self.request.get_full_path() or ''
|
||||
page_number = self.page.next_page_number()
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
|
||||
class PreviousPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_previous():
|
||||
def get_previous_link(self):
|
||||
if not self.page.has_previous():
|
||||
return None
|
||||
page = value.previous_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
|
||||
class PaginationSerializer(pagination.BasePaginationSerializer):
|
||||
'''
|
||||
Custom pagination serializer to output only URL path (without host/port).
|
||||
'''
|
||||
|
||||
count = serializers.Field(source='paginator.count')
|
||||
next = NextPageField(source='*')
|
||||
previous = PreviousPageField(source='*')
|
||||
url = self.request and self.request.get_full_path() or ''
|
||||
page_number = self.page.previous_page_number()
|
||||
if page_number == 1:
|
||||
return remove_query_param(url, self.page_query_param)
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
|
||||
@ -61,7 +61,7 @@ class ModelAccessPermission(permissions.BasePermission):
|
||||
else:
|
||||
if obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'add', request.DATA)
|
||||
return check_user_access(request.user, view.model, 'add', request.data)
|
||||
|
||||
def check_put_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
@ -70,10 +70,10 @@ class ModelAccessPermission(permissions.BasePermission):
|
||||
return True
|
||||
if getattr(view, 'is_variable_data', False):
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
dict(variables=request.DATA))
|
||||
dict(variables=request.data))
|
||||
else:
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
request.DATA)
|
||||
request.data)
|
||||
|
||||
def check_patch_permissions(self, request, view, obj=None):
|
||||
return self.check_put_permissions(request, view, obj)
|
||||
@ -127,7 +127,7 @@ class ModelAccessPermission(permissions.BasePermission):
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
logger.debug('has_permission(user=%s method=%s data=%r, %s, %r)',
|
||||
request.user, request.method, request.DATA,
|
||||
request.user, request.method, request.data,
|
||||
view.__class__.__name__, obj)
|
||||
try:
|
||||
response = self.check_permissions(request, view, obj)
|
||||
@ -156,7 +156,7 @@ class JobTemplateCallbackPermission(ModelAccessPermission):
|
||||
# Require method to be POST, host_config_key to be specified and match
|
||||
# the requested job template, and require the job template to be
|
||||
# active in order to proceed.
|
||||
host_config_key = request.DATA.get('host_config_key', '')
|
||||
host_config_key = request.data.get('host_config_key', '')
|
||||
if request.method.lower() != 'post':
|
||||
raise PermissionDenied()
|
||||
elif not host_config_key:
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
# Django REST Framework
|
||||
from rest_framework import renderers
|
||||
|
||||
|
||||
class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
'''
|
||||
Customizations to the default browsable API renderer.
|
||||
@ -16,14 +17,16 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
return renderers.JSONRenderer()
|
||||
return renderer
|
||||
|
||||
def get_raw_data_form(self, view, method, request):
|
||||
def get_raw_data_form(self, data, view, method, request):
|
||||
# Set a flag on the view to indiciate to the view/serializer that we're
|
||||
# creating a raw data form for the browsable API.
|
||||
try:
|
||||
setattr(view, '_raw_data_form_marker', True)
|
||||
return super(BrowsableAPIRenderer, self).get_raw_data_form(view, method, request)
|
||||
return super(BrowsableAPIRenderer, self).get_raw_data_form(data, view, method, request)
|
||||
finally:
|
||||
delattr(view, '_raw_data_form_marker')
|
||||
|
||||
def get_rendered_html_form(self, view, method, request):
|
||||
def get_rendered_html_form(self, data, view, method, request):
|
||||
'''Never show auto-generated form (only raw form).'''
|
||||
obj = getattr(view, 'object', None)
|
||||
if not self.show_form_for_method(view, method, request, obj):
|
||||
@ -31,9 +34,10 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
if method in ('DELETE', 'OPTIONS'):
|
||||
return True # Don't actually need to return a form
|
||||
|
||||
def get_context(self, data, accepted_media_type, renderer_context):
|
||||
context = super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context)
|
||||
return context
|
||||
def get_filter_form(self, data, view, request):
|
||||
# Don't show filter form in browsable API.
|
||||
return
|
||||
|
||||
|
||||
class PlainTextRenderer(renderers.BaseRenderer):
|
||||
|
||||
@ -45,9 +49,12 @@ class PlainTextRenderer(renderers.BaseRenderer):
|
||||
data = unicode(data)
|
||||
return data.encode(self.charset)
|
||||
|
||||
|
||||
class DownloadTextRenderer(PlainTextRenderer):
|
||||
|
||||
format = "txt_download"
|
||||
|
||||
|
||||
class AnsiTextRenderer(PlainTextRenderer):
|
||||
|
||||
media_type = 'text/plain'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
awx/api/test_api.py
Normal file
4
awx/api/test_api.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from awx.api.tests import * # noqa
|
||||
@ -1,7 +1,7 @@
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from ordereddict import OrderedDict
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
import functools
|
||||
|
||||
@ -23,21 +23,22 @@ def paginated(method):
|
||||
def func(self, request, *args, **kwargs):
|
||||
# Manually spin up pagination.
|
||||
# How many results do we show?
|
||||
limit = api_settings.PAGINATE_BY
|
||||
if request.QUERY_PARAMS.get(api_settings.PAGINATE_BY_PARAM, False):
|
||||
limit = request.QUERY_PARAMS[api_settings.PAGINATE_BY_PARAM]
|
||||
if api_settings.MAX_PAGINATE_BY:
|
||||
limit = min(api_settings.MAX_PAGINATE_BY, limit)
|
||||
paginator_class = api_settings.DEFAULT_PAGINATION_CLASS
|
||||
limit = paginator_class.page_size
|
||||
if request.query_params.get(paginator_class.page_size_query_param, False):
|
||||
limit = request.query_params[paginator_class.page_size_query_param]
|
||||
if paginator_class.max_page_size:
|
||||
limit = min(paginator_class.max_page_size, limit)
|
||||
limit = int(limit)
|
||||
|
||||
# Get the order parameter if it's given
|
||||
if request.QUERY_PARAMS.get("ordering", False):
|
||||
ordering = request.QUERY_PARAMS["ordering"]
|
||||
if request.query_params.get("ordering", False):
|
||||
ordering = request.query_params["ordering"]
|
||||
else:
|
||||
ordering = None
|
||||
|
||||
# What page are we on?
|
||||
page = int(request.QUERY_PARAMS.get('page', 1))
|
||||
page = int(request.query_params.get('page', 1))
|
||||
offset = (page - 1) * limit
|
||||
|
||||
# Add the limit, offset, page, and order variables to the keyword arguments
|
||||
|
||||
285
awx/api/views.py
285
awx/api/views.py
@ -12,6 +12,7 @@ import socket
|
||||
import sys
|
||||
import errno
|
||||
from base64 import b64encode
|
||||
from collections import OrderedDict
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
@ -21,7 +22,7 @@ from django.core.exceptions import FieldError
|
||||
from django.db.models import Q, Count
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@ -31,14 +32,16 @@ from django.http import HttpResponse
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import PermissionDenied, ParseError
|
||||
from rest_framework.parsers import YAMLParser
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.renderers import YAMLRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import exception_handler
|
||||
from rest_framework import status
|
||||
|
||||
# Django REST Framework YAML
|
||||
from rest_framework_yaml.parsers import YAMLParser
|
||||
from rest_framework_yaml.renderers import YAMLRenderer
|
||||
|
||||
# MongoEngine
|
||||
import mongoengine
|
||||
|
||||
@ -71,7 +74,7 @@ from awx.fact.models import * # noqa
|
||||
from awx.main.utils import emit_websocket_notification
|
||||
from awx.main.conf import tower_settings
|
||||
|
||||
def api_exception_handler(exc):
|
||||
def api_exception_handler(exc, context):
|
||||
'''
|
||||
Override default API exception handler to catch IntegrityError exceptions.
|
||||
'''
|
||||
@ -79,8 +82,7 @@ def api_exception_handler(exc):
|
||||
exc = ParseError(exc.args[0])
|
||||
if isinstance(exc, FieldError):
|
||||
exc = ParseError(exc.args[0])
|
||||
return exception_handler(exc)
|
||||
|
||||
return exception_handler(exc, context)
|
||||
|
||||
class ApiRootView(APIView):
|
||||
|
||||
@ -110,7 +112,7 @@ class ApiV1RootView(APIView):
|
||||
def get(self, request, format=None):
|
||||
''' list top level resources '''
|
||||
|
||||
data = SortedDict()
|
||||
data = OrderedDict()
|
||||
data['authtoken'] = reverse('api:auth_token_view')
|
||||
data['ping'] = reverse('api:api_v1_ping_view')
|
||||
data['config'] = reverse('api:api_v1_config_view')
|
||||
@ -224,20 +226,20 @@ class ApiV1ConfigView(APIView):
|
||||
def post(self, request):
|
||||
if not request.user.is_superuser:
|
||||
return Response(None, status=status.HTTP_404_NOT_FOUND)
|
||||
if not type(request.DATA) == dict:
|
||||
if not type(request.data) == dict:
|
||||
return Response({"error": "Invalid license data"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if "eula_accepted" not in request.DATA:
|
||||
if "eula_accepted" not in request.data:
|
||||
return Response({"error": "Missing 'eula_accepted' property"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
eula_accepted = to_python_boolean(request.DATA["eula_accepted"])
|
||||
eula_accepted = to_python_boolean(request.data["eula_accepted"])
|
||||
except ValueError:
|
||||
return Response({"error": "'eula_accepted' value is invalid"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not eula_accepted:
|
||||
return Response({"error": "'eula_accepted' must be True"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
request.DATA.pop("eula_accepted")
|
||||
request.data.pop("eula_accepted")
|
||||
try:
|
||||
data_actual = json.dumps(request.DATA)
|
||||
data_actual = json.dumps(request.data)
|
||||
except Exception:
|
||||
# FIX: Log
|
||||
return Response({"error": "Invalid JSON"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -306,7 +308,7 @@ class DashboardView(APIView):
|
||||
|
||||
def get(self, request, format=None):
|
||||
''' Show Dashboard Details '''
|
||||
data = SortedDict()
|
||||
data = OrderedDict()
|
||||
data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view'),
|
||||
'inventory_graph': reverse('api:dashboard_inventory_graph_view')}
|
||||
user_inventory = get_user_queryset(request.user, Inventory)
|
||||
@ -411,8 +413,8 @@ class DashboardJobsGraphView(APIView):
|
||||
new_in_200 = True
|
||||
|
||||
def get(self, request, format=None):
|
||||
period = request.QUERY_PARAMS.get('period', 'month')
|
||||
job_type = request.QUERY_PARAMS.get('job_type', 'all')
|
||||
period = request.query_params.get('period', 'month')
|
||||
job_type = request.query_params.get('job_type', 'all')
|
||||
|
||||
user_unified_jobs = get_user_queryset(request.user, UnifiedJob)
|
||||
|
||||
@ -460,7 +462,7 @@ class DashboardInventoryGraphView(APIView):
|
||||
new_in_200 = True
|
||||
|
||||
def get(self, request, format=None):
|
||||
period = request.QUERY_PARAMS.get('period', 'month')
|
||||
period = request.query_params.get('period', 'month')
|
||||
|
||||
end_date = now()
|
||||
if period == 'month':
|
||||
@ -476,7 +478,7 @@ class DashboardInventoryGraphView(APIView):
|
||||
start_date = start_date.replace(minute=0, second=0, microsecond=0)
|
||||
delta = dateutil.relativedelta.relativedelta(hours=1)
|
||||
else:
|
||||
raise ParseError(u'Unknown period "%s"' % unicode(period))
|
||||
raise ParseError(u'Unknown period "%s"' % force_text(period))
|
||||
|
||||
host_stats = []
|
||||
date = start_date
|
||||
@ -527,7 +529,7 @@ class AuthView(APIView):
|
||||
new_in_240 = True
|
||||
|
||||
def get(self, request):
|
||||
data = SortedDict()
|
||||
data = OrderedDict()
|
||||
err_backend, err_message = request.session.get('social_auth_error', (None, None))
|
||||
auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS).items()
|
||||
# Return auth backends in consistent order: Google, GitHub, SAML.
|
||||
@ -567,27 +569,27 @@ class AuthTokenView(APIView):
|
||||
model = AuthToken
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.DATA)
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
request_hash = AuthToken.get_request_hash(self.request)
|
||||
try:
|
||||
token = AuthToken.objects.filter(user=serializer.object['user'],
|
||||
token = AuthToken.objects.filter(user=serializer.validated_data['user'],
|
||||
request_hash=request_hash,
|
||||
expires__gt=now(),
|
||||
reason='')[0]
|
||||
token.refresh()
|
||||
except IndexError:
|
||||
token = AuthToken.objects.create(user=serializer.object['user'],
|
||||
token = AuthToken.objects.create(user=serializer.validated_data['user'],
|
||||
request_hash=request_hash)
|
||||
# Get user un-expired tokens that are not invalidated that are
|
||||
# over the configured limit.
|
||||
# Mark them as invalid and inform the user
|
||||
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.object['user'])
|
||||
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user'])
|
||||
for t in invalid_tokens:
|
||||
# TODO: send socket notification
|
||||
emit_websocket_notification('/socket.io/control',
|
||||
'limit_reached',
|
||||
dict(reason=unicode(AuthToken.reason_long('limit_reached'))),
|
||||
dict(reason=force_text(AuthToken.reason_long('limit_reached'))),
|
||||
token_key=t.key)
|
||||
t.invalidate(reason='limit_reached')
|
||||
|
||||
@ -769,7 +771,7 @@ class ProjectList(ListCreateAPIView):
|
||||
# Not optimal, but make sure the project status and last_updated fields
|
||||
# are up to date here...
|
||||
projects_qs = Project.objects.filter(active=True)
|
||||
projects_qs = projects_qs.select_related('current_update', 'last_updated')
|
||||
projects_qs = projects_qs.select_related('current_job', 'last_job')
|
||||
for project in projects_qs:
|
||||
project._set_status_and_last_job_run()
|
||||
return super(ProjectList, self).get(request, *args, **kwargs)
|
||||
@ -994,15 +996,15 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
|
||||
def update_filter(self, request, *args, **kwargs):
|
||||
''' make sure non-read-only fields that can only be edited by admins, are only edited by admins '''
|
||||
obj = self.get_object()
|
||||
can_change = request.user.can_access(User, 'change', obj, request.DATA)
|
||||
can_admin = request.user.can_access(User, 'admin', obj, request.DATA)
|
||||
can_change = request.user.can_access(User, 'change', obj, request.data)
|
||||
can_admin = request.user.can_access(User, 'admin', obj, request.data)
|
||||
if can_change and not can_admin:
|
||||
admin_only_edit_fields = ('last_name', 'first_name', 'username',
|
||||
'is_active', 'is_superuser')
|
||||
changed = {}
|
||||
for field in admin_only_edit_fields:
|
||||
left = getattr(obj, field, None)
|
||||
right = request.DATA.get(field, None)
|
||||
right = request.data.get(field, None)
|
||||
if left is not None and right is not None and left != right:
|
||||
changed[field] = (left, right)
|
||||
if changed:
|
||||
@ -1061,11 +1063,11 @@ class InventoryScriptDetail(RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = CustomInventoryScriptSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
can_delete = request.user.can_access(self.model, 'delete', obj)
|
||||
instance = self.get_object()
|
||||
can_delete = request.user.can_access(self.model, 'delete', instance)
|
||||
if not can_delete:
|
||||
raise PermissionDenied("Cannot delete inventory script")
|
||||
for inv_src in InventorySource.objects.filter(source_script=obj):
|
||||
for inv_src in InventorySource.objects.filter(source_script=instance):
|
||||
inv_src.source_script = None
|
||||
inv_src.save()
|
||||
return super(InventoryScriptDetail, self).destroy(request, *args, **kwargs)
|
||||
@ -1137,10 +1139,10 @@ class InventorySingleFactView(MongoAPIView):
|
||||
raise LicenseForbids('Your license does not permit use '
|
||||
'of system tracking.')
|
||||
|
||||
fact_key = request.QUERY_PARAMS.get("fact_key", None)
|
||||
fact_value = request.QUERY_PARAMS.get("fact_value", None)
|
||||
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
|
||||
module_spec = request.QUERY_PARAMS.get("module", None)
|
||||
fact_key = request.query_params.get("fact_key", None)
|
||||
fact_value = request.query_params.get("fact_value", None)
|
||||
datetime_spec = request.query_params.get("timestamp", None)
|
||||
module_spec = request.query_params.get("module", None)
|
||||
|
||||
if fact_key is None or fact_value is None or module_spec is None:
|
||||
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -1231,9 +1233,9 @@ class HostFactVersionsList(MongoListAPIView):
|
||||
filter_backends = (MongoFilterBackend,)
|
||||
|
||||
def get_queryset(self):
|
||||
from_spec = self.request.QUERY_PARAMS.get('from', None)
|
||||
to_spec = self.request.QUERY_PARAMS.get('to', None)
|
||||
module_spec = self.request.QUERY_PARAMS.get('module', None)
|
||||
from_spec = self.request.query_params.get('from', None)
|
||||
to_spec = self.request.query_params.get('to', None)
|
||||
module_spec = self.request.query_params.get('module', None)
|
||||
|
||||
if not feature_enabled("system_tracking"):
|
||||
raise LicenseForbids("Your license does not permit use "
|
||||
@ -1285,10 +1287,10 @@ class HostSingleFactView(MongoAPIView):
|
||||
raise LicenseForbids('Your license does not permit use '
|
||||
'of system tracking.')
|
||||
|
||||
fact_key = request.QUERY_PARAMS.get("fact_key", None)
|
||||
fact_value = request.QUERY_PARAMS.get("fact_value", None)
|
||||
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
|
||||
module_spec = request.QUERY_PARAMS.get("module", None)
|
||||
fact_key = request.query_params.get("fact_key", None)
|
||||
fact_value = request.query_params.get("fact_value", None)
|
||||
datetime_spec = request.query_params.get("timestamp", None)
|
||||
module_spec = request.query_params.get("module", None)
|
||||
|
||||
if fact_key is None or fact_value is None or module_spec is None:
|
||||
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -1310,8 +1312,8 @@ class HostFactCompareView(MongoAPIView):
|
||||
raise LicenseForbids('Your license does not permit use '
|
||||
'of system tracking.')
|
||||
|
||||
datetime_spec = request.QUERY_PARAMS.get('datetime', None)
|
||||
module_spec = request.QUERY_PARAMS.get('module', "ansible")
|
||||
datetime_spec = request.query_params.get('datetime', None)
|
||||
module_spec = request.query_params.get('module', "ansible")
|
||||
datetime_actual = dateutil.parser.parse(datetime_spec) if datetime_spec is not None else now()
|
||||
|
||||
host_obj = self.get_parent_object()
|
||||
@ -1333,7 +1335,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
|
||||
relationship = 'children'
|
||||
|
||||
def unattach(self, request, *args, **kwargs):
|
||||
sub_id = request.DATA.get('id', None)
|
||||
sub_id = request.data.get('id', None)
|
||||
if sub_id is not None:
|
||||
return super(GroupChildrenList, self).unattach(request, *args, **kwargs)
|
||||
parent = self.get_parent_object()
|
||||
@ -1345,7 +1347,7 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
|
||||
Special case for disassociating a child group from the parent. If the
|
||||
child group has no more parents, then automatically mark it inactive.
|
||||
'''
|
||||
sub_id = request.DATA.get('id', None)
|
||||
sub_id = request.data.get('id', None)
|
||||
if not sub_id:
|
||||
data = dict(msg='"id" is required to disassociate')
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -1394,12 +1396,12 @@ class GroupHostsList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
parent_group = Group.objects.get(id=self.kwargs['pk'])
|
||||
existing_hosts = Host.objects.filter(inventory=parent_group.inventory, name=request.DATA['name'])
|
||||
if existing_hosts.count() > 0 and ('variables' not in request.DATA or
|
||||
request.DATA['variables'] == '' or
|
||||
request.DATA['variables'] == '{}' or
|
||||
request.DATA['variables'] == '---'):
|
||||
request.DATA['id'] = existing_hosts[0].id
|
||||
existing_hosts = Host.objects.filter(inventory=parent_group.inventory, name=request.data['name'])
|
||||
if existing_hosts.count() > 0 and ('variables' not in request.data or
|
||||
request.data['variables'] == '' or
|
||||
request.data['variables'] == '{}' or
|
||||
request.data['variables'] == '---'):
|
||||
request.data['id'] = existing_hosts[0].id
|
||||
return self.attach(request, *args, **kwargs)
|
||||
return super(GroupHostsList, self).create(request, *args, **kwargs)
|
||||
|
||||
@ -1483,10 +1485,10 @@ class GroupSingleFactView(MongoAPIView):
|
||||
raise LicenseForbids('Your license does not permit use '
|
||||
'of system tracking.')
|
||||
|
||||
fact_key = request.QUERY_PARAMS.get("fact_key", None)
|
||||
fact_value = request.QUERY_PARAMS.get("fact_value", None)
|
||||
datetime_spec = request.QUERY_PARAMS.get("timestamp", None)
|
||||
module_spec = request.QUERY_PARAMS.get("module", None)
|
||||
fact_key = request.query_params.get("fact_key", None)
|
||||
fact_value = request.query_params.get("fact_value", None)
|
||||
datetime_spec = request.query_params.get("timestamp", None)
|
||||
module_spec = request.query_params.get("module", None)
|
||||
|
||||
if fact_key is None or fact_value is None or module_spec is None:
|
||||
return Response({"error": "Missing fields"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -1547,33 +1549,33 @@ class InventoryScriptView(RetrieveAPIView):
|
||||
filter_backends = ()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
hostname = request.QUERY_PARAMS.get('host', '')
|
||||
hostvars = bool(request.QUERY_PARAMS.get('hostvars', ''))
|
||||
show_all = bool(request.QUERY_PARAMS.get('all', ''))
|
||||
obj = self.get_object()
|
||||
hostname = request.query_params.get('host', '')
|
||||
hostvars = bool(request.query_params.get('hostvars', ''))
|
||||
show_all = bool(request.query_params.get('all', ''))
|
||||
if show_all:
|
||||
hosts_q = dict(active=True)
|
||||
else:
|
||||
hosts_q = dict(active=True, enabled=True)
|
||||
if hostname:
|
||||
host = get_object_or_404(self.object.hosts, name=hostname, **hosts_q)
|
||||
host = get_object_or_404(obj.hosts, name=hostname, **hosts_q)
|
||||
data = host.variables_dict
|
||||
else:
|
||||
data = SortedDict()
|
||||
if self.object.variables_dict:
|
||||
all_group = data.setdefault('all', SortedDict())
|
||||
all_group['vars'] = self.object.variables_dict
|
||||
data = OrderedDict()
|
||||
if obj.variables_dict:
|
||||
all_group = data.setdefault('all', OrderedDict())
|
||||
all_group['vars'] = obj.variables_dict
|
||||
|
||||
# Add hosts without a group to the all group.
|
||||
groupless_hosts_qs = self.object.hosts.filter(groups__isnull=True, **hosts_q).order_by('name')
|
||||
groupless_hosts_qs = obj.hosts.filter(groups__isnull=True, **hosts_q).order_by('name')
|
||||
groupless_hosts = list(groupless_hosts_qs.values_list('name', flat=True))
|
||||
if groupless_hosts:
|
||||
all_group = data.setdefault('all', SortedDict())
|
||||
all_group = data.setdefault('all', OrderedDict())
|
||||
all_group['hosts'] = groupless_hosts
|
||||
|
||||
# Build in-memory mapping of groups and their hosts.
|
||||
group_hosts_kw = dict(group__inventory_id=self.object.id, group__active=True,
|
||||
host__inventory_id=self.object.id, host__active=True)
|
||||
group_hosts_kw = dict(group__inventory_id=obj.id, group__active=True,
|
||||
host__inventory_id=obj.id, host__active=True)
|
||||
if 'enabled' in hosts_q:
|
||||
group_hosts_kw['host__enabled'] = hosts_q['enabled']
|
||||
group_hosts_qs = Group.hosts.through.objects.filter(**group_hosts_kw)
|
||||
@ -1586,8 +1588,8 @@ class InventoryScriptView(RetrieveAPIView):
|
||||
|
||||
# Build in-memory mapping of groups and their children.
|
||||
group_parents_qs = Group.parents.through.objects.filter(
|
||||
from_group__inventory_id=self.object.id, from_group__active=True,
|
||||
to_group__inventory_id=self.object.id, to_group__active=True,
|
||||
from_group__inventory_id=obj.id, from_group__active=True,
|
||||
to_group__inventory_id=obj.id, to_group__active=True,
|
||||
)
|
||||
group_parents_qs = group_parents_qs.order_by('from_group__name')
|
||||
group_parents_qs = group_parents_qs.values_list('from_group_id', 'from_group__name', 'to_group_id')
|
||||
@ -1597,28 +1599,27 @@ class InventoryScriptView(RetrieveAPIView):
|
||||
group_children.append(from_group_name)
|
||||
|
||||
# Now use in-memory maps to build up group info.
|
||||
for group in self.object.groups.filter(active=True):
|
||||
group_info = SortedDict()
|
||||
for group in obj.groups.filter(active=True):
|
||||
group_info = OrderedDict()
|
||||
group_info['hosts'] = group_hosts_map.get(group.id, [])
|
||||
group_info['children'] = group_children_map.get(group.id, [])
|
||||
group_info['vars'] = group.variables_dict
|
||||
data[group.name] = group_info
|
||||
|
||||
if hostvars:
|
||||
data.setdefault('_meta', SortedDict())
|
||||
data['_meta'].setdefault('hostvars', SortedDict())
|
||||
for host in self.object.hosts.filter(**hosts_q):
|
||||
data.setdefault('_meta', OrderedDict())
|
||||
data['_meta'].setdefault('hostvars', OrderedDict())
|
||||
for host in obj.hosts.filter(**hosts_q):
|
||||
data['_meta']['hostvars'][host.name] = host.variables_dict
|
||||
|
||||
# workaround for Ansible inventory bug (github #3687), localhost
|
||||
# must be explicitly listed in the all group for dynamic inventory
|
||||
# scripts to pick it up.
|
||||
localhost_names = ('localhost', '127.0.0.1', '::1')
|
||||
localhosts_qs = self.object.hosts.filter(name__in=localhost_names,
|
||||
**hosts_q)
|
||||
localhosts_qs = obj.hosts.filter(name__in=localhost_names, **hosts_q)
|
||||
localhosts = list(localhosts_qs.values_list('name', flat=True))
|
||||
if localhosts:
|
||||
all_group = data.setdefault('all', SortedDict())
|
||||
all_group = data.setdefault('all', OrderedDict())
|
||||
all_group_hosts = all_group.get('hosts', [])
|
||||
all_group_hosts.extend(localhosts)
|
||||
all_group['hosts'] = sorted(set(all_group_hosts))
|
||||
@ -1657,13 +1658,6 @@ class InventoryTreeView(RetrieveAPIView):
|
||||
group_children_map)
|
||||
return Response(tree_data)
|
||||
|
||||
def get_description_context(self):
|
||||
d = super(InventoryTreeView, self).get_description_context()
|
||||
d.update({
|
||||
'serializer_fields': GroupTreeSerializer().metadata(),
|
||||
})
|
||||
return d
|
||||
|
||||
class InventoryInventorySourcesList(SubListAPIView):
|
||||
|
||||
model = InventorySource
|
||||
@ -1828,23 +1822,23 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
|
||||
if not request.user.can_access(self.model, 'start', obj):
|
||||
raise PermissionDenied()
|
||||
|
||||
if 'credential' not in request.DATA and 'credential_id' in request.DATA:
|
||||
request.DATA['credential'] = request.DATA['credential_id']
|
||||
if 'credential' not in request.data and 'credential_id' in request.data:
|
||||
request.data['credential'] = request.data['credential_id']
|
||||
|
||||
passwords = {}
|
||||
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA, 'passwords': passwords})
|
||||
serializer = self.serializer_class(instance=obj, data=request.data, context={'obj': obj, 'data': request.data, 'passwords': passwords})
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# At this point, a credential is gauranteed to exist at serializer.object.credential
|
||||
if not request.user.can_access(Credential, 'read', serializer.object.credential):
|
||||
# At this point, a credential is gauranteed to exist at serializer.instance.credential
|
||||
if not request.user.can_access(Credential, 'read', serializer.instance.credential):
|
||||
raise PermissionDenied()
|
||||
|
||||
kv = {
|
||||
'credential': serializer.object.credential.pk,
|
||||
'credential': serializer.instance.credential.pk,
|
||||
}
|
||||
if 'extra_vars' in request.DATA:
|
||||
kv['extra_vars'] = request.DATA['extra_vars']
|
||||
if 'extra_vars' in request.data:
|
||||
kv['extra_vars'] = request.data['extra_vars']
|
||||
kv.update(passwords)
|
||||
|
||||
new_job = obj.create_unified_job(**kv)
|
||||
@ -1892,7 +1886,7 @@ class JobTemplateSurveySpec(GenericAPIView):
|
||||
if not request.user.can_access(self.model, 'change', obj, None):
|
||||
raise PermissionDenied()
|
||||
try:
|
||||
obj.survey_spec = json.dumps(request.DATA)
|
||||
obj.survey_spec = json.dumps(request.data)
|
||||
except ValueError:
|
||||
# TODO: Log
|
||||
return Response(dict(error="Invalid JSON when parsing survey spec"), status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2040,7 +2034,7 @@ class JobTemplateCallback(GenericAPIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
extra_vars = None
|
||||
if request.content_type == "application/json":
|
||||
extra_vars = request.DATA.get("extra_vars", None)
|
||||
extra_vars = request.data.get("extra_vars", None)
|
||||
# Permission class should have already validated host_config_key.
|
||||
job_template = self.get_object()
|
||||
# Attempt to find matching hosts based on remote address.
|
||||
@ -2144,8 +2138,8 @@ class SystemJobTemplateLaunch(GenericAPIView):
|
||||
if not request.user.can_access(self.model, 'start', obj):
|
||||
raise PermissionDenied()
|
||||
|
||||
new_job = obj.create_unified_job(**request.DATA)
|
||||
new_job.signal_start(**request.DATA)
|
||||
new_job = obj.create_unified_job(**request.data)
|
||||
new_job.signal_start(**request.data)
|
||||
data = dict(system_job=new_job.id)
|
||||
return Response(data, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
@ -2223,7 +2217,7 @@ class JobStart(GenericAPIView):
|
||||
if not request.user.can_access(self.model, 'start', obj):
|
||||
raise PermissionDenied()
|
||||
if obj.can_start:
|
||||
result = obj.signal_start(**request.DATA)
|
||||
result = obj.signal_start(**request.data)
|
||||
if not result:
|
||||
data = dict(passwords_needed_to_start=obj.passwords_needed_to_start)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2262,15 +2256,15 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView):
|
||||
if not request.user.can_access(self.model, 'start', obj):
|
||||
raise PermissionDenied()
|
||||
|
||||
# Note: is_valid() may modify request.DATA
|
||||
# Note: is_valid() may modify request.data
|
||||
# It will remove any key/value pair who's key is not in the 'passwords_needed_to_start' list
|
||||
serializer = self.serializer_class(data=request.DATA, context={'obj': obj, 'data': request.DATA})
|
||||
serializer = self.serializer_class(data=request.data, context={'obj': obj, 'data': request.data})
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
obj.launch_type = 'relaunch'
|
||||
new_job = obj.copy()
|
||||
result = new_job.signal_start(**request.DATA)
|
||||
result = new_job.signal_start(**request.data)
|
||||
if not result:
|
||||
data = dict(passwords_needed_to_start=new_job.passwords_needed_to_start)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2357,13 +2351,11 @@ class JobJobEventsList(BaseJobEventsList):
|
||||
# Post allowed for job event callback only.
|
||||
def post(self, request, *args, **kwargs):
|
||||
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
|
||||
data = request.DATA.copy()
|
||||
data = request.data.copy()
|
||||
data['job'] = parent_obj.pk
|
||||
serializer = self.get_serializer(data=data)
|
||||
if serializer.is_valid():
|
||||
self.pre_save(serializer.object)
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
self.instance = serializer.save()
|
||||
headers = {'Location': serializer.data['url']}
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
@ -2392,16 +2384,16 @@ class JobJobPlaysList(BaseJobEventsList):
|
||||
# doing this here for the moment until/unless we need to implement more
|
||||
# complex filtering (since we aren't under a serializer)
|
||||
|
||||
if "id__in" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.QUERY_PARAMS["id__in"].split(",")])
|
||||
elif "id__gt" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__gt=request.QUERY_PARAMS['id__gt'])
|
||||
elif "id__lt" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__lt=request.QUERY_PARAMS['id__lt'])
|
||||
if "failed" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(failed=(request.QUERY_PARAMS['failed'].lower() == 'true'))
|
||||
if "play__icontains" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(play__icontains=request.QUERY_PARAMS['play__icontains'])
|
||||
if "id__in" in request.query_params:
|
||||
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.query_params["id__in"].split(",")])
|
||||
elif "id__gt" in request.query_params:
|
||||
qs = qs.filter(id__gt=request.query_params['id__gt'])
|
||||
elif "id__lt" in request.query_params:
|
||||
qs = qs.filter(id__lt=request.query_params['id__lt'])
|
||||
if "failed" in request.query_params:
|
||||
qs = qs.filter(failed=(request.query_params['failed'].lower() == 'true'))
|
||||
if "play__icontains" in request.query_params:
|
||||
qs = qs.filter(play__icontains=request.query_params['play__icontains'])
|
||||
|
||||
count = qs.count()
|
||||
|
||||
@ -2465,10 +2457,10 @@ class JobJobTasksList(BaseJobEventsList):
|
||||
return ({'detail': 'job not found'}, -1, status.HTTP_404_NOT_FOUND)
|
||||
job = job[0]
|
||||
|
||||
if 'event_id' not in request.QUERY_PARAMS:
|
||||
if 'event_id' not in request.query_params:
|
||||
return ({'detail': '"event_id" not provided'}, -1, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
parent_task = job.job_events.filter(pk=int(request.QUERY_PARAMS.get('event_id', -1)))
|
||||
parent_task = job.job_events.filter(pk=int(request.query_params.get('event_id', -1)))
|
||||
if not parent_task.exists():
|
||||
return ({'detail': 'parent event not found'}, -1, status.HTTP_404_NOT_FOUND)
|
||||
parent_task = parent_task[0]
|
||||
@ -2507,16 +2499,16 @@ class JobJobTasksList(BaseJobEventsList):
|
||||
# doing this here for the moment until/unless we need to implement more
|
||||
# complex filtering (since we aren't under a serializer)
|
||||
|
||||
if "id__in" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.QUERY_PARAMS["id__in"].split(",")])
|
||||
elif "id__gt" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__gt=request.QUERY_PARAMS['id__gt'])
|
||||
elif "id__lt" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(id__lt=request.QUERY_PARAMS['id__lt'])
|
||||
if "failed" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(failed=(request.QUERY_PARAMS['failed'].lower() == 'true'))
|
||||
if "task__icontains" in request.QUERY_PARAMS:
|
||||
qs = qs.filter(task__icontains=request.QUERY_PARAMS['task__icontains'])
|
||||
if "id__in" in request.query_params:
|
||||
qs = qs.filter(id__in=[int(filter_id) for filter_id in request.query_params["id__in"].split(",")])
|
||||
elif "id__gt" in request.query_params:
|
||||
qs = qs.filter(id__gt=request.query_params['id__gt'])
|
||||
elif "id__lt" in request.query_params:
|
||||
qs = qs.filter(id__lt=request.query_params['id__lt'])
|
||||
if "failed" in request.query_params:
|
||||
qs = qs.filter(failed=(request.query_params['failed'].lower() == 'true'))
|
||||
if "task__icontains" in request.query_params:
|
||||
qs = qs.filter(task__icontains=request.query_params['task__icontains'])
|
||||
|
||||
if ordering is not None:
|
||||
qs = qs.order_by(ordering)
|
||||
@ -2594,7 +2586,7 @@ class AdHocCommandList(ListCreateAPIView):
|
||||
def create(self, request, *args, **kwargs):
|
||||
# Inject inventory ID and limit if parent objects is a host/group.
|
||||
if hasattr(self, 'get_parent_object') and not getattr(self, 'parent_key', None):
|
||||
data = request.DATA
|
||||
data = request.data
|
||||
# HACK: Make request data mutable.
|
||||
if getattr(data, '_mutable', None) is False:
|
||||
data._mutable = True
|
||||
@ -2604,11 +2596,11 @@ class AdHocCommandList(ListCreateAPIView):
|
||||
data['limit'] = parent_obj.name
|
||||
|
||||
# Check for passwords needed before creating ad hoc command.
|
||||
credential_pk = get_pk_from_dict(request.DATA, 'credential')
|
||||
credential_pk = get_pk_from_dict(request.data, 'credential')
|
||||
if credential_pk:
|
||||
credential = get_object_or_400(Credential, pk=credential_pk)
|
||||
needed = credential.passwords_needed
|
||||
provided = dict([(field, request.DATA.get(field, '')) for field in needed])
|
||||
provided = dict([(field, request.data.get(field, '')) for field in needed])
|
||||
if not all(provided.values()):
|
||||
data = dict(passwords_needed_to_start=needed)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2619,7 +2611,7 @@ class AdHocCommandList(ListCreateAPIView):
|
||||
|
||||
# Start ad hoc command running when created.
|
||||
ad_hoc_command = get_object_or_400(self.model, pk=response.data['id'])
|
||||
result = ad_hoc_command.signal_start(**request.DATA)
|
||||
result = ad_hoc_command.signal_start(**request.data)
|
||||
if not result:
|
||||
data = dict(passwords_needed_to_start=ad_hoc_command.passwords_needed_to_start)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2702,21 +2694,21 @@ class AdHocCommandRelaunch(GenericAPIView):
|
||||
data[field[:-3]] = getattr(obj, field)
|
||||
else:
|
||||
data[field] = getattr(obj, field)
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer = AdHocCommandSerializer(data=data, context=self.get_serializer_context())
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Check for passwords needed before copying ad hoc command.
|
||||
needed = obj.passwords_needed_to_start
|
||||
provided = dict([(field, request.DATA.get(field, '')) for field in needed])
|
||||
provided = dict([(field, request.data.get(field, '')) for field in needed])
|
||||
if not all(provided.values()):
|
||||
data = dict(passwords_needed_to_start=needed)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Copy and start the new ad hoc command.
|
||||
new_ad_hoc_command = obj.copy()
|
||||
result = new_ad_hoc_command.signal_start(**request.DATA)
|
||||
result = new_ad_hoc_command.signal_start(**request.data)
|
||||
if not result:
|
||||
data = dict(passwords_needed_to_start=new_ad_hoc_command.passwords_needed_to_start)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
@ -2773,13 +2765,11 @@ class AdHocCommandAdHocCommandEventsList(BaseAdHocCommandEventsList):
|
||||
if request.user:
|
||||
raise PermissionDenied()
|
||||
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
|
||||
data = request.DATA.copy()
|
||||
data['ad_hoc_command'] = parent_obj.pk
|
||||
data = request.data.copy()
|
||||
data['ad_hoc_command'] = parent_obj
|
||||
serializer = self.get_serializer(data=data)
|
||||
if serializer.is_valid():
|
||||
self.pre_save(serializer.object)
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
self.instance = serializer.save()
|
||||
headers = {'Location': serializer.data['url']}
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
@ -2870,11 +2860,11 @@ class UnifiedJobStdout(RetrieveAPIView):
|
||||
return Response(response_message)
|
||||
|
||||
if request.accepted_renderer.format in ('html', 'api', 'json'):
|
||||
content_format = request.QUERY_PARAMS.get('content_format', 'html')
|
||||
content_encoding = request.QUERY_PARAMS.get('content_encoding', None)
|
||||
start_line = request.QUERY_PARAMS.get('start_line', 0)
|
||||
end_line = request.QUERY_PARAMS.get('end_line', None)
|
||||
dark_val = request.QUERY_PARAMS.get('dark', '')
|
||||
content_format = request.query_params.get('content_format', 'html')
|
||||
content_encoding = request.query_params.get('content_encoding', None)
|
||||
start_line = request.query_params.get('start_line', 0)
|
||||
end_line = request.query_params.get('end_line', None)
|
||||
dark_val = request.query_params.get('dark', '')
|
||||
dark = bool(dark_val and dark_val[0].lower() in ('1', 't', 'y'))
|
||||
content_only = bool(request.accepted_renderer.format in ('api', 'json'))
|
||||
dark_bg = (content_only and dark) or (not content_only and (dark or not dark_val))
|
||||
@ -2973,7 +2963,7 @@ class SettingsList(ListCreateAPIView):
|
||||
def get_queryset(self):
|
||||
class SettingsIntermediary(object):
|
||||
def __init__(self, key, description, category, value,
|
||||
value_type, user):
|
||||
value_type, user=None):
|
||||
self.key = key
|
||||
self.description = description
|
||||
self.category = category
|
||||
@ -3004,8 +2994,7 @@ class SettingsList(ListCreateAPIView):
|
||||
m_entry['description'],
|
||||
m_entry['category'],
|
||||
m_entry['default'],
|
||||
m_entry['type'],
|
||||
None))
|
||||
m_entry['type']))
|
||||
return settings_actual
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
@ -3023,7 +3012,7 @@ class SettingsReset(APIView):
|
||||
# NOTE: Extend more with user settings
|
||||
if not request.user.can_access(TowerSettings, 'delete', None):
|
||||
raise PermissionDenied()
|
||||
settings_key = request.DATA.get('key', None)
|
||||
settings_key = request.data.get('key', None)
|
||||
if settings_key is not None:
|
||||
TowerSettings.objects.filter(key=settings_key).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
4
awx/fact/test_fact.py
Normal file
4
awx/fact/test_fact.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from awx.fact.tests import * # noqa
|
||||
@ -684,7 +684,7 @@ class ProjectAccess(BaseAccess):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Project.objects.filter(active=True).distinct()
|
||||
qs = qs.select_related('modified_by', 'credential', 'current_update', 'last_update')
|
||||
qs = qs.select_related('modified_by', 'credential', 'current_job', 'last_job')
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
|
||||
@ -1280,7 +1280,7 @@ class AdHocCommandEventAccess(BaseAccess):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.distinct()
|
||||
qs = qs.select_related('created_by', 'modified_by', 'ad_hoc_command', 'host')
|
||||
qs = qs.select_related('ad_hoc_command', 'host')
|
||||
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
@ -1308,8 +1308,7 @@ class JobHostSummaryAccess(BaseAccess):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.distinct()
|
||||
qs = qs.select_related('created_by', 'modified_by', 'job', 'job__job_template',
|
||||
'host')
|
||||
qs = qs.select_related('job', 'job__job_template', 'host')
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
job_qs = self.user.get_queryset(Job)
|
||||
@ -1334,8 +1333,7 @@ class JobEventAccess(BaseAccess):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.distinct()
|
||||
qs = qs.select_related('created_by', 'modified_by', 'job', 'job__job_template',
|
||||
'host', 'parent')
|
||||
qs = qs.select_related('job', 'job__job_template', 'host', 'parent')
|
||||
qs = qs.prefetch_related('hosts', 'children')
|
||||
|
||||
# Filter certain "internal" events generated by async polling.
|
||||
@ -1495,7 +1493,6 @@ class ActivityStreamAccess(BaseAccess):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.distinct()
|
||||
#qs = qs.select_related('created_by')
|
||||
qs = qs.select_related('actor')
|
||||
qs = qs.prefetch_related('organization', 'user', 'inventory', 'host', 'group', 'inventory_source',
|
||||
'inventory_update', 'credential', 'team', 'project', 'project_update',
|
||||
|
||||
@ -37,16 +37,14 @@ class TowerConfiguration(object):
|
||||
if key not in settings_manifest:
|
||||
raise AttributeError("Tower Setting with key '{0}' does not exist".format(key))
|
||||
settings_entry = settings_manifest[key]
|
||||
settings_actual = TowerSettings.objects.filter(key=key)
|
||||
if not settings_actual.exists():
|
||||
try:
|
||||
settings_actual = TowerSettings.objects.get(key=key)
|
||||
except TowerSettings.DoesNotExist:
|
||||
settings_actual = TowerSettings(key=key,
|
||||
description=settings_entry['description'],
|
||||
category=settings_entry['category'],
|
||||
value=value,
|
||||
value_type=settings_entry['type'])
|
||||
else:
|
||||
settings_actual = settings_actual[0]
|
||||
settings_actual.value = value
|
||||
settings_actual.value_converted = value
|
||||
settings_actual.save()
|
||||
|
||||
tower_settings = TowerConfiguration()
|
||||
|
||||
@ -5,9 +5,6 @@
|
||||
from django.db import models
|
||||
from django.db.models.fields.related import SingleRelatedObjectDescriptor
|
||||
|
||||
# South
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
|
||||
__all__ = ['AutoOneToOneField']
|
||||
|
||||
# Based on AutoOneToOneField from django-annoying:
|
||||
@ -20,8 +17,8 @@ class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
||||
try:
|
||||
return super(AutoSingleRelatedObjectDescriptor,
|
||||
self).__get__(instance, instance_type)
|
||||
except self.related.model.DoesNotExist:
|
||||
obj = self.related.model(**{self.related.field.name: instance})
|
||||
except self.related.related_model.DoesNotExist:
|
||||
obj = self.related.related_model(**{self.related.field.name: instance})
|
||||
if self.related.field.rel.parent_link:
|
||||
raise NotImplementedError('not supported with polymorphic!')
|
||||
for f in instance._meta.local_fields:
|
||||
@ -35,6 +32,3 @@ class AutoOneToOneField(models.OneToOneField):
|
||||
def contribute_to_related_class(self, cls, related):
|
||||
setattr(cls, related.get_accessor_name(),
|
||||
AutoSingleRelatedObjectDescriptor(related))
|
||||
|
||||
add_introspection_rules([([AutoOneToOneField], [], {})],
|
||||
[r'^awx\.main\.fields\.AutoOneToOneField'])
|
||||
|
||||
@ -1110,13 +1110,14 @@ class Command(NoArgsCommand):
|
||||
for db_group in self.inventory.groups.filter(name__in=group_names):
|
||||
mem_group = self.all_group.all_groups[db_group.name]
|
||||
group_group_count += len(mem_group.children)
|
||||
child_names = set([g.name for g in mem_group.children])
|
||||
db_children_qs = self.inventory.groups.filter(name__in=child_names)
|
||||
# FIXME: May fail unit tests when len(child_names) > 1000.
|
||||
for db_child in db_children_qs.filter(children__id=db_group.id):
|
||||
self.logger.info('Group "%s" already child of group "%s"', db_child.name, db_group.name)
|
||||
for db_child in db_children_qs.exclude(children__id=db_group.id):
|
||||
self._batch_add_m2m(db_group.children, db_child)
|
||||
all_child_names = sorted([g.name for g in mem_group.children])
|
||||
for offset2 in xrange(0, len(all_child_names), self._batch_size):
|
||||
child_names = all_child_names[offset2:(offset2 + self._batch_size)]
|
||||
db_children_qs = self.inventory.groups.filter(name__in=child_names)
|
||||
for db_child in db_children_qs.filter(children__id=db_group.id):
|
||||
self.logger.info('Group "%s" already child of group "%s"', db_child.name, db_group.name)
|
||||
for db_child in db_children_qs.exclude(children__id=db_group.id):
|
||||
self._batch_add_m2m(db_group.children, db_child)
|
||||
self.logger.info('Group "%s" added as child of "%s"', db_child.name, db_group.name)
|
||||
self._batch_add_m2m(db_group.children, flush=True)
|
||||
if settings.SQL_DEBUG:
|
||||
|
||||
@ -16,7 +16,7 @@ from django.conf import settings
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from django.db import transaction, DatabaseError
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils.tzinfo import FixedOffset
|
||||
from django.utils.timezone import FixedOffset
|
||||
from django.db import connection
|
||||
|
||||
# AWX
|
||||
|
||||
@ -12,10 +12,11 @@ class HostManager(models.Manager):
|
||||
|
||||
def active_count(self):
|
||||
"""Return count of active, unique hosts for licensing."""
|
||||
try:
|
||||
return self.filter(active=True, inventory__active=True).distinct('name').count()
|
||||
except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name')
|
||||
return len(set(self.filter(active=True, inventory__active=True).values_list('name', flat=True)))
|
||||
#try:
|
||||
# return self.filter(active=True, inventory__active=True).distinct('name').count()
|
||||
#except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name')
|
||||
# FIXME-CC
|
||||
return len(set(self.filter(active=True, inventory__active=True).values_list('name', flat=True)))
|
||||
|
||||
class InstanceManager(models.Manager):
|
||||
"""A custom manager class for the Instance model.
|
||||
|
||||
991
awx/main/migrations/0001_initial.py
Normal file
991
awx/main/migrations/0001_initial.py
Normal file
@ -0,0 +1,991 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
import jsonfield.fields
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
import taggit.managers
|
||||
import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ActivityStream',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('operation', models.CharField(max_length=13, choices=[(b'create', 'Entity Created'), (b'update', 'Entity Updated'), (b'delete', 'Entity Deleted'), (b'associate', 'Entity Associated with another Entity'), (b'disassociate', 'Entity was Disassociated with another Entity')])),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('changes', models.TextField(blank=True)),
|
||||
('object_relationship_type', models.TextField(blank=True)),
|
||||
('object1', models.TextField()),
|
||||
('object2', models.TextField()),
|
||||
('actor', models.ForeignKey(related_name='activity_stream', on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdHocCommandEvent',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('host_name', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('event', models.CharField(max_length=100, choices=[(b'runner_on_failed', 'Host Failed'), (b'runner_on_ok', 'Host OK'), (b'runner_on_unreachable', 'Host Unreachable')])),
|
||||
('event_data', jsonfield.fields.JSONField(default={}, blank=True)),
|
||||
('failed', models.BooleanField(default=False, editable=False)),
|
||||
('changed', models.BooleanField(default=False, editable=False)),
|
||||
('counter', models.PositiveIntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-pk',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AuthToken',
|
||||
fields=[
|
||||
('key', models.CharField(max_length=40, serialize=False, primary_key=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('expires', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('request_hash', models.CharField(default=b'', max_length=40, blank=True)),
|
||||
('reason', models.CharField(default=b'', help_text='Reason the auth token was invalidated.', max_length=1024, blank=True)),
|
||||
('user', models.ForeignKey(related_name='auth_tokens', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Credential',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('kind', models.CharField(default=b'ssh', max_length=32, choices=[(b'ssh', 'Machine'), (b'scm', 'Source Control'), (b'aws', 'Amazon Web Services'), (b'rax', 'Rackspace'), (b'vmware', 'VMware vCenter'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure'), (b'openstack', 'OpenStack')])),
|
||||
('cloud', models.BooleanField(default=False, editable=False)),
|
||||
('host', models.CharField(default=b'', help_text='The hostname or IP address to use.', max_length=1024, verbose_name='Host', blank=True)),
|
||||
('username', models.CharField(default=b'', help_text='Username for this credential.', max_length=1024, verbose_name='Username', blank=True)),
|
||||
('password', models.CharField(default=b'', help_text='Password for this credential (or "ASK" to prompt the user for machine credentials).', max_length=1024, verbose_name='Password', blank=True)),
|
||||
('security_token', models.CharField(default=b'', help_text='Security Token for this credential', max_length=1024, verbose_name='Security Token', blank=True)),
|
||||
('project', models.CharField(default=b'', help_text='The identifier for the project.', max_length=100, verbose_name='Project', blank=True)),
|
||||
('ssh_key_data', models.TextField(default=b'', help_text='RSA or DSA private key to be used instead of password.', verbose_name='SSH private key', blank=True)),
|
||||
('ssh_key_unlock', models.CharField(default=b'', help_text='Passphrase to unlock SSH private key if encrypted (or "ASK" to prompt the user for machine credentials).', max_length=1024, verbose_name='SSH key unlock', blank=True)),
|
||||
('become_method', models.CharField(default=b'', help_text='Privilege escalation method.', max_length=32, blank=True, choices=[(b'', 'None'), (b'sudo', 'Sudo'), (b'su', 'Su'), (b'pbrun', 'Pbrun'), (b'pfexec', 'Pfexec')])),
|
||||
('become_username', models.CharField(default=b'', help_text='Privilege escalation username.', max_length=1024, blank=True)),
|
||||
('become_password', models.CharField(default=b'', help_text='Password for privilege escalation method.', max_length=1024, blank=True)),
|
||||
('vault_password', models.CharField(default=b'', help_text='Vault password (or "ASK" to prompt the user).', max_length=1024, blank=True)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'credential', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'credential', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('kind', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomInventoryScript',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('script', models.TextField(default=b'', help_text='Inventory script contents', blank=True)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'custominventoryscript', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'custominventoryscript', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('variables', models.TextField(default=b'', help_text='Group variables in JSON or YAML format.', blank=True)),
|
||||
('total_hosts', models.PositiveIntegerField(default=0, help_text='Total number of hosts directly or indirectly in this group.', editable=False)),
|
||||
('has_active_failures', models.BooleanField(default=False, help_text='Flag indicating whether this group has any hosts with active failures.', editable=False)),
|
||||
('hosts_with_active_failures', models.PositiveIntegerField(default=0, help_text='Number of hosts in this group with active failures.', editable=False)),
|
||||
('total_groups', models.PositiveIntegerField(default=0, help_text='Total number of child groups contained within this group.', editable=False)),
|
||||
('groups_with_active_failures', models.PositiveIntegerField(default=0, help_text='Number of child groups within this group that have active failures.', editable=False)),
|
||||
('has_inventory_sources', models.BooleanField(default=False, help_text='Flag indicating whether this group was created/updated from any external inventory sources.', editable=False)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'group', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Host',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('enabled', models.BooleanField(default=True, help_text='Is this host online and available for running jobs?')),
|
||||
('instance_id', models.CharField(default=b'', max_length=100, blank=True)),
|
||||
('variables', models.TextField(default=b'', help_text='Host variables in JSON or YAML format.', blank=True)),
|
||||
('has_active_failures', models.BooleanField(default=False, help_text='Flag indicating whether the last job failed for this host.', editable=False)),
|
||||
('has_inventory_sources', models.BooleanField(default=False, help_text='Flag indicating whether this host was created/updated from any external inventory sources.', editable=False)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'host', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('inventory', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Instance',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('uuid', models.CharField(unique=True, max_length=40)),
|
||||
('hostname', models.CharField(unique=True, max_length=250)),
|
||||
('primary', models.BooleanField(default=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Inventory',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=512)),
|
||||
('variables', models.TextField(default=b'', help_text='Inventory variables in JSON or YAML format.', blank=True)),
|
||||
('has_active_failures', models.BooleanField(default=False, help_text='Flag indicating whether any hosts in this inventory have failed.', editable=False)),
|
||||
('total_hosts', models.PositiveIntegerField(default=0, help_text='Total number of hosts in this inventory.', editable=False)),
|
||||
('hosts_with_active_failures', models.PositiveIntegerField(default=0, help_text='Number of hosts in this inventory with active failures.', editable=False)),
|
||||
('total_groups', models.PositiveIntegerField(default=0, help_text='Total number of groups in this inventory.', editable=False)),
|
||||
('groups_with_active_failures', models.PositiveIntegerField(default=0, help_text='Number of groups in this inventory with active failures.', editable=False)),
|
||||
('has_inventory_sources', models.BooleanField(default=False, help_text='Flag indicating whether this inventory has any external inventory sources.', editable=False)),
|
||||
('total_inventory_sources', models.PositiveIntegerField(default=0, help_text='Total number of external inventory sources configured within this inventory.', editable=False)),
|
||||
('inventory_sources_with_failures', models.PositiveIntegerField(default=0, help_text='Number of external inventory sources in this inventory with failures.', editable=False)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'inventory', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'inventory', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
'verbose_name_plural': 'inventories',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JobEvent',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('event', models.CharField(max_length=100, choices=[(b'runner_on_failed', 'Host Failed'), (b'runner_on_ok', 'Host OK'), (b'runner_on_error', 'Host Failure'), (b'runner_on_skipped', 'Host Skipped'), (b'runner_on_unreachable', 'Host Unreachable'), (b'runner_on_no_hosts', 'No Hosts Remaining'), (b'runner_on_async_poll', 'Host Polling'), (b'runner_on_async_ok', 'Host Async OK'), (b'runner_on_async_failed', 'Host Async Failure'), (b'runner_on_file_diff', 'File Difference'), (b'playbook_on_start', 'Playbook Started'), (b'playbook_on_notify', 'Running Handlers'), (b'playbook_on_no_hosts_matched', 'No Hosts Matched'), (b'playbook_on_no_hosts_remaining', 'No Hosts Remaining'), (b'playbook_on_task_start', 'Task Started'), (b'playbook_on_vars_prompt', 'Variables Prompted'), (b'playbook_on_setup', 'Gathering Facts'), (b'playbook_on_import_for_host', 'internal: on Import for Host'), (b'playbook_on_not_import_for_host', 'internal: on Not Import for Host'), (b'playbook_on_play_start', 'Play Started'), (b'playbook_on_stats', 'Playbook Complete')])),
|
||||
('event_data', jsonfield.fields.JSONField(default={}, blank=True)),
|
||||
('failed', models.BooleanField(default=False, editable=False)),
|
||||
('changed', models.BooleanField(default=False, editable=False)),
|
||||
('host_name', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('play', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('role', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('task', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('counter', models.PositiveIntegerField(default=0)),
|
||||
('host', models.ForeignKey(related_name='job_events_as_primary_host', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Host', null=True)),
|
||||
('hosts', models.ManyToManyField(related_name='job_events', editable=False, to='main.Host')),
|
||||
('parent', models.ForeignKey(related_name='children', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.JobEvent', null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('pk',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JobHostSummary',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('host_name', models.CharField(default=b'', max_length=1024, editable=False)),
|
||||
('changed', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('dark', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('failures', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('ok', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('processed', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('skipped', models.PositiveIntegerField(default=0, editable=False)),
|
||||
('failed', models.BooleanField(default=False, editable=False)),
|
||||
('host', models.ForeignKey(related_name='job_host_summaries', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Host', null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-pk',),
|
||||
'verbose_name_plural': 'job host summaries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JobOrigin',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('instance', models.ForeignKey(to='main.Instance')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organization',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=512)),
|
||||
('admins', models.ManyToManyField(related_name='admin_of_organizations', to=settings.AUTH_USER_MODEL, blank=True)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'organization', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'organization', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
('users', models.ManyToManyField(related_name='organizations', to=settings.AUTH_USER_MODEL, blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Permission',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('permission_type', models.CharField(max_length=64, choices=[(b'read', 'Read Inventory'), (b'write', 'Edit Inventory'), (b'admin', 'Administrate Inventory'), (b'run', 'Deploy To Inventory'), (b'check', 'Deploy To Inventory (Dry Run)'), (b'scan', 'Scan an Inventory'), (b'create', 'Create a Job Template')])),
|
||||
('run_ad_hoc_commands', models.BooleanField(default=False, help_text='Execute Commands on the Inventory')),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'permission', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('inventory', models.ForeignKey(related_name='permissions', on_delete=django.db.models.deletion.SET_NULL, to='main.Inventory', null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'permission', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Profile',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('ldap_dn', models.CharField(default=b'', max_length=1024)),
|
||||
('user', awx.main.fields.AutoOneToOneField(related_name='profile', editable=False, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Schedule',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(unique=True, max_length=512)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
('dtstart', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('dtend', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('rrule', models.CharField(max_length=255)),
|
||||
('next_run', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('extra_data', jsonfield.fields.JSONField(default={}, blank=True)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'schedule', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'schedule', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-next_run'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Team',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('created_by', models.ForeignKey(related_name="{u'class': 'team', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('modified_by', models.ForeignKey(related_name="{u'class': 'team', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('organization', models.ForeignKey(related_name='teams', on_delete=django.db.models.deletion.SET_NULL, to='main.Organization', null=True)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
('users', models.ManyToManyField(related_name='teams', to=settings.AUTH_USER_MODEL, blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('organization__name', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UnifiedJob',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('old_pk', models.PositiveIntegerField(default=None, null=True, editable=False)),
|
||||
('launch_type', models.CharField(default=b'manual', max_length=20, editable=False, choices=[(b'manual', 'Manual'), (b'relaunch', 'Relaunch'), (b'callback', 'Callback'), (b'scheduled', 'Scheduled'), (b'dependency', 'Dependency')])),
|
||||
('cancel_flag', models.BooleanField(default=False, editable=False)),
|
||||
('status', models.CharField(default=b'new', max_length=20, editable=False, choices=[(b'new', 'New'), (b'pending', 'Pending'), (b'waiting', 'Waiting'), (b'running', 'Running'), (b'successful', 'Successful'), (b'failed', 'Failed'), (b'error', 'Error'), (b'canceled', 'Canceled')])),
|
||||
('failed', models.BooleanField(default=False, editable=False)),
|
||||
('started', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('finished', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('elapsed', models.DecimalField(editable=False, max_digits=12, decimal_places=3)),
|
||||
('job_args', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('job_cwd', models.CharField(default=b'', max_length=1024, editable=False, blank=True)),
|
||||
('job_env', jsonfield.fields.JSONField(default={}, editable=False, blank=True)),
|
||||
('job_explanation', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('start_args', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('result_stdout_text', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('result_stdout_file', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('result_traceback', models.TextField(default=b'', editable=False, blank=True)),
|
||||
('celery_task_id', models.CharField(default=b'', max_length=100, editable=False, blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UnifiedJobTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('description', models.TextField(default=b'', blank=True)),
|
||||
('active', models.BooleanField(default=True, editable=False)),
|
||||
('name', models.CharField(max_length=512)),
|
||||
('old_pk', models.PositiveIntegerField(default=None, null=True, editable=False)),
|
||||
('last_job_failed', models.BooleanField(default=False, editable=False)),
|
||||
('last_job_run', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('has_schedules', models.BooleanField(default=False, editable=False)),
|
||||
('next_job_run', models.DateTimeField(default=None, null=True, editable=False)),
|
||||
('status', models.CharField(default=b'ok', max_length=32, editable=False, choices=[(b'new', 'New'), (b'pending', 'Pending'), (b'waiting', 'Waiting'), (b'running', 'Running'), (b'successful', 'Successful'), (b'failed', 'Failed'), (b'error', 'Error'), (b'canceled', 'Canceled'), (b'never updated', b'Never Updated'), (b'ok', b'OK'), (b'missing', b'Missing'), (b'none', 'No External Source'), (b'updating', 'Updating')])),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdHocCommand',
|
||||
fields=[
|
||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||
('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check'), (b'scan', 'Scan')])),
|
||||
('limit', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('module_name', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('module_args', models.TextField(default=b'', blank=True)),
|
||||
('forks', models.PositiveIntegerField(default=0, blank=True)),
|
||||
('verbosity', models.PositiveIntegerField(default=0, blank=True, choices=[(0, b'0 (Normal)'), (1, b'1 (Verbose)'), (2, b'2 (More Verbose)'), (3, b'3 (Debug)'), (4, b'4 (Connection Debug)'), (5, b'5 (WinRM Debug)')])),
|
||||
('become_enabled', models.BooleanField(default=False)),
|
||||
],
|
||||
bases=('main.unifiedjob',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InventorySource',
|
||||
fields=[
|
||||
('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')),
|
||||
('source', models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'Local File, Directory or Script'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure'), (b'vmware', 'VMware vCenter'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')])),
|
||||
('source_path', models.CharField(default=b'', max_length=1024, editable=False, blank=True)),
|
||||
('source_vars', models.TextField(default=b'', help_text='Inventory source variables in YAML or JSON format.', blank=True)),
|
||||
('source_regions', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('instance_filters', models.CharField(default=b'', help_text='Comma-separated list of filter expressions (EC2 only). Hosts are imported when ANY of the filters match.', max_length=1024, blank=True)),
|
||||
('group_by', models.CharField(default=b'', help_text='Limit groups automatically created from inventory source (EC2 only).', max_length=1024, blank=True)),
|
||||
('overwrite', models.BooleanField(default=False, help_text='Overwrite local groups and hosts from remote inventory source.')),
|
||||
('overwrite_vars', models.BooleanField(default=False, help_text='Overwrite local variables from remote inventory source.')),
|
||||
('update_on_launch', models.BooleanField(default=False)),
|
||||
('update_cache_timeout', models.PositiveIntegerField(default=0)),
|
||||
],
|
||||
bases=('main.unifiedjobtemplate', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InventoryUpdate',
|
||||
fields=[
|
||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||
('source', models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'Local File, Directory or Script'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure'), (b'vmware', 'VMware vCenter'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')])),
|
||||
('source_path', models.CharField(default=b'', max_length=1024, editable=False, blank=True)),
|
||||
('source_vars', models.TextField(default=b'', help_text='Inventory source variables in YAML or JSON format.', blank=True)),
|
||||
('source_regions', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('instance_filters', models.CharField(default=b'', help_text='Comma-separated list of filter expressions (EC2 only). Hosts are imported when ANY of the filters match.', max_length=1024, blank=True)),
|
||||
('group_by', models.CharField(default=b'', help_text='Limit groups automatically created from inventory source (EC2 only).', max_length=1024, blank=True)),
|
||||
('overwrite', models.BooleanField(default=False, help_text='Overwrite local groups and hosts from remote inventory source.')),
|
||||
('overwrite_vars', models.BooleanField(default=False, help_text='Overwrite local variables from remote inventory source.')),
|
||||
('license_error', models.BooleanField(default=False, editable=False)),
|
||||
],
|
||||
bases=('main.unifiedjob', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Job',
|
||||
fields=[
|
||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||
('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check'), (b'scan', 'Scan')])),
|
||||
('playbook', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('forks', models.PositiveIntegerField(default=0, blank=True)),
|
||||
('limit', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('verbosity', models.PositiveIntegerField(default=0, blank=True, choices=[(0, b'0 (Normal)'), (1, b'1 (Verbose)'), (2, b'2 (More Verbose)'), (3, b'3 (Debug)'), (4, b'4 (Connection Debug)'), (5, b'5 (WinRM Debug)')])),
|
||||
('extra_vars', models.TextField(default=b'', blank=True)),
|
||||
('job_tags', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('force_handlers', models.BooleanField(default=False)),
|
||||
('skip_tags', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('start_at_task', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('become_enabled', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('id',),
|
||||
},
|
||||
bases=('main.unifiedjob', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JobTemplate',
|
||||
fields=[
|
||||
('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')),
|
||||
('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check'), (b'scan', 'Scan')])),
|
||||
('playbook', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('forks', models.PositiveIntegerField(default=0, blank=True)),
|
||||
('limit', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('verbosity', models.PositiveIntegerField(default=0, blank=True, choices=[(0, b'0 (Normal)'), (1, b'1 (Verbose)'), (2, b'2 (More Verbose)'), (3, b'3 (Debug)'), (4, b'4 (Connection Debug)'), (5, b'5 (WinRM Debug)')])),
|
||||
('extra_vars', models.TextField(default=b'', blank=True)),
|
||||
('job_tags', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('force_handlers', models.BooleanField(default=False)),
|
||||
('skip_tags', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('start_at_task', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('become_enabled', models.BooleanField(default=False)),
|
||||
('host_config_key', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||
('ask_variables_on_launch', models.BooleanField(default=False)),
|
||||
('survey_enabled', models.BooleanField(default=False)),
|
||||
('survey_spec', jsonfield.fields.JSONField(default={}, blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
},
|
||||
bases=('main.unifiedjobtemplate', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Project',
|
||||
fields=[
|
||||
('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')),
|
||||
('local_path', models.CharField(help_text='Local path (relative to PROJECTS_ROOT) containing playbooks and related files for this project.', max_length=1024, blank=True)),
|
||||
('scm_type', models.CharField(default=b'', max_length=8, verbose_name='SCM Type', blank=True, choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion')])),
|
||||
('scm_url', models.CharField(default=b'', max_length=1024, verbose_name='SCM URL', blank=True)),
|
||||
('scm_branch', models.CharField(default=b'', help_text='Specific branch, tag or commit to checkout.', max_length=256, verbose_name='SCM Branch', blank=True)),
|
||||
('scm_clean', models.BooleanField(default=False)),
|
||||
('scm_delete_on_update', models.BooleanField(default=False)),
|
||||
('scm_delete_on_next_update', models.BooleanField(default=False, editable=False)),
|
||||
('scm_update_on_launch', models.BooleanField(default=False)),
|
||||
('scm_update_cache_timeout', models.PositiveIntegerField(default=0, blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('id',),
|
||||
},
|
||||
bases=('main.unifiedjobtemplate', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectUpdate',
|
||||
fields=[
|
||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||
('local_path', models.CharField(help_text='Local path (relative to PROJECTS_ROOT) containing playbooks and related files for this project.', max_length=1024, blank=True)),
|
||||
('scm_type', models.CharField(default=b'', max_length=8, verbose_name='SCM Type', blank=True, choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion')])),
|
||||
('scm_url', models.CharField(default=b'', max_length=1024, verbose_name='SCM URL', blank=True)),
|
||||
('scm_branch', models.CharField(default=b'', help_text='Specific branch, tag or commit to checkout.', max_length=256, verbose_name='SCM Branch', blank=True)),
|
||||
('scm_clean', models.BooleanField(default=False)),
|
||||
('scm_delete_on_update', models.BooleanField(default=False)),
|
||||
],
|
||||
bases=('main.unifiedjob', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemJob',
|
||||
fields=[
|
||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||
('job_type', models.CharField(default=b'', max_length=32, blank=True, choices=[(b'cleanup_jobs', 'Remove jobs older than a certain number of days'), (b'cleanup_activitystream', 'Remove activity stream entries older than a certain number of days'), (b'cleanup_deleted', 'Purge previously deleted items from the database'), (b'cleanup_facts', 'Purge and/or reduce the granularity of system tracking data')])),
|
||||
('extra_vars', models.TextField(default=b'', blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('id',),
|
||||
},
|
||||
bases=('main.unifiedjob', models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemJobTemplate',
|
||||
fields=[
|
||||
('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')),
|
||||
('job_type', models.CharField(default=b'', max_length=32, blank=True, choices=[(b'cleanup_jobs', 'Remove jobs older than a certain number of days'), (b'cleanup_activitystream', 'Remove activity stream entries older than a certain number of days'), (b'cleanup_deleted', 'Purge previously deleted items from the database'), (b'cleanup_facts', 'Purge and/or reduce the granularity of system tracking data')])),
|
||||
],
|
||||
bases=('main.unifiedjobtemplate', models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'unifiedjobtemplate', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='current_job',
|
||||
field=models.ForeignKey(related_name='unifiedjobtemplate_as_current_job+', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.UnifiedJob', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='last_job',
|
||||
field=models.ForeignKey(related_name='unifiedjobtemplate_as_last_job+', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.UnifiedJob', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'unifiedjobtemplate', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='next_schedule',
|
||||
field=models.ForeignKey(related_name='unifiedjobtemplate_as_next_schedule+', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Schedule', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='polymorphic_ctype',
|
||||
field=models.ForeignKey(related_name='polymorphic_main.unifiedjobtemplate_set+', editable=False, to='contenttypes.ContentType', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'unifiedjob', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='dependent_jobs',
|
||||
field=models.ManyToManyField(related_name='_unifiedjob_dependent_jobs_+', editable=False, to='main.UnifiedJob'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'unifiedjob', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='polymorphic_ctype',
|
||||
field=models.ForeignKey(related_name='polymorphic_main.unifiedjob_set+', editable=False, to='contenttypes.ContentType', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='schedule',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Schedule', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='unifiedjob',
|
||||
name='unified_job_template',
|
||||
field=models.ForeignKey(related_name='unifiedjob_unified_jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.UnifiedJobTemplate', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='unified_job_template',
|
||||
field=models.ForeignKey(related_name='schedules', to='main.UnifiedJobTemplate'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='permission',
|
||||
name='team',
|
||||
field=models.ForeignKey(related_name='permissions', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='main.Team', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='permission',
|
||||
name='user',
|
||||
field=models.ForeignKey(related_name='permissions', on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='joborigin',
|
||||
name='unified_job',
|
||||
field=models.OneToOneField(related_name='job_origin', to='main.UnifiedJob'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventory',
|
||||
name='organization',
|
||||
field=models.ForeignKey(related_name='inventories', to='main.Organization', help_text='Organization containing this inventory.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventory',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='hosts', to='main.Inventory'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='last_job_host_summary',
|
||||
field=models.ForeignKey(related_name='hosts_as_last_job_summary+', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, editable=False, to='main.JobHostSummary', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'host', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='hosts',
|
||||
field=models.ManyToManyField(help_text='Hosts associated directly with this group.', related_name='groups', to='main.Host', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='groups', to='main.Inventory'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(related_name="{u'class': 'group', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='parents',
|
||||
field=models.ManyToManyField(related_name='children', to='main.Group', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='custominventoryscript',
|
||||
name='organization',
|
||||
field=models.ForeignKey(related_name='custom_inventory_scripts', on_delete=django.db.models.deletion.SET_NULL, to='main.Organization', help_text='Organization owning this inventory script', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='custominventoryscript',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='credential',
|
||||
name='team',
|
||||
field=models.ForeignKey(related_name='credentials', default=None, blank=True, to='main.Team', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='credential',
|
||||
name='user',
|
||||
field=models.ForeignKey(related_name='credentials', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adhoccommandevent',
|
||||
name='host',
|
||||
field=models.ForeignKey(related_name='ad_hoc_command_events', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Host', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='credential',
|
||||
field=models.ManyToManyField(to='main.Credential', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='custom_inventory_script',
|
||||
field=models.ManyToManyField(to='main.CustomInventoryScript', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='group',
|
||||
field=models.ManyToManyField(to='main.Group', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='host',
|
||||
field=models.ManyToManyField(to='main.Host', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='inventory',
|
||||
field=models.ManyToManyField(to='main.Inventory', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='organization',
|
||||
field=models.ManyToManyField(to='main.Organization', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='permission',
|
||||
field=models.ManyToManyField(to='main.Permission', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='schedule',
|
||||
field=models.ManyToManyField(to='main.Schedule', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='team',
|
||||
field=models.ManyToManyField(to='main.Team', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='unified_job',
|
||||
field=models.ManyToManyField(related_name='_activitystream_unified_job_+', to='main.UnifiedJob', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='unified_job_template',
|
||||
field=models.ManyToManyField(related_name='_activitystream_unified_job_template_+', to='main.UnifiedJobTemplate', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='user',
|
||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, blank=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='unifiedjobtemplate',
|
||||
unique_together=set([('polymorphic_ctype', 'name')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='projects',
|
||||
field=models.ManyToManyField(related_name='teams', to='main.Project', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemjob',
|
||||
name='system_job_template',
|
||||
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.SystemJobTemplate', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='projectupdate',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='projectupdates', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='projectupdate',
|
||||
name='project',
|
||||
field=models.ForeignKey(related_name='project_updates', editable=False, to='main.Project'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='projects', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='permission',
|
||||
name='project',
|
||||
field=models.ForeignKey(related_name='permissions', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='main.Project', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='projects',
|
||||
field=models.ManyToManyField(related_name='organizations', to='main.Project', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobtemplate',
|
||||
name='cloud_credential',
|
||||
field=models.ForeignKey(related_name='jobtemplates_as_cloud_credential+', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobtemplate',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='jobtemplates', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobtemplate',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='jobtemplates', on_delete=django.db.models.deletion.SET_NULL, to='main.Inventory', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobtemplate',
|
||||
name='project',
|
||||
field=models.ForeignKey(related_name='jobtemplates', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Project', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobhostsummary',
|
||||
name='job',
|
||||
field=models.ForeignKey(related_name='job_host_summaries', editable=False, to='main.Job'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='jobevent',
|
||||
name='job',
|
||||
field=models.ForeignKey(related_name='job_events', editable=False, to='main.Job'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='cloud_credential',
|
||||
field=models.ForeignKey(related_name='jobs_as_cloud_credential+', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='hosts',
|
||||
field=models.ManyToManyField(related_name='jobs', editable=False, through='main.JobHostSummary', to='main.Host'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, to='main.Inventory', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='job_template',
|
||||
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.JobTemplate', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='project',
|
||||
field=models.ForeignKey(related_name='jobs', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Project', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryupdate',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='inventoryupdates', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryupdate',
|
||||
name='inventory_source',
|
||||
field=models.ForeignKey(related_name='inventory_updates', editable=False, to='main.InventorySource'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryupdate',
|
||||
name='source_script',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.CustomInventoryScript', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventorysource',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='inventorysources', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventorysource',
|
||||
name='group',
|
||||
field=awx.main.fields.AutoOneToOneField(related_name='inventory_source', null=True, default=None, editable=False, to='main.Group'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventorysource',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='inventory_sources', default=None, editable=False, to='main.Inventory', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventorysource',
|
||||
name='source_script',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.CustomInventoryScript', null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='inventory',
|
||||
unique_together=set([('name', 'organization')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='inventory_sources',
|
||||
field=models.ManyToManyField(help_text='Inventory source(s) that created or modified this host.', related_name='hosts', editable=False, to='main.InventorySource'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='last_job',
|
||||
field=models.ForeignKey(related_name='hosts_as_last_job+', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Job', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='inventory_sources',
|
||||
field=models.ManyToManyField(help_text='Inventory source(s) that created or modified this group.', related_name='groups', editable=False, to='main.InventorySource'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='custominventoryscript',
|
||||
unique_together=set([('name', 'organization')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='credential',
|
||||
unique_together=set([('user', 'team', 'kind', 'name')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adhoccommandevent',
|
||||
name='ad_hoc_command',
|
||||
field=models.ForeignKey(related_name='ad_hoc_command_events', editable=False, to='main.AdHocCommand'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adhoccommand',
|
||||
name='credential',
|
||||
field=models.ForeignKey(related_name='ad_hoc_commands', on_delete=django.db.models.deletion.SET_NULL, default=None, to='main.Credential', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adhoccommand',
|
||||
name='hosts',
|
||||
field=models.ManyToManyField(related_name='ad_hoc_commands', editable=False, through='main.AdHocCommandEvent', to='main.Host'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adhoccommand',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(related_name='ad_hoc_commands', on_delete=django.db.models.deletion.SET_NULL, to='main.Inventory', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='ad_hoc_command',
|
||||
field=models.ManyToManyField(to='main.AdHocCommand', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='inventory_source',
|
||||
field=models.ManyToManyField(to='main.InventorySource', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='inventory_update',
|
||||
field=models.ManyToManyField(to='main.InventoryUpdate', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='job',
|
||||
field=models.ManyToManyField(to='main.Job', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='job_template',
|
||||
field=models.ManyToManyField(to='main.JobTemplate', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='project',
|
||||
field=models.ManyToManyField(to='main.Project', blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitystream',
|
||||
name='project_update',
|
||||
field=models.ManyToManyField(to='main.ProjectUpdate', blank=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='team',
|
||||
unique_together=set([('organization', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='jobhostsummary',
|
||||
unique_together=set([('job', 'host_name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='host',
|
||||
unique_together=set([('name', 'inventory')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='group',
|
||||
unique_together=set([('name', 'inventory')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adhoccommandevent',
|
||||
unique_together=set([('ad_hoc_command', 'host_name')]),
|
||||
),
|
||||
]
|
||||
34
awx/main/migrations/0002_v300_changes.py
Normal file
34
awx/main/migrations/0002_v300_changes.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('main', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TowerSettings',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('key', models.CharField(unique=True, max_length=255)),
|
||||
('description', models.TextField()),
|
||||
('category', models.CharField(max_length=128)),
|
||||
('value', models.TextField()),
|
||||
('value_type', models.CharField(max_length=12, choices=[(b'string', 'String'), (b'int', 'Integer'), (b'float', 'Decimal'), (b'json', 'JSON'), (b'bool', 'Boolean'), (b'password', 'Password'), (b'list', 'List')])),
|
||||
('user', models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,2 +1,2 @@
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# Copyright (c) 2016 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
@ -30,9 +30,6 @@ __all__ = ['AdHocCommand', 'AdHocCommandEvent']
|
||||
|
||||
class AdHocCommand(UnifiedJob):
|
||||
|
||||
MODULE_NAME_CHOICES = [(x,x) for x in tower_settings.AD_HOC_COMMANDS]
|
||||
MODULE_NAME_DEFAULT = 'command' if 'command' in tower_settings.AD_HOC_COMMANDS else None
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'main'
|
||||
|
||||
@ -61,9 +58,8 @@ class AdHocCommand(UnifiedJob):
|
||||
)
|
||||
module_name = models.CharField(
|
||||
max_length=1024,
|
||||
default=MODULE_NAME_DEFAULT,
|
||||
choices=MODULE_NAME_CHOICES,
|
||||
blank=bool(MODULE_NAME_DEFAULT),
|
||||
default='',
|
||||
blank=True,
|
||||
)
|
||||
module_args = models.TextField(
|
||||
blank=True,
|
||||
@ -88,6 +84,12 @@ class AdHocCommand(UnifiedJob):
|
||||
through='AdHocCommandEvent',
|
||||
)
|
||||
|
||||
def clean_inventory(self):
|
||||
inv = self.inventory
|
||||
if not inv or not inv.active:
|
||||
raise ValidationError('Inventory is no longer available.')
|
||||
return inv
|
||||
|
||||
def clean_credential(self):
|
||||
cred = self.credential
|
||||
if cred and cred.kind != 'ssh':
|
||||
|
||||
@ -9,7 +9,6 @@ import shlex
|
||||
import yaml
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -59,14 +58,14 @@ PERMISSION_TYPE_CHOICES = [
|
||||
|
||||
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'openstack', 'custom']
|
||||
|
||||
VERBOSITY_CHOICES = getattr(settings, 'VERBOSITY_CHOICES', [
|
||||
VERBOSITY_CHOICES = [
|
||||
(0, '0 (Normal)'),
|
||||
(1, '1 (Verbose)'),
|
||||
(2, '2 (More Verbose)'),
|
||||
(3, '3 (Debug)'),
|
||||
(4, '4 (Connection Debug)'),
|
||||
(5, '5 (WinRM Debug)'),
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
class VarsDictProperty(object):
|
||||
@ -157,16 +156,6 @@ class BaseModel(models.Model):
|
||||
self.save(update_fields=update_fields)
|
||||
return update_fields
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# For compatibility with Django 1.4.x, attempt to handle any calls to
|
||||
# save that pass update_fields.
|
||||
try:
|
||||
super(BaseModel, self).save(*args, **kwargs)
|
||||
except TypeError:
|
||||
if 'update_fields' not in kwargs:
|
||||
raise
|
||||
kwargs.pop('update_fields')
|
||||
super(BaseModel, self).save(*args, **kwargs)
|
||||
|
||||
class CreatedModifiedModel(BaseModel):
|
||||
'''
|
||||
|
||||
@ -6,10 +6,13 @@ import json
|
||||
|
||||
# Django
|
||||
from django.db import models
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# Tower
|
||||
from awx.main.models.base import CreatedModifiedModel
|
||||
|
||||
|
||||
class TowerSettings(CreatedModifiedModel):
|
||||
|
||||
class Meta:
|
||||
@ -53,10 +56,21 @@ class TowerSettings(CreatedModifiedModel):
|
||||
elif self.value_type == 'list':
|
||||
converted_type = [x.strip() for x in self.value.split(',')]
|
||||
elif self.value_type == 'bool':
|
||||
converted_type = self.value in [True, "true", "True", 1, "1", "yes"]
|
||||
converted_type = smart_text(self.value).lower() in ('true', 'yes', '1')
|
||||
elif self.value_type == 'string':
|
||||
converted_type = self.value
|
||||
else:
|
||||
t = __builtins__[self.value_type]
|
||||
converted_type = t(self.value)
|
||||
return converted_type
|
||||
|
||||
@value_converted.setter
|
||||
def value_converted(self, value):
|
||||
if self.value_type == 'json':
|
||||
self.value = json.dumps(value)
|
||||
elif self.value_type == 'list':
|
||||
self.value = ','.join(value)
|
||||
elif self.value_type == 'bool':
|
||||
self.value = smart_text(bool(value))
|
||||
else:
|
||||
self.value = smart_text(value)
|
||||
|
||||
@ -539,7 +539,7 @@ class Group(CommonModelNameNotUnique):
|
||||
def mark_actual():
|
||||
all_group_hosts = Group.hosts.through.objects.select_related("host", "group").filter(group__inventory=self.inventory)
|
||||
group_hosts = {'groups': {}, 'hosts': {}}
|
||||
all_group_parents = Group.parents.through.objects.select_related("parent", "group").filter(from_group__inventory=self.inventory)
|
||||
all_group_parents = Group.parents.through.objects.select_related("from_group", "to_group").filter(from_group__inventory=self.inventory)
|
||||
group_children = {}
|
||||
group_parents = {}
|
||||
marked_hosts = []
|
||||
|
||||
@ -317,3 +317,10 @@ def user_mark_inactive(user, save=True):
|
||||
user.save()
|
||||
|
||||
User.add_to_class('mark_inactive', user_mark_inactive)
|
||||
|
||||
|
||||
# Add get_absolute_url method to User model if not present.
|
||||
if not hasattr(User, 'get_absolute_url'):
|
||||
def user_get_absolute_url(user):
|
||||
return reverse('api:user_detail', args=(user.pk,))
|
||||
User.add_to_class('get_absolute_url', user_get_absolute_url)
|
||||
|
||||
@ -11,7 +11,7 @@ import urlparse
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import smart_str, smart_text
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.timezone import now, make_aware, get_default_timezone
|
||||
@ -181,7 +181,7 @@ class ProjectOptions(models.Model):
|
||||
# Filter files in a tasks subdirectory.
|
||||
if 'tasks' in playbook.split(os.sep):
|
||||
continue
|
||||
results.append(playbook)
|
||||
results.append(smart_text(playbook))
|
||||
return sorted(results, key=lambda x: smart_str(x).lower())
|
||||
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ class ScheduleManager(ScheduleFilterMethods, models.Manager):
|
||||
|
||||
use_for_related_objects = True
|
||||
|
||||
def get_query_set(self):
|
||||
def get_queryset(self):
|
||||
return ScheduleQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
|
||||
@ -8,13 +8,13 @@ import logging
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
from collections import OrderedDict
|
||||
from StringIO import StringIO
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.timezone import now
|
||||
|
||||
@ -77,7 +77,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique):
|
||||
('updating', _('Updating')), # Same as running.
|
||||
]
|
||||
|
||||
ALL_STATUS_CHOICES = SortedDict(PROJECT_STATUS_CHOICES + INVENTORY_SOURCE_STATUS_CHOICES + JOB_TEMPLATE_STATUS_CHOICES + DEPRECATED_STATUS_CHOICES).items()
|
||||
ALL_STATUS_CHOICES = OrderedDict(PROJECT_STATUS_CHOICES + INVENTORY_SOURCE_STATUS_CHOICES + JOB_TEMPLATE_STATUS_CHOICES + DEPRECATED_STATUS_CHOICES).items()
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user