Merge branch 'devel' into uxi/templates

This commit is contained in:
Greg Considine 2017-04-05 15:37:49 -04:00 committed by GitHub
commit 81dea61ba9
93 changed files with 1382 additions and 827 deletions

1
.gitignore vendored
View File

@ -108,6 +108,7 @@ reports
*.results *.results
local/ local/
*.mo *.mo
requirements/vendor
# AWX python libs populated by requirements.txt # AWX python libs populated by requirements.txt
awx/lib/.deps_built awx/lib/.deps_built

View File

@ -225,15 +225,18 @@ clean-tmp:
clean-venv: clean-venv:
rm -rf venv/ rm -rf venv/
clean-dist:
rm -rf dist
# Remove temporary build files, compiled Python files. # Remove temporary build files, compiled Python files.
clean: clean-rpm clean-deb clean-ui clean-tar clean-packer clean-bundle clean: clean-rpm clean-deb clean-ui clean-tar clean-packer clean-bundle clean-dist
rm -rf awx/public rm -rf awx/public
rm -rf awx/lib/site-packages rm -rf awx/lib/site-packages
rm -rf dist/*
rm -rf awx/job_status rm -rf awx/job_status
rm -rf awx/job_output rm -rf awx/job_output
rm -rf reports rm -rf reports
rm -f awx/awx_test.sqlite3 rm -f awx/awx_test.sqlite3
rm -rf requirements/vendor
rm -rf tmp rm -rf tmp
mkdir tmp mkdir tmp
rm -rf build $(NAME)-$(VERSION) *.egg-info rm -rf build $(NAME)-$(VERSION) *.egg-info
@ -282,7 +285,11 @@ virtualenv_tower:
fi fi
requirements_ansible: virtualenv_ansible requirements_ansible: virtualenv_ansible
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \
cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed -r /dev/stdin ; \
else \
cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) --ignore-installed -r /dev/stdin ; \
fi
$(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt $(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt
requirements_ansible_dev: requirements_ansible_dev:
@ -292,7 +299,11 @@ requirements_ansible_dev:
# Install third-party requirements needed for Tower's environment. # Install third-party requirements needed for Tower's environment.
requirements_tower: virtualenv_tower requirements_tower: virtualenv_tower
$(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \
cat requirements/requirements.txt requirements/requirements_local.txt | $(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --ignore-installed -r /dev/stdin ; \
else \
cat requirements/requirements.txt requirements/requirements_git.txt | $(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) --ignore-installed -r /dev/stdin ; \
fi
$(VENV_BASE)/tower/bin/pip uninstall --yes -r requirements/requirements_tower_uninstall.txt $(VENV_BASE)/tower/bin/pip uninstall --yes -r requirements/requirements_tower_uninstall.txt
requirements_tower_dev: requirements_tower_dev:
@ -690,39 +701,44 @@ setup_bundle_tarball: setup-bundle-build setup-bundle-build/$(OFFLINE_TAR_FILE)
rpm-build: rpm-build:
mkdir -p $@ mkdir -p $@
rpm-build/$(SDIST_TAR_FILE): rpm-build dist/$(SDIST_TAR_FILE) rpm-build/$(SDIST_TAR_FILE): rpm-build dist/$(SDIST_TAR_FILE) tar-build/$(SETUP_TAR_FILE)
cp packaging/rpm/$(NAME).spec rpm-build/ cp packaging/rpm/$(NAME).spec rpm-build/
cp packaging/rpm/tower.te rpm-build/ cp packaging/rpm/tower.te rpm-build/
cp packaging/rpm/tower.fc rpm-build/ cp packaging/rpm/tower.fc rpm-build/
cp packaging/rpm/$(NAME).sysconfig rpm-build/ cp packaging/rpm/$(NAME).sysconfig rpm-build/
cp packaging/remove_tower_source.py rpm-build/ cp packaging/remove_tower_source.py rpm-build/
cp packaging/bytecompile.sh rpm-build/ cp packaging/bytecompile.sh rpm-build/
cp tar-build/$(SETUP_TAR_FILE) rpm-build/
if [ "$(OFFICIAL)" != "yes" ] ; then \ if [ "$(OFFICIAL)" != "yes" ] ; then \
(cd dist/ && tar zxf $(SDIST_TAR_FILE)) ; \ (cd dist/ && tar zxf $(SDIST_TAR_FILE)) ; \
(cd dist/ && mv $(NAME)-$(VERSION)-$(BUILD) $(NAME)-$(VERSION)) ; \ (cd dist/ && mv $(NAME)-$(VERSION)-$(BUILD) $(NAME)-$(VERSION)) ; \
(cd dist/ && tar czf ../rpm-build/$(SDIST_TAR_FILE) $(NAME)-$(VERSION)) ; \ (cd dist/ && tar czf ../rpm-build/$(SDIST_TAR_FILE) $(NAME)-$(VERSION)) ; \
ln -sf $(SDIST_TAR_FILE) rpm-build/$(NAME)-$(VERSION).tar.gz ; \ ln -sf $(SDIST_TAR_FILE) rpm-build/$(NAME)-$(VERSION).tar.gz ; \
(cd tar-build/ && tar zxf $(SETUP_TAR_FILE)) ; \
(cd tar-build/ && mv $(NAME)-setup-$(VERSION)-$(BUILD) $(NAME)-setup-$(VERSION)) ; \
(cd tar-build/ && tar czf ../rpm-build/$(SETUP_TAR_FILE) $(NAME)-setup-$(VERSION)) ; \
ln -sf $(SETUP_TAR_FILE) rpm-build/$(NAME)-setup-$(VERSION).tar.gz ; \
else \ else \
cp -a dist/$(SDIST_TAR_FILE) rpm-build/ ; \ cp -a dist/$(SDIST_TAR_FILE) rpm-build/ ; \
fi fi
rpmtar: sdist rpm-build/$(SDIST_TAR_FILE) rpmtar: sdist rpm-build/$(SDIST_TAR_FILE)
brewrpmtar: rpm-build/python-deps.tar.gz rpmtar brewrpmtar: rpm-build/python-deps.tar.gz requirements/requirements_local.txt requirements/requirements_ansible_local.txt rpmtar
rpm-build/python-deps.tar.gz: requirements/vendor rpm-build rpm-build/python-deps.tar.gz: requirements/vendor rpm-build
tar czf rpm-build/python-deps.tar.gz requirements/vendor tar czf rpm-build/python-deps.tar.gz requirements/vendor
requirements/vendor: requirements/vendor:
pip download \ cat requirements/requirements.txt requirements/requirements_git.txt | pip download \
--no-binary=:all: \ --no-binary=:all: \
--requirement=requirements/requirements_ansible.txt \ --requirement=/dev/stdin \
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
pip download \ cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | pip download \
--no-binary=:all: \ --no-binary=:all: \
--requirement=requirements/requirements.txt \ --requirement=/dev/stdin \
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
@ -732,6 +748,21 @@ requirements/vendor:
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
requirements/requirements_local.txt:
@echo "This is going to take a while..."
pip download \
--requirement=requirements/requirements_git.txt \
--no-deps \
--exists-action=w \
--dest=requirements/vendor 2>/dev/null | sed -n 's/^\s*Saved\s*//p' > $@
requirements/requirements_ansible_local.txt:
pip download \
--requirement=requirements/requirements_ansible_git.txt \
--no-deps \
--exists-action=w \
--dest=requirements/vendor 2>/dev/null | sed -n 's/^\s*Saved\s*//p' > $@
rpm-build/$(RPM_NVR).src.rpm: /etc/mock/$(MOCK_CFG).cfg rpm-build/$(RPM_NVR).src.rpm: /etc/mock/$(MOCK_CFG).cfg
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build \ $(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build \
--define "tower_version $(VERSION)" --define "tower_release $(RELEASE)" $(SCL_DEFINES) --define "tower_version $(VERSION)" --define "tower_release $(RELEASE)" $(SCL_DEFINES)
@ -760,6 +791,9 @@ rpm-build/$(GPG_FILE): rpm-build
rpm-sign: rpm-build/$(GPG_FILE) rpmtar rpm-build/$(RPM_NVR).$(RPM_ARCH).rpm rpm-sign: rpm-build/$(GPG_FILE) rpmtar rpm-build/$(RPM_NVR).$(RPM_ARCH).rpm
rpm --define "_signature gpg" --define "_gpg_name $(GPG_KEY)" --addsign rpm-build/$(RPM_NVR).$(RPM_ARCH).rpm rpm --define "_signature gpg" --define "_gpg_name $(GPG_KEY)" --addsign rpm-build/$(RPM_NVR).$(RPM_ARCH).rpm
rpm --define "_signature gpg" --define "_gpg_name $(GPG_KEY)" --addsign rpm-build/$(NAME)-ui-$(VERSION)-$(RELEASE)$(RPM_DIST).$(RPM_ARCH).rpm
rpm --define "_signature gpg" --define "_gpg_name $(GPG_KEY)" --addsign rpm-build/$(NAME)-server-$(VERSION)-$(RELEASE)$(RPM_DIST).$(RPM_ARCH).rpm
rpm --define "_signature gpg" --define "_gpg_name $(GPG_KEY)" --addsign rpm-build/$(NAME)-setup-$(VERSION)-$(RELEASE)$(RPM_DIST).$(RPM_ARCH).rpm
endif endif
deb-build: deb-build:

View File

@ -75,7 +75,7 @@ class FieldLookupBackend(BaseFilterBackend):
''' '''
RESERVED_NAMES = ('page', 'page_size', 'format', 'order', 'order_by', RESERVED_NAMES = ('page', 'page_size', 'format', 'order', 'order_by',
'search', 'type') 'search', 'type', 'host_filter')
SUPPORTED_LOOKUPS = ('exact', 'iexact', 'contains', 'icontains', SUPPORTED_LOOKUPS = ('exact', 'iexact', 'contains', 'icontains',
'startswith', 'istartswith', 'endswith', 'iendswith', 'startswith', 'istartswith', 'endswith', 'iendswith',

View File

@ -31,6 +31,7 @@ from awx.api.filters import FieldLookupBackend
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils import * # noqa from awx.main.utils import * # noqa
from awx.api.serializers import ResourceAccessListElementSerializer from awx.api.serializers import ResourceAccessListElementSerializer
from awx.api.versioning import URLPathVersioning
__all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView', 'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView',
@ -86,6 +87,8 @@ def get_view_description(cls, html=False):
class APIView(views.APIView): class APIView(views.APIView):
versioning_class = URLPathVersioning
def initialize_request(self, request, *args, **kwargs): def initialize_request(self, request, *args, **kwargs):
''' '''
Store the Django REST Framework Request object as an attribute on the Store the Django REST Framework Request object as an attribute on the
@ -161,6 +164,7 @@ class APIView(views.APIView):
'new_in_240': getattr(self, 'new_in_240', False), 'new_in_240': getattr(self, 'new_in_240', False),
'new_in_300': getattr(self, 'new_in_300', False), 'new_in_300': getattr(self, 'new_in_300', False),
'new_in_310': getattr(self, 'new_in_310', False), 'new_in_310': getattr(self, 'new_in_310', False),
'new_in_320': getattr(self, 'new_in_320', False),
'deprecated': getattr(self, 'deprecated', False), 'deprecated': getattr(self, 'deprecated', False),
} }
@ -188,6 +192,23 @@ class APIView(views.APIView):
return data return data
def determine_version(self, request, *args, **kwargs):
return (
getattr(request, 'version', None),
getattr(request, 'versioning_scheme', None),
)
def dispatch(self, request, *args, **kwargs):
if self.versioning_class is not None:
scheme = self.versioning_class()
request.version, request.versioning_scheme = (
scheme.determine_version(request, *args, **kwargs),
scheme
)
if 'version' in kwargs:
kwargs.pop('version')
return super(APIView, self).dispatch(request, *args, **kwargs)
class GenericAPIView(generics.GenericAPIView, APIView): class GenericAPIView(generics.GenericAPIView, APIView):
# Base class for all model-based views. # Base class for all model-based views.
@ -424,7 +445,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
obj = serializer.save() obj = serializer.save()
serializer = self.get_serializer(instance=obj) serializer = self.get_serializer(instance=obj)
headers = {'Location': obj.get_absolute_url()} headers = {'Location': obj.get_absolute_url(request)}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

View File

@ -4,9 +4,10 @@
# Python # Python
import copy import copy
import json import json
import logging
import re import re
import six import six
import logging import urllib
from collections import OrderedDict from collections import OrderedDict
from dateutil import rrule from dateutil import rrule
@ -18,7 +19,6 @@ from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -43,11 +43,12 @@ from awx.main.models import * # noqa
from awx.main.access import get_user_capabilities from awx.main.access import get_user_capabilities
from awx.main.fields import ImplicitRoleField from awx.main.fields import ImplicitRoleField
from awx.main.utils import ( from awx.main.utils import (
get_type_for_model, get_model_for_type, build_url, timestamp_apiformat, get_type_for_model, get_model_for_type, timestamp_apiformat,
camelcase_to_underscore, getattrd, parse_yaml_or_json) camelcase_to_underscore, getattrd, parse_yaml_or_json)
from awx.main.validators import vars_validate_or_raise from awx.main.validators import vars_validate_or_raise
from awx.conf.license import feature_enabled from awx.conf.license import feature_enabled
from awx.api.versioning import reverse
from awx.api.fields import BooleanNullField, CharNullField, ChoiceNullField, EncryptedPasswordField, VerbatimField from awx.api.fields import BooleanNullField, CharNullField, ChoiceNullField, EncryptedPasswordField, VerbatimField
logger = logging.getLogger('awx.api.serializers') logger = logging.getLogger('awx.api.serializers')
@ -103,7 +104,7 @@ SUMMARIZABLE_FK_FIELDS = {
} }
def reverse_gfk(content_object): def reverse_gfk(content_object, request):
''' '''
Computes a reverse for a GenericForeignKey field. Computes a reverse for a GenericForeignKey field.
@ -116,7 +117,7 @@ def reverse_gfk(content_object):
return {} return {}
return { return {
camelcase_to_underscore(content_object.__class__.__name__): content_object.get_absolute_url() camelcase_to_underscore(content_object.__class__.__name__): content_object.get_absolute_url(request=request)
} }
@ -268,9 +269,9 @@ class BaseSerializer(serializers.ModelSerializer):
if obj is None or not hasattr(obj, 'get_absolute_url'): if obj is None or not hasattr(obj, 'get_absolute_url'):
return '' return ''
elif isinstance(obj, User): elif isinstance(obj, User):
return reverse('api:user_detail', args=(obj.pk,)) return self.reverse('api:user_detail', kwargs={'pk': obj.pk})
else: else:
return obj.get_absolute_url() return obj.get_absolute_url(request=self.context.get('request'))
def _get_related(self, obj): def _get_related(self, obj):
return {} if obj is None else self.get_related(obj) return {} if obj is None else self.get_related(obj)
@ -278,9 +279,9 @@ class BaseSerializer(serializers.ModelSerializer):
def get_related(self, obj): def get_related(self, obj):
res = OrderedDict() res = OrderedDict()
if getattr(obj, 'created_by', None): if getattr(obj, 'created_by', None):
res['created_by'] = reverse('api:user_detail', args=(obj.created_by.pk,)) res['created_by'] = self.reverse('api:user_detail', kwargs={'pk': obj.created_by.pk})
if getattr(obj, 'modified_by', None): if getattr(obj, 'modified_by', None):
res['modified_by'] = reverse('api:user_detail', args=(obj.modified_by.pk,)) res['modified_by'] = self.reverse('api:user_detail', kwargs={'pk': obj.modified_by.pk})
return res return res
def _get_summary_fields(self, obj): def _get_summary_fields(self, obj):
@ -496,6 +497,10 @@ class BaseSerializer(serializers.ModelSerializer):
raise ValidationError(d) raise ValidationError(d)
return attrs return attrs
def reverse(self, *args, **kwargs):
kwargs['request'] = self.context.get('request')
return reverse(*args, **kwargs)
class EmptySerializer(serializers.Serializer): class EmptySerializer(serializers.Serializer):
pass pass
@ -525,11 +530,11 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(UnifiedJobTemplateSerializer, self).get_related(obj) res = super(UnifiedJobTemplateSerializer, self).get_related(obj)
if obj.current_job: if obj.current_job:
res['current_job'] = obj.current_job.get_absolute_url() res['current_job'] = obj.current_job.get_absolute_url(request=self.context.get('request'))
if obj.last_job: if obj.last_job:
res['last_job'] = obj.last_job.get_absolute_url() res['last_job'] = obj.last_job.get_absolute_url(request=self.context.get('request'))
if obj.next_schedule: if obj.next_schedule:
res['next_schedule'] = obj.next_schedule.get_absolute_url() res['next_schedule'] = obj.next_schedule.get_absolute_url(request=self.context.get('request'))
return res return res
def get_types(self): def get_types(self):
@ -589,19 +594,19 @@ class UnifiedJobSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(UnifiedJobSerializer, self).get_related(obj) res = super(UnifiedJobSerializer, self).get_related(obj)
if obj.unified_job_template: if obj.unified_job_template:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url() res['unified_job_template'] = obj.unified_job_template.get_absolute_url(request=self.context.get('request'))
if obj.schedule: if obj.schedule:
res['schedule'] = obj.schedule.get_absolute_url() res['schedule'] = obj.schedule.get_absolute_url(request=self.context.get('request'))
if isinstance(obj, ProjectUpdate): if isinstance(obj, ProjectUpdate):
res['stdout'] = reverse('api:project_update_stdout', args=(obj.pk,)) res['stdout'] = self.reverse('api:project_update_stdout', kwargs={'pk': obj.pk})
elif isinstance(obj, InventoryUpdate): elif isinstance(obj, InventoryUpdate):
res['stdout'] = reverse('api:inventory_update_stdout', args=(obj.pk,)) res['stdout'] = self.reverse('api:inventory_update_stdout', kwargs={'pk': obj.pk})
elif isinstance(obj, Job): elif isinstance(obj, Job):
res['stdout'] = reverse('api:job_stdout', args=(obj.pk,)) res['stdout'] = self.reverse('api:job_stdout', kwargs={'pk': obj.pk})
elif isinstance(obj, AdHocCommand): elif isinstance(obj, AdHocCommand):
res['stdout'] = reverse('api:ad_hoc_command_stdout', args=(obj.pk,)) res['stdout'] = self.reverse('api:ad_hoc_command_stdout', kwargs={'pk': obj.pk})
if obj.workflow_job_id: if obj.workflow_job_id:
res['source_workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job_id,)) res['source_workflow_job'] = self.reverse('api:workflow_job_detail', kwargs={'pk': obj.workflow_job_id})
return res return res
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
@ -811,14 +816,14 @@ class UserSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(UserSerializer, self).get_related(obj) res = super(UserSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
teams = reverse('api:user_teams_list', args=(obj.pk,)), teams = self.reverse('api:user_teams_list', kwargs={'pk': obj.pk}),
organizations = reverse('api:user_organizations_list', args=(obj.pk,)), organizations = self.reverse('api:user_organizations_list', kwargs={'pk': obj.pk}),
admin_of_organizations = reverse('api:user_admin_of_organizations_list', args=(obj.pk,)), admin_of_organizations = self.reverse('api:user_admin_of_organizations_list', kwargs={'pk': obj.pk}),
projects = reverse('api:user_projects_list', args=(obj.pk,)), projects = self.reverse('api:user_projects_list', kwargs={'pk': obj.pk}),
credentials = reverse('api:user_credentials_list', args=(obj.pk,)), credentials = self.reverse('api:user_credentials_list', kwargs={'pk': obj.pk}),
roles = reverse('api:user_roles_list', args=(obj.pk,)), roles = self.reverse('api:user_roles_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:user_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:user_activity_stream_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:user_access_list', args=(obj.pk,)), access_list = self.reverse('api:user_access_list', kwargs={'pk': obj.pk}),
)) ))
return res return res
@ -864,20 +869,20 @@ class OrganizationSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(OrganizationSerializer, self).get_related(obj) res = super(OrganizationSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
projects = reverse('api:organization_projects_list', args=(obj.pk,)), projects = self.reverse('api:organization_projects_list', kwargs={'pk': obj.pk}),
inventories = reverse('api:organization_inventories_list', args=(obj.pk,)), inventories = self.reverse('api:organization_inventories_list', kwargs={'pk': obj.pk}),
workflow_job_templates = reverse('api:organization_workflow_job_templates_list', args=(obj.pk,)), workflow_job_templates = self.reverse('api:organization_workflow_job_templates_list', kwargs={'pk': obj.pk}),
users = reverse('api:organization_users_list', args=(obj.pk,)), users = self.reverse('api:organization_users_list', kwargs={'pk': obj.pk}),
admins = reverse('api:organization_admins_list', args=(obj.pk,)), admins = self.reverse('api:organization_admins_list', kwargs={'pk': obj.pk}),
teams = reverse('api:organization_teams_list', args=(obj.pk,)), teams = self.reverse('api:organization_teams_list', kwargs={'pk': obj.pk}),
credentials = reverse('api:organization_credential_list', args=(obj.pk,)), credentials = self.reverse('api:organization_credential_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:organization_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:organization_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates = reverse('api:organization_notification_templates_list', args=(obj.pk,)), notification_templates = self.reverse('api:organization_notification_templates_list', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:organization_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:organization_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:organization_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:organization_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:organization_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:organization_notification_templates_error_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:organization_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:organization_object_roles_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:organization_access_list', args=(obj.pk,)), access_list = self.reverse('api:organization_access_list', kwargs={'pk': obj.pk}),
)) ))
return res return res
@ -903,8 +908,8 @@ class ProjectOptionsSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(ProjectOptionsSerializer, self).get_related(obj) res = super(ProjectOptionsSerializer, self).get_related(obj)
if obj.credential: if obj.credential:
res['credential'] = reverse('api:credential_detail', res['credential'] = self.reverse('api:credential_detail',
args=(obj.credential.pk,)) kwargs={'pk': obj.credential.pk})
return res return res
def validate(self, attrs): def validate(self, attrs):
@ -953,28 +958,28 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(ProjectSerializer, self).get_related(obj) res = super(ProjectSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
teams = reverse('api:project_teams_list', args=(obj.pk,)), teams = self.reverse('api:project_teams_list', kwargs={'pk': obj.pk}),
playbooks = reverse('api:project_playbooks', args=(obj.pk,)), playbooks = self.reverse('api:project_playbooks', kwargs={'pk': obj.pk}),
update = reverse('api:project_update_view', args=(obj.pk,)), update = self.reverse('api:project_update_view', kwargs={'pk': obj.pk}),
project_updates = reverse('api:project_updates_list', args=(obj.pk,)), project_updates = self.reverse('api:project_updates_list', kwargs={'pk': obj.pk}),
schedules = reverse('api:project_schedules_list', args=(obj.pk,)), schedules = self.reverse('api:project_schedules_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:project_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:project_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:project_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:project_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:project_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:project_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:project_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:project_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:project_access_list', args=(obj.pk,)), access_list = self.reverse('api:project_access_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:project_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:project_object_roles_list', kwargs={'pk': obj.pk}),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', res['organization'] = self.reverse('api:organization_detail',
args=(obj.organization.pk,)) kwargs={'pk': obj.organization.pk})
# Backwards compatibility. # Backwards compatibility.
if obj.current_update: if obj.current_update:
res['current_update'] = reverse('api:project_update_detail', res['current_update'] = self.reverse('api:project_update_detail',
args=(obj.current_update.pk,)) kwargs={'pk': obj.current_update.pk})
if obj.last_update: if obj.last_update:
res['last_update'] = reverse('api:project_update_detail', res['last_update'] = self.reverse('api:project_update_detail',
args=(obj.last_update.pk,)) kwargs={'pk': obj.last_update.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -1039,9 +1044,9 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(ProjectUpdateSerializer, self).get_related(obj) res = super(ProjectUpdateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
project = reverse('api:project_detail', args=(obj.project.pk,)), project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}),
cancel = reverse('api:project_update_cancel', args=(obj.pk,)), cancel = self.reverse('api:project_update_cancel', kwargs={'pk': obj.pk}),
notifications = reverse('api:project_update_notifications_list', args=(obj.pk,)), notifications = self.reverse('api:project_update_notifications_list', kwargs={'pk': obj.pk}),
)) ))
return res return res
@ -1078,23 +1083,22 @@ class InventorySerializer(BaseSerializerWithVariables):
def get_related(self, obj): def get_related(self, obj):
res = super(InventorySerializer, self).get_related(obj) res = super(InventorySerializer, self).get_related(obj)
res.update(dict( res.update(dict(
hosts = reverse('api:inventory_hosts_list', args=(obj.pk,)), hosts = self.reverse('api:inventory_hosts_list', kwargs={'pk': obj.pk}),
groups = reverse('api:inventory_groups_list', args=(obj.pk,)), groups = self.reverse('api:inventory_groups_list', kwargs={'pk': obj.pk}),
root_groups = reverse('api:inventory_root_groups_list', args=(obj.pk,)), root_groups = self.reverse('api:inventory_root_groups_list', kwargs={'pk': obj.pk}),
variable_data = reverse('api:inventory_variable_data', args=(obj.pk,)), variable_data = self.reverse('api:inventory_variable_data', kwargs={'pk': obj.pk}),
script = reverse('api:inventory_script_view', args=(obj.pk,)), script = self.reverse('api:inventory_script_view', kwargs={'pk': obj.pk}),
tree = reverse('api:inventory_tree_view', args=(obj.pk,)), tree = self.reverse('api:inventory_tree_view', kwargs={'pk': obj.pk}),
inventory_sources = reverse('api:inventory_inventory_sources_list', args=(obj.pk,)), inventory_sources = self.reverse('api:inventory_inventory_sources_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:inventory_activity_stream_list', kwargs={'pk': obj.pk}),
job_templates = reverse('api:inventory_job_template_list', args=(obj.pk,)), job_templates = self.reverse('api:inventory_job_template_list', kwargs={'pk': obj.pk}),
scan_job_templates = reverse('api:inventory_scan_job_template_list', args=(obj.pk,)), scan_job_templates = self.reverse('api:inventory_scan_job_template_list', kwargs={'pk': obj.pk}),
ad_hoc_commands = reverse('api:inventory_ad_hoc_commands_list', args=(obj.pk,)), ad_hoc_commands = self.reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:inventory_access_list', args=(obj.pk,)), access_list = self.reverse('api:inventory_access_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:inventory_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:inventory_object_roles_list', kwargs={'pk': obj.pk}),
#single_fact = reverse('api:inventory_single_fact_view', args=(obj.pk,)),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -1143,24 +1147,23 @@ class HostSerializer(BaseSerializerWithVariables):
def get_related(self, obj): def get_related(self, obj):
res = super(HostSerializer, self).get_related(obj) res = super(HostSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
variable_data = reverse('api:host_variable_data', args=(obj.pk,)), variable_data = self.reverse('api:host_variable_data', kwargs={'pk': obj.pk}),
groups = reverse('api:host_groups_list', args=(obj.pk,)), groups = self.reverse('api:host_groups_list', kwargs={'pk': obj.pk}),
all_groups = reverse('api:host_all_groups_list', args=(obj.pk,)), all_groups = self.reverse('api:host_all_groups_list', kwargs={'pk': obj.pk}),
job_events = reverse('api:host_job_events_list', args=(obj.pk,)), job_events = self.reverse('api:host_job_events_list', kwargs={'pk': obj.pk}),
job_host_summaries = reverse('api:host_job_host_summaries_list', args=(obj.pk,)), job_host_summaries = self.reverse('api:host_job_host_summaries_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:host_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:host_activity_stream_list', kwargs={'pk': obj.pk}),
inventory_sources = reverse('api:host_inventory_sources_list', args=(obj.pk,)), inventory_sources = self.reverse('api:host_inventory_sources_list', kwargs={'pk': obj.pk}),
ad_hoc_commands = reverse('api:host_ad_hoc_commands_list', args=(obj.pk,)), ad_hoc_commands = self.reverse('api:host_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
ad_hoc_command_events = reverse('api:host_ad_hoc_command_events_list', args=(obj.pk,)), ad_hoc_command_events = self.reverse('api:host_ad_hoc_command_events_list', kwargs={'pk': obj.pk}),
fact_versions = reverse('api:host_fact_versions_list', args=(obj.pk,)), fact_versions = self.reverse('api:host_fact_versions_list', kwargs={'pk': obj.pk}),
#single_fact = reverse('api:host_single_fact_view', args=(obj.pk,)),
)) ))
if obj.inventory: if obj.inventory:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,)) res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
if obj.last_job: if obj.last_job:
res['last_job'] = reverse('api:job_detail', args=(obj.last_job.pk,)) res['last_job'] = self.reverse('api:job_detail', kwargs={'pk': obj.last_job.pk})
if obj.last_job_host_summary: if obj.last_job_host_summary:
res['last_job_host_summary'] = reverse('api:job_host_summary_detail', args=(obj.last_job_host_summary.pk,)) res['last_job_host_summary'] = self.reverse('api:job_host_summary_detail', kwargs={'pk': obj.last_job_host_summary.pk})
return res return res
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
@ -1253,22 +1256,21 @@ class GroupSerializer(BaseSerializerWithVariables):
def get_related(self, obj): def get_related(self, obj):
res = super(GroupSerializer, self).get_related(obj) res = super(GroupSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
variable_data = reverse('api:group_variable_data', args=(obj.pk,)), variable_data = self.reverse('api:group_variable_data', kwargs={'pk': obj.pk}),
hosts = reverse('api:group_hosts_list', args=(obj.pk,)), hosts = self.reverse('api:group_hosts_list', kwargs={'pk': obj.pk}),
potential_children = reverse('api:group_potential_children_list', args=(obj.pk,)), potential_children = self.reverse('api:group_potential_children_list', kwargs={'pk': obj.pk}),
children = reverse('api:group_children_list', args=(obj.pk,)), children = self.reverse('api:group_children_list', kwargs={'pk': obj.pk}),
all_hosts = reverse('api:group_all_hosts_list', args=(obj.pk,)), all_hosts = self.reverse('api:group_all_hosts_list', kwargs={'pk': obj.pk}),
job_events = reverse('api:group_job_events_list', args=(obj.pk,)), job_events = self.reverse('api:group_job_events_list', kwargs={'pk': obj.pk}),
job_host_summaries = reverse('api:group_job_host_summaries_list', args=(obj.pk,)), job_host_summaries = self.reverse('api:group_job_host_summaries_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:group_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:group_activity_stream_list', kwargs={'pk': obj.pk}),
inventory_sources = reverse('api:group_inventory_sources_list', args=(obj.pk,)), inventory_sources = self.reverse('api:group_inventory_sources_list', kwargs={'pk': obj.pk}),
ad_hoc_commands = reverse('api:group_ad_hoc_commands_list', args=(obj.pk,)), ad_hoc_commands = self.reverse('api:group_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
#single_fact = reverse('api:group_single_fact_view', args=(obj.pk,)),
)) ))
if obj.inventory: if obj.inventory:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,)) res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
if obj.inventory_source: if obj.inventory_source:
res['inventory_source'] = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)) res['inventory_source'] = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk})
return res return res
def validate_name(self, value): def validate_name(self, value):
@ -1363,11 +1365,11 @@ class CustomInventoryScriptSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(CustomInventoryScriptSerializer, self).get_related(obj) res = super(CustomInventoryScriptSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
object_roles = reverse('api:inventory_script_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:inventory_script_object_roles_list', kwargs={'pk': obj.pk}),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
@ -1381,10 +1383,10 @@ class InventorySourceOptionsSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(InventorySourceOptionsSerializer, self).get_related(obj) res = super(InventorySourceOptionsSerializer, self).get_related(obj)
if obj.credential: if obj.credential:
res['credential'] = reverse('api:credential_detail', res['credential'] = self.reverse('api:credential_detail',
args=(obj.credential.pk,)) kwargs={'pk': obj.credential.pk})
if obj.source_script: if obj.source_script:
res['source_script'] = reverse('api:inventory_script_detail', args=(obj.source_script.pk,)) res['source_script'] = self.reverse('api:inventory_script_detail', kwargs={'pk': obj.source_script.pk})
return res return res
def validate_source_vars(self, value): def validate_source_vars(self, value):
@ -1437,27 +1439,27 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
def get_related(self, obj): def get_related(self, obj):
res = super(InventorySourceSerializer, self).get_related(obj) res = super(InventorySourceSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
update = reverse('api:inventory_source_update_view', args=(obj.pk,)), update = self.reverse('api:inventory_source_update_view', kwargs={'pk': obj.pk}),
inventory_updates = reverse('api:inventory_source_updates_list', args=(obj.pk,)), inventory_updates = self.reverse('api:inventory_source_updates_list', kwargs={'pk': obj.pk}),
schedules = reverse('api:inventory_source_schedules_list', args=(obj.pk,)), schedules = self.reverse('api:inventory_source_schedules_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:inventory_activity_stream_list', kwargs={'pk': obj.pk}),
hosts = reverse('api:inventory_source_hosts_list', args=(obj.pk,)), hosts = self.reverse('api:inventory_source_hosts_list', kwargs={'pk': obj.pk}),
groups = reverse('api:inventory_source_groups_list', args=(obj.pk,)), groups = self.reverse('api:inventory_source_groups_list', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:inventory_source_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:inventory_source_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:inventory_source_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:inventory_source_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:inventory_source_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:inventory_source_notification_templates_error_list', kwargs={'pk': obj.pk}),
)) ))
if obj.inventory: if obj.inventory:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,)) res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
if obj.group: if obj.group:
res['group'] = reverse('api:group_detail', args=(obj.group.pk,)) res['group'] = self.reverse('api:group_detail', kwargs={'pk': obj.group.pk})
# Backwards compatibility. # Backwards compatibility.
if obj.current_update: if obj.current_update:
res['current_update'] = reverse('api:inventory_update_detail', res['current_update'] = self.reverse('api:inventory_update_detail',
args=(obj.current_update.pk,)) kwargs={'pk': obj.current_update.pk})
if obj.last_update: if obj.last_update:
res['last_update'] = reverse('api:inventory_update_detail', res['last_update'] = self.reverse('api:inventory_update_detail',
args=(obj.last_update.pk,)) kwargs={'pk': obj.last_update.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -1488,9 +1490,9 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri
def get_related(self, obj): def get_related(self, obj):
res = super(InventoryUpdateSerializer, self).get_related(obj) res = super(InventoryUpdateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
inventory_source = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)), inventory_source = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk}),
cancel = reverse('api:inventory_update_cancel', args=(obj.pk,)), cancel = self.reverse('api:inventory_update_cancel', kwargs={'pk': obj.pk}),
notifications = reverse('api:inventory_update_notifications_list', args=(obj.pk,)), notifications = self.reverse('api:inventory_update_notifications_list', kwargs={'pk': obj.pk}),
)) ))
return res return res
@ -1518,16 +1520,16 @@ class TeamSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(TeamSerializer, self).get_related(obj) res = super(TeamSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
projects = reverse('api:team_projects_list', args=(obj.pk,)), projects = self.reverse('api:team_projects_list', kwargs={'pk': obj.pk}),
users = reverse('api:team_users_list', args=(obj.pk,)), users = self.reverse('api:team_users_list', kwargs={'pk': obj.pk}),
credentials = reverse('api:team_credentials_list', args=(obj.pk,)), credentials = self.reverse('api:team_credentials_list', kwargs={'pk': obj.pk}),
roles = reverse('api:team_roles_list', args=(obj.pk,)), roles = self.reverse('api:team_roles_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:team_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:team_object_roles_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:team_activity_stream_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:team_access_list', args=(obj.pk,)), access_list = self.reverse('api:team_access_list', kwargs={'pk': obj.pk}),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -1564,11 +1566,11 @@ class RoleSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
ret = super(RoleSerializer, self).get_related(obj) ret = super(RoleSerializer, self).get_related(obj)
ret['users'] = reverse('api:role_users_list', args=(obj.pk,)) ret['users'] = self.reverse('api:role_users_list', kwargs={'pk': obj.pk})
ret['teams'] = reverse('api:role_teams_list', args=(obj.pk,)) ret['teams'] = self.reverse('api:role_teams_list', kwargs={'pk': obj.pk})
try: try:
if obj.content_object: if obj.content_object:
ret.update(reverse_gfk(obj.content_object)) ret.update(reverse_gfk(obj.content_object, self.context.get('request')))
except AttributeError: except AttributeError:
# AttributeError's happen if our content_object is pointing at # AttributeError's happen if our content_object is pointing at
# a model that no longer exists. This is dirty data and ideally # a model that no longer exists. This is dirty data and ideally
@ -1610,7 +1612,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
try: try:
role_dict['resource_name'] = role.content_object.name role_dict['resource_name'] = role.content_object.name
role_dict['resource_type'] = role.content_type.name role_dict['resource_type'] = role.content_type.name
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object, self.context.get('request'))
except AttributeError: except AttributeError:
pass pass
if role.content_type is not None: if role.content_type is not None:
@ -1638,7 +1640,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
if role.content_type is not None: if role.content_type is not None:
role_dict['resource_name'] = role.content_object.name role_dict['resource_name'] = role.content_object.name
role_dict['resource_type'] = role.content_type.name role_dict['resource_type'] = role.content_type.name
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object, self.context.get('request'))
role_dict['user_capabilities'] = {'unattach': requesting_user.can_access( role_dict['user_capabilities'] = {'unattach': requesting_user.can_access(
Role, 'unattach', role, team_role, 'parents', data={}, skip_sub_obj_read_check=False)} Role, 'unattach', role, team_role, 'parents', data={}, skip_sub_obj_read_check=False)}
else: else:
@ -1717,22 +1719,22 @@ class CredentialSerializer(BaseSerializer):
res = super(CredentialSerializer, self).get_related(obj) res = super(CredentialSerializer, self).get_related(obj)
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
res.update(dict( res.update(dict(
activity_stream = reverse('api:credential_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:credential_activity_stream_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:credential_access_list', args=(obj.pk,)), access_list = self.reverse('api:credential_access_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:credential_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:credential_object_roles_list', kwargs={'pk': obj.pk}),
owner_users = reverse('api:credential_owner_users_list', args=(obj.pk,)), owner_users = self.reverse('api:credential_owner_users_list', kwargs={'pk': obj.pk}),
owner_teams = reverse('api:credential_owner_teams_list', args=(obj.pk,)), owner_teams = self.reverse('api:credential_owner_teams_list', kwargs={'pk': obj.pk}),
)) ))
parents = [role for role in obj.admin_role.parents.all() if role.object_id is not None] parents = [role for role in obj.admin_role.parents.all() if role.object_id is not None]
if parents: if parents:
res.update({parents[0].content_type.name:parents[0].content_object.get_absolute_url()}) res.update({parents[0].content_type.name:parents[0].content_object.get_absolute_url(self.context.get('request'))})
elif len(obj.admin_role.members.all()) > 0: elif len(obj.admin_role.members.all()) > 0:
user = obj.admin_role.members.all()[0] user = obj.admin_role.members.all()[0]
res.update({'user': reverse('api:user_detail', args=(user.pk,))}) res.update({'user': self.reverse('api:user_detail', kwargs={'pk': user.pk})})
return res return res
@ -1746,7 +1748,7 @@ class CredentialSerializer(BaseSerializer):
'type': 'user', 'type': 'user',
'name': user.username, 'name': user.username,
'description': ' '.join([user.first_name, user.last_name]), 'description': ' '.join([user.first_name, user.last_name]),
'url': reverse('api:user_detail', args=(user.pk,)), 'url': self.reverse('api:user_detail', kwargs={'pk': user.pk}),
}) })
for parent in [role for role in obj.admin_role.parents.all() if role.object_id is not None]: for parent in [role for role in obj.admin_role.parents.all() if role.object_id is not None]:
@ -1755,7 +1757,7 @@ class CredentialSerializer(BaseSerializer):
'type': camelcase_to_underscore(parent.content_object.__class__.__name__), 'type': camelcase_to_underscore(parent.content_object.__class__.__name__),
'name': parent.content_object.name, 'name': parent.content_object.name,
'description': parent.content_object.description, 'description': parent.content_object.description,
'url': parent.content_object.get_absolute_url(), 'url': parent.content_object.get_absolute_url(self.context.get('request')),
}) })
return summary_dict return summary_dict
@ -1862,19 +1864,19 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(JobOptionsSerializer, self).get_related(obj) res = super(JobOptionsSerializer, self).get_related(obj)
res['labels'] = reverse('api:job_template_label_list', args=(obj.pk,)) res['labels'] = self.reverse('api:job_template_label_list', kwargs={'pk': obj.pk})
if obj.inventory: if obj.inventory:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,)) res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
if obj.project: if obj.project:
res['project'] = reverse('api:project_detail', args=(obj.project.pk,)) res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk})
if obj.credential: if obj.credential:
res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,)) res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential.pk})
if obj.cloud_credential: if obj.cloud_credential:
res['cloud_credential'] = reverse('api:credential_detail', res['cloud_credential'] = self.reverse('api:credential_detail',
args=(obj.cloud_credential.pk,)) kwargs={'pk': obj.cloud_credential.pk})
if obj.network_credential: if obj.network_credential:
res['network_credential'] = reverse('api:credential_detail', res['network_credential'] = self.reverse('api:credential_detail',
args=(obj.network_credential.pk,)) kwargs={'pk': obj.network_credential.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -1947,20 +1949,20 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO
def get_related(self, obj): def get_related(self, obj):
res = super(JobTemplateSerializer, self).get_related(obj) res = super(JobTemplateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)), jobs = self.reverse('api:job_template_jobs_list', kwargs={'pk': obj.pk}),
schedules = reverse('api:job_template_schedules_list', args=(obj.pk,)), schedules = self.reverse('api:job_template_schedules_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:job_template_activity_stream_list', kwargs={'pk': obj.pk}),
launch = reverse('api:job_template_launch', args=(obj.pk,)), launch = self.reverse('api:job_template_launch', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:job_template_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:job_template_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:job_template_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:job_template_access_list', args=(obj.pk,)), access_list = self.reverse('api:job_template_access_list', kwargs={'pk': obj.pk}),
survey_spec = reverse('api:job_template_survey_spec', args=(obj.pk,)), survey_spec = self.reverse('api:job_template_survey_spec', kwargs={'pk': obj.pk}),
labels = reverse('api:job_template_label_list', args=(obj.pk,)), labels = self.reverse('api:job_template_label_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:job_template_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:job_template_object_roles_list', kwargs={'pk': obj.pk}),
)) ))
if obj.host_config_key: if obj.host_config_key:
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,)) res['callback'] = self.reverse('api:job_template_callback', kwargs={'pk': obj.pk})
return res return res
def validate(self, attrs): def validate(self, attrs):
@ -2015,22 +2017,22 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(JobSerializer, self).get_related(obj) res = super(JobSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
job_events = reverse('api:job_job_events_list', args=(obj.pk,)), job_events = self.reverse('api:job_job_events_list', kwargs={'pk': obj.pk}),
job_host_summaries = reverse('api:job_job_host_summaries_list', args=(obj.pk,)), job_host_summaries = self.reverse('api:job_job_host_summaries_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:job_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:job_activity_stream_list', kwargs={'pk': obj.pk}),
notifications = reverse('api:job_notifications_list', args=(obj.pk,)), notifications = self.reverse('api:job_notifications_list', kwargs={'pk': obj.pk}),
labels = reverse('api:job_label_list', args=(obj.pk,)), labels = self.reverse('api:job_label_list', kwargs={'pk': obj.pk}),
)) ))
if obj.job_template: if obj.job_template:
res['job_template'] = reverse('api:job_template_detail', res['job_template'] = self.reverse('api:job_template_detail',
args=(obj.job_template.pk,)) kwargs={'pk': obj.job_template.pk})
if obj.can_start or True: if obj.can_start or True:
res['start'] = reverse('api:job_start', args=(obj.pk,)) res['start'] = self.reverse('api:job_start', kwargs={'pk': obj.pk})
if obj.can_cancel or True: if obj.can_cancel or True:
res['cancel'] = reverse('api:job_cancel', args=(obj.pk,)) res['cancel'] = self.reverse('api:job_cancel', kwargs={'pk': obj.pk})
if obj.project_update: if obj.project_update:
res['project_update'] = reverse('api:project_update_detail', args=(obj.project_update.pk,)) res['project_update'] = self.reverse('api:project_update_detail', kwargs={'pk': obj.project_update.pk})
res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,)) res['relaunch'] = self.reverse('api:job_relaunch', kwargs={'pk': obj.pk})
return res return res
def get_artifacts(self, obj): def get_artifacts(self, obj):
@ -2175,16 +2177,16 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(AdHocCommandSerializer, self).get_related(obj) res = super(AdHocCommandSerializer, self).get_related(obj)
if obj.inventory: if obj.inventory:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,)) res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
if obj.credential: if obj.credential:
res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,)) res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential.pk})
res.update(dict( res.update(dict(
events = reverse('api:ad_hoc_command_ad_hoc_command_events_list', args=(obj.pk,)), events = self.reverse('api:ad_hoc_command_ad_hoc_command_events_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:ad_hoc_command_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:ad_hoc_command_activity_stream_list', kwargs={'pk': obj.pk}),
notifications = reverse('api:ad_hoc_command_notifications_list', args=(obj.pk,)), notifications = self.reverse('api:ad_hoc_command_notifications_list', kwargs={'pk': obj.pk}),
)) ))
res['cancel'] = reverse('api:ad_hoc_command_cancel', args=(obj.pk,)) res['cancel'] = self.reverse('api:ad_hoc_command_cancel', kwargs={'pk': obj.pk})
res['relaunch'] = reverse('api:ad_hoc_command_relaunch', args=(obj.pk,)) res['relaunch'] = self.reverse('api:ad_hoc_command_relaunch', kwargs={'pk': obj.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -2229,12 +2231,12 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(SystemJobTemplateSerializer, self).get_related(obj) res = super(SystemJobTemplateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
jobs = reverse('api:system_job_template_jobs_list', args=(obj.pk,)), jobs = self.reverse('api:system_job_template_jobs_list', kwargs={'pk': obj.pk}),
schedules = reverse('api:system_job_template_schedules_list', args=(obj.pk,)), schedules = self.reverse('api:system_job_template_schedules_list', kwargs={'pk': obj.pk}),
launch = reverse('api:system_job_template_launch', args=(obj.pk,)), launch = self.reverse('api:system_job_template_launch', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:system_job_template_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:system_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:system_job_template_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:system_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:system_job_template_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:system_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
)) ))
return res return res
@ -2249,11 +2251,11 @@ class SystemJobSerializer(UnifiedJobSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(SystemJobSerializer, self).get_related(obj) res = super(SystemJobSerializer, self).get_related(obj)
if obj.system_job_template: if obj.system_job_template:
res['system_job_template'] = reverse('api:system_job_template_detail', res['system_job_template'] = self.reverse('api:system_job_template_detail',
args=(obj.system_job_template.pk,)) kwargs={'pk': obj.system_job_template.pk})
res['notifications'] = reverse('api:system_job_notifications_list', args=(obj.pk,)) res['notifications'] = self.reverse('api:system_job_notifications_list', kwargs={'pk': obj.pk})
if obj.can_cancel or True: if obj.can_cancel or True:
res['cancel'] = reverse('api:system_job_cancel', args=(obj.pk,)) res['cancel'] = self.reverse('api:system_job_cancel', kwargs={'pk': obj.pk})
return res return res
@ -2275,22 +2277,22 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
def get_related(self, obj): def get_related(self, obj):
res = super(WorkflowJobTemplateSerializer, self).get_related(obj) res = super(WorkflowJobTemplateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
workflow_jobs = reverse('api:workflow_job_template_jobs_list', args=(obj.pk,)), workflow_jobs = self.reverse('api:workflow_job_template_jobs_list', kwargs={'pk': obj.pk}),
schedules = reverse('api:workflow_job_template_schedules_list', args=(obj.pk,)), schedules = self.reverse('api:workflow_job_template_schedules_list', kwargs={'pk': obj.pk}),
launch = reverse('api:workflow_job_template_launch', args=(obj.pk,)), launch = self.reverse('api:workflow_job_template_launch', kwargs={'pk': obj.pk}),
copy = reverse('api:workflow_job_template_copy', args=(obj.pk,)), copy = self.reverse('api:workflow_job_template_copy', kwargs={'pk': obj.pk}),
workflow_nodes = reverse('api:workflow_job_template_workflow_nodes_list', args=(obj.pk,)), workflow_nodes = self.reverse('api:workflow_job_template_workflow_nodes_list', kwargs={'pk': obj.pk}),
labels = reverse('api:workflow_job_template_label_list', args=(obj.pk,)), labels = self.reverse('api:workflow_job_template_label_list', kwargs={'pk': obj.pk}),
activity_stream = reverse('api:workflow_job_template_activity_stream_list', args=(obj.pk,)), activity_stream = self.reverse('api:workflow_job_template_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates_any = reverse('api:workflow_job_template_notification_templates_any_list', args=(obj.pk,)), notification_templates_any = self.reverse('api:workflow_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_success = reverse('api:workflow_job_template_notification_templates_success_list', args=(obj.pk,)), notification_templates_success = self.reverse('api:workflow_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = reverse('api:workflow_job_template_notification_templates_error_list', args=(obj.pk,)), notification_templates_error = self.reverse('api:workflow_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = reverse('api:workflow_job_template_access_list', args=(obj.pk,)), access_list = self.reverse('api:workflow_job_template_access_list', kwargs={'pk': obj.pk}),
object_roles = reverse('api:workflow_job_template_object_roles_list', args=(obj.pk,)), object_roles = self.reverse('api:workflow_job_template_object_roles_list', kwargs={'pk': obj.pk}),
survey_spec = reverse('api:workflow_job_template_survey_spec', args=(obj.pk,)), survey_spec = self.reverse('api:workflow_job_template_survey_spec', kwargs={'pk': obj.pk}),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
def validate_extra_vars(self, value): def validate_extra_vars(self, value):
@ -2312,15 +2314,15 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(WorkflowJobSerializer, self).get_related(obj) res = super(WorkflowJobSerializer, self).get_related(obj)
if obj.workflow_job_template: if obj.workflow_job_template:
res['workflow_job_template'] = reverse('api:workflow_job_template_detail', res['workflow_job_template'] = self.reverse('api:workflow_job_template_detail',
args=(obj.workflow_job_template.pk,)) kwargs={'pk': obj.workflow_job_template.pk})
res['notifications'] = reverse('api:workflow_job_notifications_list', args=(obj.pk,)) res['notifications'] = self.reverse('api:workflow_job_notifications_list', kwargs={'pk': obj.pk})
res['workflow_nodes'] = reverse('api:workflow_job_workflow_nodes_list', args=(obj.pk,)) res['workflow_nodes'] = self.reverse('api:workflow_job_workflow_nodes_list', kwargs={'pk': obj.pk})
res['labels'] = reverse('api:workflow_job_label_list', args=(obj.pk,)) res['labels'] = self.reverse('api:workflow_job_label_list', kwargs={'pk': obj.pk})
res['activity_stream'] = reverse('api:workflow_job_activity_stream_list', args=(obj.pk,)) res['activity_stream'] = self.reverse('api:workflow_job_activity_stream_list', kwargs={'pk': obj.pk})
res['relaunch'] = reverse('api:workflow_job_relaunch', args=(obj.pk,)) res['relaunch'] = self.reverse('api:workflow_job_relaunch', kwargs={'pk': obj.pk})
if obj.can_cancel or True: if obj.can_cancel or True:
res['cancel'] = reverse('api:workflow_job_cancel', args=(obj.pk,)) res['cancel'] = self.reverse('api:workflow_job_cancel', kwargs={'pk': obj.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -2362,7 +2364,7 @@ class WorkflowNodeBaseSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(WorkflowNodeBaseSerializer, self).get_related(obj) res = super(WorkflowNodeBaseSerializer, self).get_related(obj)
if obj.unified_job_template: if obj.unified_job_template:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url() res['unified_job_template'] = obj.unified_job_template.get_absolute_url(self.context.get('request'))
return res return res
def validate(self, attrs): def validate(self, attrs):
@ -2380,11 +2382,11 @@ class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj)
res['success_nodes'] = reverse('api:workflow_job_template_node_success_nodes_list', args=(obj.pk,)) res['success_nodes'] = self.reverse('api:workflow_job_template_node_success_nodes_list', kwargs={'pk': obj.pk})
res['failure_nodes'] = reverse('api:workflow_job_template_node_failure_nodes_list', args=(obj.pk,)) res['failure_nodes'] = self.reverse('api:workflow_job_template_node_failure_nodes_list', kwargs={'pk': obj.pk})
res['always_nodes'] = reverse('api:workflow_job_template_node_always_nodes_list', args=(obj.pk,)) res['always_nodes'] = self.reverse('api:workflow_job_template_node_always_nodes_list', kwargs={'pk': obj.pk})
if obj.workflow_job_template: if obj.workflow_job_template:
res['workflow_job_template'] = reverse('api:workflow_job_template_detail', args=(obj.workflow_job_template.pk,)) res['workflow_job_template'] = self.reverse('api:workflow_job_template_detail', kwargs={'pk': obj.workflow_job_template.pk})
return res return res
def to_internal_value(self, data): def to_internal_value(self, data):
@ -2440,13 +2442,13 @@ class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(WorkflowJobNodeSerializer, self).get_related(obj) res = super(WorkflowJobNodeSerializer, self).get_related(obj)
res['success_nodes'] = reverse('api:workflow_job_node_success_nodes_list', args=(obj.pk,)) res['success_nodes'] = self.reverse('api:workflow_job_node_success_nodes_list', kwargs={'pk': obj.pk})
res['failure_nodes'] = reverse('api:workflow_job_node_failure_nodes_list', args=(obj.pk,)) res['failure_nodes'] = self.reverse('api:workflow_job_node_failure_nodes_list', kwargs={'pk': obj.pk})
res['always_nodes'] = reverse('api:workflow_job_node_always_nodes_list', args=(obj.pk,)) res['always_nodes'] = self.reverse('api:workflow_job_node_always_nodes_list', kwargs={'pk': obj.pk})
if obj.job: if obj.job:
res['job'] = obj.job.get_absolute_url() res['job'] = obj.job.get_absolute_url(self.context.get('request'))
if obj.workflow_job: if obj.workflow_job:
res['workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job.pk,)) res['workflow_job'] = self.reverse('api:workflow_job_detail', kwargs={'pk': obj.workflow_job.pk})
return res return res
@ -2500,10 +2502,10 @@ class JobHostSummarySerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(JobHostSummarySerializer, self).get_related(obj) res = super(JobHostSummarySerializer, self).get_related(obj)
res.update(dict( res.update(dict(
job=reverse('api:job_detail', args=(obj.job.pk,)))) job=self.reverse('api:job_detail', kwargs={'pk': obj.job.pk})))
if obj.host is not None: if obj.host is not None:
res.update(dict( res.update(dict(
host=reverse('api:host_detail', args=(obj.host.pk,)) host=self.reverse('api:host_detail', kwargs={'pk': obj.host.pk})
)) ))
return res return res
@ -2533,16 +2535,16 @@ class JobEventSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj) res = super(JobEventSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
job = reverse('api:job_detail', args=(obj.job_id,)), job = self.reverse('api:job_detail', kwargs={'pk': obj.job_id}),
)) ))
if obj.parent_id: if obj.parent_id:
res['parent'] = reverse('api:job_event_detail', args=(obj.parent_id,)) res['parent'] = self.reverse('api:job_event_detail', kwargs={'pk': obj.parent_id})
if obj.children.exists(): if obj.children.exists():
res['children'] = reverse('api:job_event_children_list', args=(obj.pk,)) res['children'] = self.reverse('api:job_event_children_list', kwargs={'pk': obj.pk})
if obj.host_id: if obj.host_id:
res['host'] = reverse('api:host_detail', args=(obj.host_id,)) res['host'] = self.reverse('api:host_detail', kwargs={'pk': obj.host_id})
if obj.hosts.exists(): if obj.hosts.exists():
res['hosts'] = reverse('api:job_event_hosts_list', args=(obj.pk,)) res['hosts'] = self.reverse('api:job_event_hosts_list', kwargs={'pk': obj.pk})
return res return res
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
@ -2582,10 +2584,10 @@ class AdHocCommandEventSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(AdHocCommandEventSerializer, self).get_related(obj) res = super(AdHocCommandEventSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
ad_hoc_command = reverse('api:ad_hoc_command_detail', args=(obj.ad_hoc_command_id,)), ad_hoc_command = self.reverse('api:ad_hoc_command_detail', kwargs={'pk': obj.ad_hoc_command_id}),
)) ))
if obj.host: if obj.host:
res['host'] = reverse('api:host_detail', args=(obj.host.pk,)) res['host'] = self.reverse('api:host_detail', kwargs={'pk': obj.host.pk})
return res return res
def to_representation(self, obj): def to_representation(self, obj):
@ -2809,11 +2811,11 @@ class NotificationTemplateSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(NotificationTemplateSerializer, self).get_related(obj) res = super(NotificationTemplateSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
test = reverse('api:notification_template_test', args=(obj.pk,)), test = self.reverse('api:notification_template_test', kwargs={'pk': obj.pk}),
notifications = reverse('api:notification_template_notification_list', args=(obj.pk,)), notifications = self.reverse('api:notification_template_notification_list', kwargs={'pk': obj.pk}),
)) ))
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
def _recent_notifications(self, obj): def _recent_notifications(self, obj):
@ -2883,7 +2885,7 @@ class NotificationSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(NotificationSerializer, self).get_related(obj) res = super(NotificationSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
notification_template = reverse('api:notification_template_detail', args=(obj.notification_template.pk,)), notification_template = self.reverse('api:notification_template_detail', kwargs={'pk': obj.notification_template.pk}),
)) ))
return res return res
@ -2897,7 +2899,7 @@ class LabelSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(LabelSerializer, self).get_related(obj) res = super(LabelSerializer, self).get_related(obj)
if obj.organization: if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
return res return res
@ -2911,10 +2913,10 @@ class ScheduleSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(ScheduleSerializer, self).get_related(obj) res = super(ScheduleSerializer, self).get_related(obj)
res.update(dict( res.update(dict(
unified_jobs = reverse('api:schedule_unified_jobs_list', args=(obj.pk,)), unified_jobs = self.reverse('api:schedule_unified_jobs_list', kwargs={'pk': obj.pk}),
)) ))
if obj.unified_job_template: if obj.unified_job_template:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url() res['unified_job_template'] = obj.unified_job_template.get_absolute_url(self.context.get('request'))
return res return res
def validate_unified_job_template(self, value): def validate_unified_job_template(self, value):
@ -3038,7 +3040,7 @@ class ActivityStreamSerializer(BaseSerializer):
def get_related(self, obj): def get_related(self, obj):
rel = {} rel = {}
if obj.actor is not None: if obj.actor is not None:
rel['actor'] = reverse('api:user_detail', args=(obj.actor.pk,)) rel['actor'] = self.reverse('api:user_detail', kwargs={'pk': obj.actor.pk})
for fk, __ in self._local_summarizable_fk_fields: for fk, __ in self._local_summarizable_fk_fields:
if not hasattr(obj, fk): if not hasattr(obj, fk):
continue continue
@ -3051,12 +3053,12 @@ class ActivityStreamSerializer(BaseSerializer):
continue continue
id_list.append(getattr(thisItem, 'id', None)) id_list.append(getattr(thisItem, 'id', None))
if fk == 'custom_inventory_script': if fk == 'custom_inventory_script':
rel[fk].append(reverse('api:inventory_script_detail', args=(thisItem.id,))) rel[fk].append(self.reverse('api:inventory_script_detail', kwargs={'pk': thisItem.id}))
else: else:
rel[fk].append(reverse('api:' + fk + '_detail', args=(thisItem.id,))) rel[fk].append(self.reverse('api:' + fk + '_detail', kwargs={'pk': thisItem.id}))
if fk == 'schedule': if fk == 'schedule':
rel['unified_job_template'] = thisItem.unified_job_template.get_absolute_url() rel['unified_job_template'] = thisItem.unified_job_template.get_absolute_url(self.context.get('request'))
return rel return rel
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
@ -3137,7 +3139,10 @@ class FactVersionSerializer(BaseFactSerializer):
'datetime': timestamp_apiformat(obj.timestamp), 'datetime': timestamp_apiformat(obj.timestamp),
'module': obj.module, 'module': obj.module,
} }
res['fact_view'] = build_url('api:host_fact_compare_view', args=(obj.host.pk,), get=params) res['fact_view'] = '%s?%s' % (
reverse('api:host_fact_compare_view', kwargs={'pk': obj.host.pk}, request=self.context.get('request')),
urllib.urlencode(params)
)
return res return res
@ -3151,7 +3156,7 @@ class FactSerializer(BaseFactSerializer):
def get_related(self, obj): def get_related(self, obj):
res = super(FactSerializer, self).get_related(obj) res = super(FactSerializer, self).get_related(obj)
res['host'] = obj.host.get_absolute_url() res['host'] = obj.host.get_absolute_url(self.context.get('request'))
return res return res
def to_representation(self, obj): def to_representation(self, obj):

View File

@ -9,5 +9,6 @@
{% if new_in_240 %}> _Added in Ansible Tower 2.4.0_{% endif %} {% if new_in_240 %}> _Added in Ansible Tower 2.4.0_{% endif %}
{% if new_in_300 %}> _Added in Ansible Tower 3.0.0_{% endif %} {% if new_in_300 %}> _Added in Ansible Tower 3.0.0_{% endif %}
{% if new_in_310 %}> _New in Ansible Tower 3.1.0_{% endif %} {% if new_in_310 %}> _New in Ansible Tower 3.1.0_{% endif %}
{% if new_in_320 %}> _New in Ansible Tower 3.2.0_{% endif %}
{% if deprecated %}> _This resource has been deprecated and will be removed in a future release_{% endif %} {% if deprecated %}> _This resource has been deprecated and will be removed in a future release_{% endif %}
{% endif %} {% endif %}

View File

@ -331,7 +331,7 @@ activity_stream_urls = patterns('awx.api.views',
) )
v1_urls = patterns('awx.api.views', v1_urls = patterns('awx.api.views',
url(r'^$', 'api_v1_root_view'), url(r'^$', 'api_version_root_view'),
url(r'^ping/$', 'api_v1_ping_view'), url(r'^ping/$', 'api_v1_ping_view'),
url(r'^config/$', 'api_v1_config_view'), url(r'^config/$', 'api_v1_config_view'),
url(r'^auth/$', 'auth_view'), url(r'^auth/$', 'auth_view'),
@ -376,5 +376,5 @@ v1_urls = patterns('awx.api.views',
urlpatterns = patterns('awx.api.views', urlpatterns = patterns('awx.api.views',
url(r'^$', 'api_root_view'), url(r'^$', 'api_root_view'),
url(r'^v1/', include(v1_urls)), url(r'^(?P<version>(v1|v2))/', include(v1_urls))
) )

30
awx/api/versioning.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2017 Ansible by Red Hat
# All Rights Reserved.
from rest_framework.reverse import reverse as drf_reverse
from rest_framework.versioning import URLPathVersioning as BaseVersioning
def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request is None or getattr(request, 'version', None) is None:
# We need the "current request" to determine the correct version to
# prepend to reverse URLs. If there is no "current request", assume
# the latest API version.
if kwargs is None:
kwargs = {}
if 'version' not in kwargs:
kwargs['version'] = 'v2'
return drf_reverse(viewname, args, kwargs, request, format, **extra)
class URLPathVersioning(BaseVersioning):
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:
kwargs = {} if (kwargs is None) else kwargs
kwargs[self.version_param] = request.version
request = None
return super(BaseVersioning, self).reverse(
viewname, args, kwargs, request, format, **extra
)

View File

@ -20,7 +20,6 @@ from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models import Q, Count, F from django.db.models import Q, Count, F
from django.db import IntegrityError, transaction, connection from django.db import IntegrityError, transaction, connection
@ -65,6 +64,7 @@ from awx.main.ha import is_ha_environment
from awx.api.authentication import TaskAuthentication, TokenGetAuthentication from awx.api.authentication import TaskAuthentication, TokenGetAuthentication
from awx.api.generics import get_view_name from awx.api.generics import get_view_name
from awx.api.generics import * # noqa from awx.api.generics import * # noqa
from awx.api.versioning import reverse
from awx.conf.license import get_license, feature_enabled, feature_exists, LicenseForbids from awx.conf.license import get_license, feature_enabled, feature_exists, LicenseForbids
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils import * # noqa from awx.main.utils import * # noqa
@ -78,6 +78,7 @@ from awx.api.metadata import RoleMetadata
from awx.main.consumers import emit_channel_notification from awx.main.consumers import emit_channel_notification
from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.models.unified_jobs import ACTIVE_STATES
from awx.main.scheduler.tasks import run_job_complete from awx.main.scheduler.tasks import run_job_complete
from awx.main.fields import DynamicFilterField
logger = logging.getLogger('awx.api.views') logger = logging.getLogger('awx.api.views')
@ -128,17 +129,17 @@ class ApiRootView(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
view_name = _('REST API') view_name = _('REST API')
versioning_class = None
def get(self, request, format=None): def get(self, request, format=None):
''' list supported API versions ''' ''' list supported API versions '''
current = reverse('api:api_v1_root_view', args=[]) v1 = reverse('api:api_version_root_view', kwargs={'version': 'v1'})
v2 = reverse('api:api_version_root_view', kwargs={'version': 'v2'})
data = dict( data = dict(
description = _('Ansible Tower REST API'), description = _('Ansible Tower REST API'),
current_version = current, current_version = v2,
available_versions = dict( available_versions = dict(v1 = v1, v2 = v2),
v1 = current
),
) )
if feature_enabled('rebranding'): if feature_enabled('rebranding'):
data['custom_logo'] = settings.CUSTOM_LOGO data['custom_logo'] = settings.CUSTOM_LOGO
@ -146,52 +147,52 @@ class ApiRootView(APIView):
return Response(data) return Response(data)
class ApiV1RootView(APIView): class ApiVersionRootView(APIView):
authentication_classes = [] authentication_classes = []
view_name = _('Version')
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
view_name = _('Version 1')
def get(self, request, format=None): def get(self, request, format=None):
''' list top level resources ''' ''' list top level resources '''
data = OrderedDict() data = OrderedDict()
data['authtoken'] = reverse('api:auth_token_view') data['authtoken'] = reverse('api:auth_token_view', request=request)
data['ping'] = reverse('api:api_v1_ping_view') data['ping'] = reverse('api:api_v1_ping_view', request=request)
data['config'] = reverse('api:api_v1_config_view') data['config'] = reverse('api:api_v1_config_view', request=request)
data['settings'] = reverse('api:setting_category_list') data['settings'] = reverse('api:setting_category_list', request=request)
data['me'] = reverse('api:user_me_list') data['me'] = reverse('api:user_me_list', request=request)
data['dashboard'] = reverse('api:dashboard_view') data['dashboard'] = reverse('api:dashboard_view', request=request)
data['organizations'] = reverse('api:organization_list') data['organizations'] = reverse('api:organization_list', request=request)
data['users'] = reverse('api:user_list') data['users'] = reverse('api:user_list', request=request)
data['projects'] = reverse('api:project_list') data['projects'] = reverse('api:project_list', request=request)
data['project_updates'] = reverse('api:project_update_list') data['project_updates'] = reverse('api:project_update_list', request=request)
data['teams'] = reverse('api:team_list') data['teams'] = reverse('api:team_list', request=request)
data['credentials'] = reverse('api:credential_list') data['credentials'] = reverse('api:credential_list', request=request)
data['inventory'] = reverse('api:inventory_list') data['inventory'] = reverse('api:inventory_list', request=request)
data['inventory_scripts'] = reverse('api:inventory_script_list') data['inventory_scripts'] = reverse('api:inventory_script_list', request=request)
data['inventory_sources'] = reverse('api:inventory_source_list') data['inventory_sources'] = reverse('api:inventory_source_list', request=request)
data['inventory_updates'] = reverse('api:inventory_update_list') data['inventory_updates'] = reverse('api:inventory_update_list', request=request)
data['groups'] = reverse('api:group_list') data['groups'] = reverse('api:group_list', request=request)
data['hosts'] = reverse('api:host_list') data['hosts'] = reverse('api:host_list', request=request)
data['job_templates'] = reverse('api:job_template_list') data['job_templates'] = reverse('api:job_template_list', request=request)
data['jobs'] = reverse('api:job_list') data['jobs'] = reverse('api:job_list', request=request)
data['job_events'] = reverse('api:job_event_list') data['job_events'] = reverse('api:job_event_list', request=request)
data['ad_hoc_commands'] = reverse('api:ad_hoc_command_list') data['ad_hoc_commands'] = reverse('api:ad_hoc_command_list', request=request)
data['system_job_templates'] = reverse('api:system_job_template_list') data['system_job_templates'] = reverse('api:system_job_template_list', request=request)
data['system_jobs'] = reverse('api:system_job_list') data['system_jobs'] = reverse('api:system_job_list', request=request)
data['schedules'] = reverse('api:schedule_list') data['schedules'] = reverse('api:schedule_list', request=request)
data['roles'] = reverse('api:role_list') data['roles'] = reverse('api:role_list', request=request)
data['notification_templates'] = reverse('api:notification_template_list') data['notification_templates'] = reverse('api:notification_template_list', request=request)
data['notifications'] = reverse('api:notification_list') data['notifications'] = reverse('api:notification_list', request=request)
data['labels'] = reverse('api:label_list') data['labels'] = reverse('api:label_list', request=request)
data['unified_job_templates'] = reverse('api:unified_job_template_list') data['unified_job_templates'] = reverse('api:unified_job_template_list', request=request)
data['unified_jobs'] = reverse('api:unified_job_list') data['unified_jobs'] = reverse('api:unified_job_list', request=request)
data['activity_stream'] = reverse('api:activity_stream_list') data['activity_stream'] = reverse('api:activity_stream_list', request=request)
data['workflow_job_templates'] = reverse('api:workflow_job_template_list') data['workflow_job_templates'] = reverse('api:workflow_job_template_list', request=request)
data['workflow_jobs'] = reverse('api:workflow_job_list') data['workflow_jobs'] = reverse('api:workflow_job_list', request=request)
data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list') data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request)
data['workflow_job_nodes'] = reverse('api:workflow_job_node_list') data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request)
return Response(data) return Response(data)
@ -336,12 +337,12 @@ class DashboardView(APIView):
def get(self, request, format=None): def get(self, request, format=None):
''' Show Dashboard Details ''' ''' Show Dashboard Details '''
data = OrderedDict() data = OrderedDict()
data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view')} data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view', request=request)}
user_inventory = get_user_queryset(request.user, Inventory) user_inventory = get_user_queryset(request.user, Inventory)
inventory_with_failed_hosts = user_inventory.filter(hosts_with_active_failures__gt=0) inventory_with_failed_hosts = user_inventory.filter(hosts_with_active_failures__gt=0)
user_inventory_external = user_inventory.filter(has_inventory_sources=True) user_inventory_external = user_inventory.filter(has_inventory_sources=True)
failed_inventory = sum(i.inventory_sources_with_failures for i in user_inventory) failed_inventory = sum(i.inventory_sources_with_failures for i in user_inventory)
data['inventories'] = {'url': reverse('api:inventory_list'), data['inventories'] = {'url': reverse('api:inventory_list', request=request),
'total': user_inventory.count(), 'total': user_inventory.count(),
'total_with_inventory_source': user_inventory_external.count(), 'total_with_inventory_source': user_inventory_external.count(),
'job_failed': inventory_with_failed_hosts.count(), 'job_failed': inventory_with_failed_hosts.count(),
@ -352,13 +353,13 @@ class DashboardView(APIView):
ec2_inventory_sources = user_inventory_sources.filter(source='ec2') ec2_inventory_sources = user_inventory_sources.filter(source='ec2')
ec2_inventory_failed = ec2_inventory_sources.filter(status='failed') ec2_inventory_failed = ec2_inventory_sources.filter(status='failed')
data['inventory_sources'] = {} data['inventory_sources'] = {}
data['inventory_sources']['rax'] = {'url': reverse('api:inventory_source_list') + "?source=rax", data['inventory_sources']['rax'] = {'url': reverse('api:inventory_source_list', request=request) + "?source=rax",
'label': 'Rackspace', 'label': 'Rackspace',
'failures_url': reverse('api:inventory_source_list') + "?source=rax&status=failed", 'failures_url': reverse('api:inventory_source_list', request=request) + "?source=rax&status=failed",
'total': rax_inventory_sources.count(), 'total': rax_inventory_sources.count(),
'failed': rax_inventory_failed.count()} 'failed': rax_inventory_failed.count()}
data['inventory_sources']['ec2'] = {'url': reverse('api:inventory_source_list') + "?source=ec2", data['inventory_sources']['ec2'] = {'url': reverse('api:inventory_source_list', request=request) + "?source=ec2",
'failures_url': reverse('api:inventory_source_list') + "?source=ec2&status=failed", 'failures_url': reverse('api:inventory_source_list', request=request) + "?source=ec2&status=failed",
'label': 'Amazon EC2', 'label': 'Amazon EC2',
'total': ec2_inventory_sources.count(), 'total': ec2_inventory_sources.count(),
'failed': ec2_inventory_failed.count()} 'failed': ec2_inventory_failed.count()}
@ -366,23 +367,23 @@ class DashboardView(APIView):
user_groups = get_user_queryset(request.user, Group) user_groups = get_user_queryset(request.user, Group)
groups_job_failed = (Group.objects.filter(hosts_with_active_failures__gt=0) | Group.objects.filter(groups_with_active_failures__gt=0)).count() groups_job_failed = (Group.objects.filter(hosts_with_active_failures__gt=0) | Group.objects.filter(groups_with_active_failures__gt=0)).count()
groups_inventory_failed = Group.objects.filter(inventory_sources__last_job_failed=True).count() groups_inventory_failed = Group.objects.filter(inventory_sources__last_job_failed=True).count()
data['groups'] = {'url': reverse('api:group_list'), data['groups'] = {'url': reverse('api:group_list', request=request),
'failures_url': reverse('api:group_list') + "?has_active_failures=True", 'failures_url': reverse('api:group_list', request=request) + "?has_active_failures=True",
'total': user_groups.count(), 'total': user_groups.count(),
'job_failed': groups_job_failed, 'job_failed': groups_job_failed,
'inventory_failed': groups_inventory_failed} 'inventory_failed': groups_inventory_failed}
user_hosts = get_user_queryset(request.user, Host) user_hosts = get_user_queryset(request.user, Host)
user_hosts_failed = user_hosts.filter(has_active_failures=True) user_hosts_failed = user_hosts.filter(has_active_failures=True)
data['hosts'] = {'url': reverse('api:host_list'), data['hosts'] = {'url': reverse('api:host_list', request=request),
'failures_url': reverse('api:host_list') + "?has_active_failures=True", 'failures_url': reverse('api:host_list', request=request) + "?has_active_failures=True",
'total': user_hosts.count(), 'total': user_hosts.count(),
'failed': user_hosts_failed.count()} 'failed': user_hosts_failed.count()}
user_projects = get_user_queryset(request.user, Project) user_projects = get_user_queryset(request.user, Project)
user_projects_failed = user_projects.filter(last_job_failed=True) user_projects_failed = user_projects.filter(last_job_failed=True)
data['projects'] = {'url': reverse('api:project_list'), data['projects'] = {'url': reverse('api:project_list', request=request),
'failures_url': reverse('api:project_list') + "?last_job_failed=True", 'failures_url': reverse('api:project_list', request=request) + "?last_job_failed=True",
'total': user_projects.count(), 'total': user_projects.count(),
'failed': user_projects_failed.count()} 'failed': user_projects_failed.count()}
@ -393,26 +394,26 @@ class DashboardView(APIView):
hg_projects = user_projects.filter(scm_type='hg') hg_projects = user_projects.filter(scm_type='hg')
hg_failed_projects = hg_projects.filter(last_job_failed=True) hg_failed_projects = hg_projects.filter(last_job_failed=True)
data['scm_types'] = {} data['scm_types'] = {}
data['scm_types']['git'] = {'url': reverse('api:project_list') + "?scm_type=git", data['scm_types']['git'] = {'url': reverse('api:project_list', request=request) + "?scm_type=git",
'label': 'Git', 'label': 'Git',
'failures_url': reverse('api:project_list') + "?scm_type=git&last_job_failed=True", 'failures_url': reverse('api:project_list', request=request) + "?scm_type=git&last_job_failed=True",
'total': git_projects.count(), 'total': git_projects.count(),
'failed': git_failed_projects.count()} 'failed': git_failed_projects.count()}
data['scm_types']['svn'] = {'url': reverse('api:project_list') + "?scm_type=svn", data['scm_types']['svn'] = {'url': reverse('api:project_list', request=request) + "?scm_type=svn",
'label': 'Subversion', 'label': 'Subversion',
'failures_url': reverse('api:project_list') + "?scm_type=svn&last_job_failed=True", 'failures_url': reverse('api:project_list', request=request) + "?scm_type=svn&last_job_failed=True",
'total': svn_projects.count(), 'total': svn_projects.count(),
'failed': svn_failed_projects.count()} 'failed': svn_failed_projects.count()}
data['scm_types']['hg'] = {'url': reverse('api:project_list') + "?scm_type=hg", data['scm_types']['hg'] = {'url': reverse('api:project_list', request=request) + "?scm_type=hg",
'label': 'Mercurial', 'label': 'Mercurial',
'failures_url': reverse('api:project_list') + "?scm_type=hg&last_job_failed=True", 'failures_url': reverse('api:project_list', request=request) + "?scm_type=hg&last_job_failed=True",
'total': hg_projects.count(), 'total': hg_projects.count(),
'failed': hg_failed_projects.count()} 'failed': hg_failed_projects.count()}
user_jobs = get_user_queryset(request.user, Job) user_jobs = get_user_queryset(request.user, Job)
user_failed_jobs = user_jobs.filter(failed=True) user_failed_jobs = user_jobs.filter(failed=True)
data['jobs'] = {'url': reverse('api:job_list'), data['jobs'] = {'url': reverse('api:job_list', request=request),
'failure_url': reverse('api:job_list') + "?failed=True", 'failure_url': reverse('api:job_list', request=request) + "?failed=True",
'total': user_jobs.count(), 'total': user_jobs.count(),
'failed': user_failed_jobs.count()} 'failed': user_failed_jobs.count()}
@ -421,15 +422,15 @@ class DashboardView(APIView):
credential_list = get_user_queryset(request.user, Credential) credential_list = get_user_queryset(request.user, Credential)
job_template_list = get_user_queryset(request.user, JobTemplate) job_template_list = get_user_queryset(request.user, JobTemplate)
organization_list = get_user_queryset(request.user, Organization) organization_list = get_user_queryset(request.user, Organization)
data['users'] = {'url': reverse('api:user_list'), data['users'] = {'url': reverse('api:user_list', request=request),
'total': user_list.count()} 'total': user_list.count()}
data['organizations'] = {'url': reverse('api:organization_list'), data['organizations'] = {'url': reverse('api:organization_list', request=request),
'total': organization_list.count()} 'total': organization_list.count()}
data['teams'] = {'url': reverse('api:team_list'), data['teams'] = {'url': reverse('api:team_list', request=request),
'total': team_list.count()} 'total': team_list.count()}
data['credentials'] = {'url': reverse('api:credential_list'), data['credentials'] = {'url': reverse('api:credential_list', request=request),
'total': credential_list.count()} 'total': credential_list.count()}
data['job_templates'] = {'url': reverse('api:job_template_list'), data['job_templates'] = {'url': reverse('api:job_template_list', request=request),
'total': job_template_list.count()} 'total': job_template_list.count()}
return Response(data) return Response(data)
@ -516,6 +517,7 @@ class AuthView(APIView):
new_in_240 = True new_in_240 = True
def get(self, request): def get(self, request):
from rest_framework.reverse import reverse
data = OrderedDict() data = OrderedDict()
err_backend, err_message = request.session.get('social_auth_error', (None, None)) err_backend, err_message = request.session.get('social_auth_error', (None, None))
auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True).items() auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True).items()
@ -527,6 +529,7 @@ class AuthView(APIView):
(not feature_enabled('enterprise_auth') and (not feature_enabled('enterprise_auth') and
name in ['saml', 'radius']): name in ['saml', 'radius']):
continue continue
login_url = reverse('social:begin', args=(name,)) login_url = reverse('social:begin', args=(name,))
complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,))) complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,)))
backend_data = { backend_data = {
@ -1167,7 +1170,7 @@ class ProjectUpdateView(RetrieveAPIView):
if not project_update: if not project_update:
return Response({}, status=status.HTTP_400_BAD_REQUEST) return Response({}, status=status.HTTP_400_BAD_REQUEST)
else: else:
headers = {'Location': project_update.get_absolute_url()} headers = {'Location': project_update.get_absolute_url(request=request)}
return Response({'project_update': project_update.id}, return Response({'project_update': project_update.id},
headers=headers, headers=headers,
status=status.HTTP_202_ACCEPTED) status=status.HTTP_202_ACCEPTED)
@ -1699,6 +1702,14 @@ class HostList(ListCreateAPIView):
serializer_class = HostSerializer serializer_class = HostSerializer
capabilities_prefetch = ['inventory.admin'] capabilities_prefetch = ['inventory.admin']
def get_queryset(self):
qs = super(HostList, self).get_queryset()
filter_string = self.request.query_params.get('host_filter', None)
if filter_string:
filter_q = DynamicFilterField.filter_string_to_q(filter_string)
qs = qs.filter(filter_q)
return qs
class HostDetail(RetrieveUpdateDestroyAPIView): class HostDetail(RetrieveUpdateDestroyAPIView):
@ -2273,7 +2284,7 @@ class InventorySourceUpdateView(RetrieveAPIView):
if not inventory_update: if not inventory_update:
return Response({}, status=status.HTTP_400_BAD_REQUEST) return Response({}, status=status.HTTP_400_BAD_REQUEST)
else: else:
headers = {'Location': inventory_update.get_absolute_url()} headers = {'Location': inventory_update.get_absolute_url(request=request)}
return Response(dict(inventory_update=inventory_update.id), status=status.HTTP_202_ACCEPTED, headers=headers) return Response(dict(inventory_update=inventory_update.id), status=status.HTTP_202_ACCEPTED, headers=headers)
else: else:
return self.http_method_not_allowed(request, *args, **kwargs) return self.http_method_not_allowed(request, *args, **kwargs)
@ -2741,7 +2752,7 @@ class JobTemplateCallback(GenericAPIView):
return Response(data, status=status.HTTP_400_BAD_REQUEST) return Response(data, status=status.HTTP_400_BAD_REQUEST)
# Return the location of the new job. # Return the location of the new job.
headers = {'Location': job.get_absolute_url()} headers = {'Location': job.get_absolute_url(request=request)}
return Response(status=status.HTTP_201_CREATED, headers=headers) return Response(status=status.HTTP_201_CREATED, headers=headers)
@ -3035,7 +3046,7 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView):
new_workflow_job.signal_start() new_workflow_job.signal_start()
data = WorkflowJobSerializer(new_workflow_job, context=self.get_serializer_context()).data data = WorkflowJobSerializer(new_workflow_job, context=self.get_serializer_context()).data
headers = {'Location': new_workflow_job.get_absolute_url()} headers = {'Location': new_workflow_job.get_absolute_url(request=request)}
return Response(data, status=status.HTTP_201_CREATED, headers=headers) return Response(data, status=status.HTTP_201_CREATED, headers=headers)
@ -3419,7 +3430,7 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView):
data = JobSerializer(new_job, context=self.get_serializer_context()).data data = JobSerializer(new_job, context=self.get_serializer_context()).data
# Add job key to match what old relaunch returned. # Add job key to match what old relaunch returned.
data['job'] = new_job.id data['job'] = new_job.id
headers = {'Location': new_job.get_absolute_url()} headers = {'Location': new_job.get_absolute_url(request=request)}
return Response(data, status=status.HTTP_201_CREATED, headers=headers) return Response(data, status=status.HTTP_201_CREATED, headers=headers)
@ -3693,7 +3704,7 @@ class AdHocCommandRelaunch(GenericAPIView):
data = AdHocCommandSerializer(new_ad_hoc_command, context=self.get_serializer_context()).data data = AdHocCommandSerializer(new_ad_hoc_command, context=self.get_serializer_context()).data
# Add ad_hoc_command key to match what was previously returned. # Add ad_hoc_command key to match what was previously returned.
data['ad_hoc_command'] = new_ad_hoc_command.id data['ad_hoc_command'] = new_ad_hoc_command.id
headers = {'Location': new_ad_hoc_command.get_absolute_url()} headers = {'Location': new_ad_hoc_command.get_absolute_url(request=request)}
return Response(data, status=status.HTTP_201_CREATED, headers=headers) return Response(data, status=status.HTTP_201_CREATED, headers=headers)
@ -3994,7 +4005,7 @@ class NotificationTemplateTest(GenericAPIView):
return Response({}, status=status.HTTP_400_BAD_REQUEST) return Response({}, status=status.HTTP_400_BAD_REQUEST)
else: else:
send_notifications.delay([notification.id]) send_notifications.delay([notification.id])
headers = {'Location': notification.get_absolute_url()} headers = {'Location': notification.get_absolute_url(request=request)}
return Response({"notification": notification.id}, return Response({"notification": notification.id},
headers=headers, headers=headers,
status=status.HTTP_202_ACCEPTED) status=status.HTTP_202_ACCEPTED)

View File

@ -7,7 +7,6 @@ import sys
# Django # Django
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -20,6 +19,7 @@ from rest_framework import status
# Tower # Tower
from awx.api.generics import * # noqa from awx.api.generics import * # noqa
from awx.api.permissions import IsSuperUser from awx.api.permissions import IsSuperUser
from awx.api.versioning import reverse
from awx.main.utils import * # noqa from awx.main.utils import * # noqa
from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException
from awx.conf.license import get_licensed_features from awx.conf.license import get_licensed_features
@ -49,7 +49,7 @@ class SettingCategoryList(ListAPIView):
else: else:
categories = {} categories = {}
for category_slug in sorted(categories.keys()): for category_slug in sorted(categories.keys()):
url = reverse('api:setting_singleton_detail', args=(category_slug,)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': category_slug}, request=self.request)
setting_categories.append(SettingCategory(url, category_slug, categories[category_slug])) setting_categories.append(SettingCategory(url, category_slug, categories[category_slug]))
return setting_categories return setting_categories

View File

@ -60,7 +60,7 @@ def executor(tmpdir_factory, request):
cli = PlaybookCLI(['', 'playbook.yml']) cli = PlaybookCLI(['', 'playbook.yml'])
cli.parse() cli.parse()
options = cli.parser.parse_args([])[0] options = cli.parser.parse_args(['-v'])[0]
loader = DataLoader() loader = DataLoader()
variable_manager = VariableManager() variable_manager = VariableManager()
inventory = Inventory(loader=loader, variable_manager=variable_manager, inventory = Inventory(loader=loader, variable_manager=variable_manager,

View File

@ -30,6 +30,8 @@ from ansible.plugins.callback.default import CallbackModule as DefaultCallbackMo
from .events import event_context from .events import event_context
from .minimal import CallbackModule as MinimalCallbackModule from .minimal import CallbackModule as MinimalCallbackModule
CENSORED = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
class BaseCallbackModule(CallbackBase): class BaseCallbackModule(CallbackBase):
''' '''
@ -69,8 +71,12 @@ class BaseCallbackModule(CallbackBase):
else: else:
task = None task = None
if event_data.get('res') and event_data['res'].get('_ansible_no_log', False): if event_data.get('res'):
event_data['res'] = {'censored': "the output has been hidden due to the fact that 'no_log: true' was specified for this result"} # noqa if event_data['res'].get('_ansible_no_log', False):
event_data['res'] = {'censored': CENSORED}
for i, item in enumerate(event_data['res'].get('results', [])):
if event_data['res']['results'][i].get('_ansible_no_log', False):
event_data['res']['results'][i] = {'censored': CENSORED}
with event_context.display_lock: with event_context.display_lock:
try: try:

View File

@ -242,9 +242,11 @@ register(
field_class=fields.IntegerField, field_class=fields.IntegerField,
allow_null=True, allow_null=True,
label=_('Logging Aggregator Port'), label=_('Logging Aggregator Port'),
help_text=_('Port on Logging Aggregator to send logs to (if required).'), help_text=_('Port on Logging Aggregator to send logs to (if required and not'
' provided in Logging Aggregator).'),
category=_('Logging'), category=_('Logging'),
category_slug='logging', category_slug='logging',
required=False
) )
register( register(
'LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_TYPE',

View File

@ -1,7 +1,11 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
# Python
import json import json
import re
import sys
from pyparsing import infixNotation, opAssoc, Optional, Literal, CharsNotIn
# Django # Django
from django.db.models.signals import ( from django.db.models.signals import (
@ -18,6 +22,7 @@ from django.db.models.fields.related import (
ReverseManyRelatedObjectsDescriptor, ReverseManyRelatedObjectsDescriptor,
) )
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.db.models import Q
# Django-JSONField # Django-JSONField
from jsonfield import JSONField as upstream_JSONField from jsonfield import JSONField as upstream_JSONField
@ -27,7 +32,7 @@ from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
from awx.main.utils import get_current_apps from awx.main.utils import get_current_apps
__all__ = ['AutoOneToOneField', 'ImplicitRoleField', 'JSONField'] __all__ = ['AutoOneToOneField', 'ImplicitRoleField', 'JSONField', 'DynamicFilterField']
class JSONField(upstream_JSONField): class JSONField(upstream_JSONField):
@ -292,3 +297,187 @@ class ImplicitRoleField(models.ForeignKey):
child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)] child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)]
Role_.objects.filter(id__in=role_ids).delete() Role_.objects.filter(id__in=role_ids).delete()
Role.rebuild_role_ancestor_list([], child_ids) Role.rebuild_role_ancestor_list([], child_ids)
unicode_spaces = [unichr(c) for c in xrange(sys.maxunicode) if unichr(c).isspace()]
unicode_spaces_other = unicode_spaces + [u'(', u')', u'=', u'"']
def string_to_type(t):
if t == 'true':
return True
elif t == 'false':
return False
if re.search('^[-+]?[0-9]+$',t):
return int(t)
if re.search('^[-+]?[0-9]+\.[0-9]+$',t):
return float(t)
return t
class DynamicFilterField(models.TextField):
class BoolOperand(object):
def __init__(self, t):
#print("Got t %s" % t)
kwargs = dict()
k, v = self._extract_key_value(t)
k, v = self._json_path_to_contains(k, v)
kwargs[k] = v
self.result = Q(**kwargs)
'''
TODO: We should be able to express this in the grammar and let
pyparsing do the heavy lifting.
TODO: separate django filter requests from our custom json filter
request so we don't process the key any. This could be
accomplished using a whitelist or introspecting the
relationship refered to to see if it's a jsonb type.
'''
def _json_path_to_contains(self, k, v):
pieces = k.split('__')
flag_first_arr_found = False
assembled_k = ''
assembled_v = v
last_kv = None
last_v = None
contains_count = 0
for i, piece in enumerate(pieces):
if flag_first_arr_found is False and piece.endswith('[]'):
assembled_k += '%s__contains' % (piece[0:-2])
contains_count += 1
flag_first_arr_found = True
elif flag_first_arr_found is False and i == len(pieces) - 1:
assembled_k += '%s' % piece
elif flag_first_arr_found is False:
assembled_k += '%s__' % piece
elif flag_first_arr_found is True:
new_kv = dict()
if piece.endswith('[]'):
new_v = []
new_kv[piece[0:-2]] = new_v
else:
new_v = dict()
new_kv[piece] = new_v
if last_v is None:
last_v = []
assembled_v = last_v
if type(last_v) is list:
last_v.append(new_kv)
elif type(last_v) is dict:
last_kv[last_kv.keys()[0]] = new_kv
last_v = new_v
last_kv = new_kv
contains_count += 1
if contains_count > 1:
if type(last_v) is list:
last_v.append(v)
if type(last_v) is dict:
last_kv[last_kv.keys()[0]] = v
return (assembled_k, assembled_v)
def _extract_key_value(self, t):
t_len = len(t)
k = None
v = None
# key
# "something"=
v_offset = 2
if t_len >= 2 and t[0] == "\"" and t[2] == "\"":
k = t[1]
v_offset = 4
# something=
else:
k = t[0]
# value
# ="something"
if t_len > (v_offset + 2) and t[v_offset] == "\"" and t[v_offset + 2] == "\"":
v = t[v_offset + 1]
# empty ""
elif t_len > (v_offset + 1):
v = ""
# no ""
else:
v = string_to_type(t[v_offset])
return (k, v)
class BoolBinOp(object):
def __init__(self, t):
self.left = t[0][0].result
self.right = t[0][2].result
self.result = self.execute_logic(self.left, self.right)
class BoolAnd(BoolBinOp):
def execute_logic(self, left, right):
return left & right
class BoolOr(BoolBinOp):
def execute_logic(self, left, right):
return left | right
class BoolNot(object):
def __init__(self,t):
self.right = t[0][1]
self.result = ~self.right
@classmethod
def filter_string_to_q(cls, filter_string):
'''
TODO:
* handle values with " via: a.b.c.d="hello\"world"
* handle keys with " via: a.\"b.c="yeah"
* handle key with __ in it
* add not support
* transform [] into contains via: a.b.c[].d[].e.f[]="blah"
* handle optional value quoted: a.b.c=""
'''
atom = CharsNotIn(unicode_spaces_other)
atom_inside_quotes = CharsNotIn(u'"')
atom_quoted = Literal('"') + Optional(atom_inside_quotes) + Literal('"')
EQUAL = Literal('=')
grammar = ((atom_quoted | atom) + EQUAL + Optional((atom_quoted | atom)))
grammar.setParseAction(cls.BoolOperand)
boolExpr = infixNotation(grammar, [
#("not", 1, opAssoc.RIGHT, cls.BoolNot),
("and", 2, opAssoc.LEFT, cls.BoolAnd),
("or", 2, opAssoc.LEFT, cls.BoolOr),
])
res = boolExpr.parseString('(' + filter_string + ')')
if len(res) > 0:
return res[0].result
raise RuntimeError("Parsing the filter_string %s went terribly wrong" % filter_string)

View File

@ -1,9 +1,11 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
# Tower
from awx.api.versioning import reverse
# Django # Django
from django.db import models from django.db import models
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
__all__ = ['ActivityStream'] __all__ = ['ActivityStream']
@ -63,8 +65,8 @@ class ActivityStream(models.Model):
label = models.ManyToManyField("Label", blank=True) label = models.ManyToManyField("Label", blank=True)
role = models.ManyToManyField("Role", blank=True) role = models.ManyToManyField("Role", blank=True)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:activity_stream_detail', args=(self.pk,)) return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# For compatibility with Django 1.4.x, attempt to handle any calls to # For compatibility with Django 1.4.x, attempt to handle any calls to

View File

@ -3,6 +3,7 @@
# Python # Python
import datetime import datetime
import hashlib
import hmac import hmac
import logging import logging
from urlparse import urljoin from urlparse import urljoin
@ -15,9 +16,9 @@ from django.utils.text import Truncator
from django.utils.timezone import utc from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa from awx.main.models.unified_jobs import * # noqa
from awx.main.models.notifications import JobNotificationMixin, NotificationTemplate from awx.main.models.notifications import JobNotificationMixin, NotificationTemplate
@ -142,8 +143,8 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
from awx.main.tasks import RunAdHocCommand from awx.main.tasks import RunAdHocCommand
return RunAdHocCommand return RunAdHocCommand
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:ad_hoc_command_detail', args=(self.pk,)) return reverse('api:ad_hoc_command_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urljoin(settings.TOWER_URL_BASE, "/#/ad_hoc_commands/{}".format(self.pk)) return urljoin(settings.TOWER_URL_BASE, "/#/ad_hoc_commands/{}".format(self.pk))
@ -152,7 +153,7 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
def task_auth_token(self): def task_auth_token(self):
'''Return temporary auth token used for task requests via API.''' '''Return temporary auth token used for task requests via API.'''
if self.status == 'running': if self.status == 'running':
h = hmac.new(settings.SECRET_KEY, self.created.isoformat()) h = hmac.new(settings.SECRET_KEY, self.created.isoformat(), digestmod=hashlib.sha1)
return '%d-%s' % (self.pk, h.hexdigest()) return '%d-%s' % (self.pk, h.hexdigest())
@property @property
@ -317,8 +318,8 @@ class AdHocCommandEvent(CreatedModifiedModel):
editable=False, editable=False,
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:ad_hoc_command_event_detail', args=(self.pk,)) return reverse('api:ad_hoc_command_event_detail', kwargs={'pk': self.pk}, request=request)
def __unicode__(self): def __unicode__(self):
return u'%s @ %s' % (self.get_event_display(), self.created.isoformat()) return u'%s @ %s' % (self.get_event_display(), self.created.isoformat())

View File

@ -5,9 +5,9 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.fields import ImplicitRoleField from awx.main.fields import ImplicitRoleField
from awx.main.constants import CLOUD_PROVIDERS from awx.main.constants import CLOUD_PROVIDERS
from awx.main.utils import decrypt_field from awx.main.utils import decrypt_field
@ -271,8 +271,8 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
needed.append(field) needed.append(field)
return needed return needed
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:credential_detail', args=(self.pk,)) return reverse('api:credential_detail', kwargs={'pk': self.pk}, request=request)
def clean_host(self): def clean_host(self):
"""Ensure that if this is a type of credential that requires a """Ensure that if this is a type of credential that requires a

View File

@ -14,10 +14,10 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db import transaction from django.db import transaction
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.timezone import now from django.utils.timezone import now
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.constants import CLOUD_PROVIDERS from awx.main.constants import CLOUD_PROVIDERS
from awx.main.fields import AutoOneToOneField, ImplicitRoleField from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.managers import HostManager from awx.main.managers import HostManager
@ -117,9 +117,8 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin):
'admin_role', 'admin_role',
]) ])
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:inventory_detail', args=(self.pk,)) return reverse('api:inventory_detail', kwargs={'pk': self.pk}, request=request)
variables_dict = VarsDictProperty('variables') variables_dict = VarsDictProperty('variables')
@ -389,8 +388,8 @@ class Host(CommonModelNameNotUnique):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:host_detail', args=(self.pk,)) return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request)
def update_computed_fields(self, update_inventory=True, update_groups=True): def update_computed_fields(self, update_inventory=True, update_groups=True):
''' '''
@ -520,8 +519,8 @@ class Group(CommonModelNameNotUnique):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:group_detail', args=(self.pk,)) return reverse('api:group_detail', kwargs={'pk': self.pk}, request=request)
@transaction.atomic @transaction.atomic
def delete_recursive(self): def delete_recursive(self):
@ -1137,8 +1136,8 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
else: else:
return 'none' return 'none'
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:inventory_source_detail', args=(self.pk,)) return reverse('api:inventory_source_detail', kwargs={'pk': self.pk}, request=request)
def _can_update(self): def _can_update(self):
if self.source == 'custom': if self.source == 'custom':
@ -1246,8 +1245,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
update_fields.append('name') update_fields.append('name')
super(InventoryUpdate, self).save(*args, **kwargs) super(InventoryUpdate, self).save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:inventory_update_detail', args=(self.pk,)) return reverse('api:inventory_update_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urljoin(settings.TOWER_URL_BASE, "/#/inventory_sync/{}".format(self.pk)) return urljoin(settings.TOWER_URL_BASE, "/#/inventory_sync/{}".format(self.pk))
@ -1322,5 +1321,5 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
parent_role=['organization.auditor_role', 'organization.member_role', 'admin_role'], parent_role=['organization.auditor_role', 'organization.member_role', 'admin_role'],
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:inventory_script_detail', args=(self.pk,)) return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request)

View File

@ -3,6 +3,7 @@
# Python # Python
import datetime import datetime
import hashlib
import hmac import hmac
import logging import logging
import time import time
@ -17,9 +18,9 @@ from django.utils.encoding import force_text
from django.utils.timezone import utc from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.constants import CLOUD_PROVIDERS from awx.main.constants import CLOUD_PROVIDERS
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa from awx.main.models.unified_jobs import * # noqa
@ -292,8 +293,8 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
''' '''
return self.create_unified_job(**kwargs) return self.create_unified_job(**kwargs)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:job_template_detail', args=(self.pk,)) return reverse('api:job_template_detail', kwargs={'pk': self.pk}, request=request)
def can_start_without_user_input(self, callback_extra_vars=None): def can_start_without_user_input(self, callback_extra_vars=None):
''' '''
@ -470,8 +471,8 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
def _get_unified_job_template_class(cls): def _get_unified_job_template_class(cls):
return JobTemplate return JobTemplate
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:job_detail', args=(self.pk,)) return reverse('api:job_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urljoin(settings.TOWER_URL_BASE, "/#/jobs/{}".format(self.pk)) return urljoin(settings.TOWER_URL_BASE, "/#/jobs/{}".format(self.pk))
@ -480,7 +481,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
def task_auth_token(self): def task_auth_token(self):
'''Return temporary auth token used for task requests via API.''' '''Return temporary auth token used for task requests via API.'''
if self.status == 'running': if self.status == 'running':
h = hmac.new(settings.SECRET_KEY, self.created.isoformat()) h = hmac.new(settings.SECRET_KEY, self.created.isoformat(), digestmod=hashlib.sha1)
return '%d-%s' % (self.pk, h.hexdigest()) return '%d-%s' % (self.pk, h.hexdigest())
@property @property
@ -687,8 +688,8 @@ class JobHostSummary(CreatedModifiedModel):
(self.host.name, self.changed, self.dark, self.failures, self.ok, (self.host.name, self.changed, self.dark, self.failures, self.ok,
self.processed, self.skipped) self.processed, self.skipped)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:job_host_summary_detail', args=(self.pk,)) return reverse('api:job_host_summary_detail', kwargs={'pk': self.pk}, request=request)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# If update_fields has been specified, add our field names to it, # If update_fields has been specified, add our field names to it,
@ -905,8 +906,8 @@ class JobEvent(CreatedModifiedModel):
editable=False, editable=False,
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:job_event_detail', args=(self.pk,)) return reverse('api:job_event_detail', kwargs={'pk': self.pk}, request=request)
def __unicode__(self): def __unicode__(self):
return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat()) return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat())
@ -1219,8 +1220,8 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions):
def _get_unified_job_field_names(cls): def _get_unified_job_field_names(cls):
return ['name', 'description', 'job_type', 'extra_vars'] return ['name', 'description', 'job_type', 'extra_vars']
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:system_job_template_detail', args=(self.pk,)) return reverse('api:system_job_template_detail', kwargs={'pk': self.pk}, request=request)
@property @property
def cache_timeout_blocked(self): def cache_timeout_blocked(self):
@ -1275,8 +1276,8 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):
def websocket_emit_data(self): def websocket_emit_data(self):
return {} return {}
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:system_job_detail', args=(self.pk,)) return reverse('api:system_job_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urljoin(settings.TOWER_URL_BASE, "/#/management_jobs/{}".format(self.pk)) return urljoin(settings.TOWER_URL_BASE, "/#/management_jobs/{}".format(self.pk))

View File

@ -3,10 +3,10 @@
# Django # Django
from django.db import models from django.db import models
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models.base import CommonModelNameNotUnique from awx.main.models.base import CommonModelNameNotUnique
from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob
@ -30,8 +30,8 @@ class Label(CommonModelNameNotUnique):
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:label_detail', args=(self.pk,)) return reverse('api:label_detail', kwargs={'pk': self.pk}, request=request)
@staticmethod @staticmethod
def get_orphaned_labels(): def get_orphaned_labels():

View File

@ -130,13 +130,18 @@ class SurveyJobTemplateMixin(models.Model):
for survey_element in self.survey_spec.get("spec", []): for survey_element in self.survey_spec.get("spec", []):
default = survey_element.get('default') default = survey_element.get('default')
variable_key = survey_element.get('variable') variable_key = survey_element.get('variable')
if survey_element.get('type') == 'password': if survey_element.get('type') == 'password':
if variable_key in kwargs_extra_vars and default: if variable_key in kwargs_extra_vars and default:
kw_value = kwargs_extra_vars[variable_key] kw_value = kwargs_extra_vars[variable_key]
if kw_value.startswith('$encrypted$') and kw_value != default: if kw_value.startswith('$encrypted$') and kw_value != default:
kwargs_extra_vars[variable_key] = default kwargs_extra_vars[variable_key] = default
if default is not None: if default is not None:
extra_vars[variable_key] = default data = {variable_key: default}
errors = self._survey_element_validation(survey_element, data)
if not errors:
extra_vars[variable_key] = default
# Overwrite job template extra vars with explicit job extra vars # Overwrite job template extra vars with explicit job extra vars
# and add on job extra vars # and add on job extra vars
@ -144,6 +149,65 @@ class SurveyJobTemplateMixin(models.Model):
kwargs['extra_vars'] = json.dumps(extra_vars) kwargs['extra_vars'] = json.dumps(extra_vars)
return kwargs return kwargs
def _survey_element_validation(self, survey_element, data):
errors = []
if survey_element['variable'] not in data and survey_element['required']:
errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (str, unicode):
errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']):
errors.append("'%s' value %s is too small (length is %s must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] < int(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'float':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (float, int):
errors.append("Value %s for '%s' expected to be a numeric type." % (data[survey_element['variable']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > float(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'multiselect':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != list:
errors.append("'%s' value is expected to be a list." % survey_element['variable'])
else:
for val in data[survey_element['variable']]:
if val not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (val, survey_element['variable'],
survey_element['choices']))
elif survey_element['type'] == 'multiplechoice':
if survey_element['variable'] in data:
if data[survey_element['variable']] not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (data[survey_element['variable']],
survey_element['variable'],
survey_element['choices']))
return errors
def survey_variable_validation(self, data): def survey_variable_validation(self, data):
errors = [] errors = []
if not self.survey_enabled: if not self.survey_enabled:
@ -153,62 +217,7 @@ class SurveyJobTemplateMixin(models.Model):
if 'description' not in self.survey_spec: if 'description' not in self.survey_spec:
errors.append("'description' missing from survey spec.") errors.append("'description' missing from survey spec.")
for survey_element in self.survey_spec.get("spec", []): for survey_element in self.survey_spec.get("spec", []):
if survey_element['variable'] not in data and \ errors += self._survey_element_validation(survey_element, data)
survey_element['required']:
errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (str, unicode):
errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']):
errors.append("'%s' value %s is too small (length is %s must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] < int(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'float':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (float, int):
errors.append("Value %s for '%s' expected to be a numeric type." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > float(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'multiselect':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != list:
errors.append("'%s' value is expected to be a list." % survey_element['variable'])
else:
for val in data[survey_element['variable']]:
if val not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (val, survey_element['variable'],
survey_element['choices']))
elif survey_element['type'] == 'multiplechoice':
if survey_element['variable'] in data:
if data[survey_element['variable']] not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (data[survey_element['variable']],
survey_element['variable'],
survey_element['choices']))
return errors return errors

View File

@ -4,11 +4,12 @@
import logging import logging
from django.db import models from django.db import models
from django.core.urlresolvers import reverse
from django.core.mail.message import EmailMessage from django.core.mail.message import EmailMessage
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
# AWX
from awx.api.versioning import reverse
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.utils import encrypt_field, decrypt_field from awx.main.utils import encrypt_field, decrypt_field
from awx.main.notifications.email_backend import CustomEmailBackend from awx.main.notifications.email_backend import CustomEmailBackend
@ -56,8 +57,8 @@ class NotificationTemplate(CommonModel):
notification_configuration = JSONField(blank=False) notification_configuration = JSONField(blank=False)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:notification_template_detail', args=(self.pk,)) return reverse('api:notification_template_detail', kwargs={'pk': self.pk}, request=request)
@property @property
def notification_class(self): def notification_class(self):
@ -169,9 +170,9 @@ class Notification(CreatedModifiedModel):
editable=False, editable=False,
) )
body = JSONField(blank=True) body = JSONField(blank=True)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:notification_detail', args=(self.pk,)) return reverse('api:notification_detail', kwargs={'pk': self.pk}, request=request)
class JobNotificationMixin(object): class JobNotificationMixin(object):

View File

@ -10,12 +10,12 @@ import uuid
# Django # Django
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.timezone import now as tz_now from django.utils.timezone import now as tz_now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.fields import AutoOneToOneField, ImplicitRoleField from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.rbac import ( from awx.main.models.rbac import (
@ -65,8 +65,8 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:organization_detail', args=(self.pk,)) return reverse('api:organization_detail', kwargs={'pk': self.pk}, request=request)
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -109,8 +109,8 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
parent_role=['organization.auditor_role', 'member_role'], parent_role=['organization.auditor_role', 'member_role'],
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:team_detail', args=(self.pk,)) return reverse('api:team_detail', kwargs={'pk': self.pk}, request=request)
class Permission(CommonModelNameNotUnique): class Permission(CommonModelNameNotUnique):
@ -167,8 +167,8 @@ class Permission(CommonModelNameNotUnique):
'+adhoc' if self.run_ad_hoc_commands else '', '+adhoc' if self.run_ad_hoc_commands else '',
)) ))
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:permission_detail', args=(self.pk,)) return reverse('api:permission_detail', kwargs={'pk': self.pk}, request=request)
class Profile(CreatedModifiedModel): class Profile(CreatedModifiedModel):
@ -326,6 +326,6 @@ class AuthToken(BaseModel):
# Add get_absolute_url method to User model if not present. # Add get_absolute_url method to User model if not present.
if not hasattr(User, 'get_absolute_url'): if not hasattr(User, 'get_absolute_url'):
def user_get_absolute_url(user): def user_get_absolute_url(user, request=None):
return reverse('api:user_detail', args=(user.pk,)) return reverse('api:user_detail', kwargs={'pk': user.pk}, request=request)
User.add_to_class('get_absolute_url', user_get_absolute_url) User.add_to_class('get_absolute_url', user_get_absolute_url)

View File

@ -14,10 +14,10 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_str, smart_text from django.utils.encoding import smart_str, smart_text
from django.utils.text import slugify from django.utils.text import slugify
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.timezone import now, make_aware, get_default_timezone from django.utils.timezone import now, make_aware, get_default_timezone
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.notifications import ( from awx.main.models.notifications import (
NotificationTemplate, NotificationTemplate,
@ -401,8 +401,8 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
success=list(success_notification_templates), success=list(success_notification_templates),
any=list(any_notification_templates)) any=list(any_notification_templates))
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:project_detail', args=(self.pk,)) return reverse('api:project_detail', kwargs={'pk': self.pk}, request=request)
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin): class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
@ -470,8 +470,8 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
def result_stdout_limited(self, start_line=0, end_line=None, redact_sensitive=True): def result_stdout_limited(self, start_line=0, end_line=None, redact_sensitive=True):
return self._result_stdout_raw_limited(start_line, end_line, redact_sensitive=redact_sensitive, escape_ascii=True) return self._result_stdout_raw_limited(start_line, end_line, redact_sensitive=redact_sensitive, escape_ascii=True)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:project_update_detail', args=(self.pk,)) return reverse('api:project_update_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urlparse.urljoin(settings.TOWER_URL_BASE, "/#/scm_update/{}".format(self.pk)) return urlparse.urljoin(settings.TOWER_URL_BASE, "/#/scm_update/{}".format(self.pk))

View File

@ -9,13 +9,12 @@ import re
# Django # Django
from django.db import models, transaction, connection from django.db import models, transaction, connection
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.api.versioning import reverse
from django.contrib.auth.models import User # noqa from django.contrib.auth.models import User # noqa
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
@ -145,8 +144,8 @@ class Role(models.Model):
super(Role, self).save(*args, **kwargs) super(Role, self).save(*args, **kwargs)
self.rebuild_role_ancestor_list([self.id], []) self.rebuild_role_ancestor_list([self.id], [])
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:role_detail', args=(self.pk,)) return reverse('api:role_detail', kwargs={'pk': self.pk}, request=request)
def __contains__(self, accessor): def __contains__(self, accessor):
if type(accessor) == User: if type(accessor) == User:

View File

@ -13,10 +13,10 @@ from django.utils.timezone import now, make_aware, get_default_timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.utils import ignore_inventory_computed_fields from awx.main.utils import ignore_inventory_computed_fields
from awx.main.consumers import emit_channel_notification from awx.main.consumers import emit_channel_notification
from django.core.urlresolvers import reverse
from awx.main.fields import JSONField from awx.main.fields import JSONField
logger = logging.getLogger('awx.main.models.schedule') logger = logging.getLogger('awx.main.models.schedule')
@ -98,8 +98,8 @@ class Schedule(CommonModel):
def __unicode__(self): def __unicode__(self):
return u'%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run) return u'%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run)
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:schedule_detail', args=(self.pk,)) return reverse('api:schedule_detail', kwargs={'pk': self.pk}, request=request)
def update_computed_fields(self): def update_computed_fields(self):
future_rs = dateutil.rrule.rrulestr(self.rrule, forceset=True) future_rs = dateutil.rrule.rrulestr(self.rrule, forceset=True)

View File

@ -153,10 +153,10 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
related_name='%(class)s_labels' related_name='%(class)s_labels'
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
real_instance = self.get_real_instance() real_instance = self.get_real_instance()
if real_instance != self: if real_instance != self:
return real_instance.get_absolute_url() return real_instance.get_absolute_url(request=request)
else: else:
return '' return ''

View File

@ -7,10 +7,10 @@
# Django # Django
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
#from django import settings as tower_settings #from django import settings as tower_settings
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models import prevent_search, UnifiedJobTemplate, UnifiedJob from awx.main.models import prevent_search, UnifiedJobTemplate, UnifiedJob
from awx.main.models.notifications import ( from awx.main.models.notifications import (
NotificationTemplate, NotificationTemplate,
@ -177,8 +177,8 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:workflow_job_template_node_detail', args=(self.pk,)) return reverse('api:workflow_job_template_node_detail', kwargs={'pk': self.pk}, request=request)
def create_wfjt_node_copy(self, user, workflow_job_template=None): def create_wfjt_node_copy(self, user, workflow_job_template=None):
''' '''
@ -223,8 +223,8 @@ class WorkflowJobNode(WorkflowNodeBase):
editable=False, editable=False,
) )
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:workflow_job_node_detail', args=(self.pk,)) return reverse('api:workflow_job_node_detail', kwargs={'pk': self.pk}, request=request)
def get_job_kwargs(self): def get_job_kwargs(self):
''' '''
@ -365,8 +365,8 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
return (base_list + return (base_list +
['survey_spec', 'survey_enabled', 'organization']) ['survey_spec', 'survey_enabled', 'organization'])
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:workflow_job_template_detail', args=(self.pk,)) return reverse('api:workflow_job_template_detail', kwargs={'pk': self.pk}, request=request)
@property @property
def cache_timeout_blocked(self): def cache_timeout_blocked(self):
@ -467,8 +467,8 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
def socketio_emit_data(self): def socketio_emit_data(self):
return {} return {}
def get_absolute_url(self): def get_absolute_url(self, request=None):
return reverse('api:workflow_job_detail', args=(self.pk,)) return reverse('api:workflow_job_detail', kwargs={'pk': self.pk}, request=request)
def get_ui_url(self): def get_ui_url(self):
return urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.pk)) return urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.pk))

View File

@ -43,6 +43,7 @@ from django.core.mail import send_mail
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
# AWX # AWX
from awx.main.constants import CLOUD_PROVIDERS from awx.main.constants import CLOUD_PROVIDERS
@ -235,7 +236,11 @@ def _send_notification_templates(instance, status_str):
@task(bind=True, queue='default') @task(bind=True, queue='default')
def handle_work_success(self, result, task_actual): def handle_work_success(self, result, task_actual):
instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id']) try:
instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id'])
except ObjectDoesNotExist:
logger.warning('Missing job `{}` in success callback.'.format(task_actual['id']))
return
if not instance: if not instance:
return return

View File

@ -1,12 +1,11 @@
import mock import mock
import pytest import pytest
from awx.api.versioning import reverse
from awx.main.middleware import ActivityStreamMiddleware from awx.main.middleware import ActivityStreamMiddleware
from awx.main.models.activity_stream import ActivityStream from awx.main.models.activity_stream import ActivityStream
from awx.main.access import ActivityStreamAccess from awx.main.access import ActivityStreamAccess
from django.core.urlresolvers import reverse
def mock_feature_enabled(feature): def mock_feature_enabled(feature):
return True return True
@ -37,7 +36,7 @@ def test_basic_fields(monkeypatch, organization, get, user, settings):
activity_stream.save() activity_stream.save()
aspk = activity_stream.pk aspk = activity_stream.pk
url = reverse('api:activity_stream_detail', args=(aspk,)) url = reverse('api:activity_stream_detail', kwargs={'pk': aspk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
assert response.status_code == 200 assert response.status_code == 200
@ -64,7 +63,7 @@ def test_middleware_actor_added(monkeypatch, post, get, user, settings):
org_id = response.data['id'] org_id = response.data['id']
activity_stream = ActivityStream.objects.filter(organization__pk=org_id).first() activity_stream = ActivityStream.objects.filter(organization__pk=org_id).first()
url = reverse('api:activity_stream_detail', args=(activity_stream.pk,)) url = reverse('api:activity_stream_detail', kwargs={'pk': activity_stream.pk})
response = get(url, u) response = get(url, u)
assert response.status_code == 200 assert response.status_code == 200
@ -147,14 +146,14 @@ def test_stream_user_direct_role_updates(get, post, organization_factory):
users=['test'], users=['test'],
inventories=['inv1']) inventories=['inv1'])
url = reverse('api:user_roles_list', args=(objects.users.test.pk,)) url = reverse('api:user_roles_list', kwargs={'pk': objects.users.test.pk})
post(url, dict(id=objects.inventories.inv1.read_role.pk), objects.superusers.admin) post(url, dict(id=objects.inventories.inv1.read_role.pk), objects.superusers.admin)
activity_stream = ActivityStream.objects.filter( activity_stream = ActivityStream.objects.filter(
inventory__pk=objects.inventories.inv1.pk, inventory__pk=objects.inventories.inv1.pk,
user__pk=objects.users.test.pk, user__pk=objects.users.test.pk,
role__pk=objects.inventories.inv1.read_role.pk).first() role__pk=objects.inventories.inv1.read_role.pk).first()
url = reverse('api:activity_stream_detail', args=(activity_stream.pk,)) url = reverse('api:activity_stream_detail', kwargs={'pk': activity_stream.pk})
response = get(url, objects.users.test) response = get(url, objects.users.test)
assert response.data['object1'] == 'user' assert response.data['object1'] == 'user'

View File

@ -1,7 +1,7 @@
import mock # noqa import mock # noqa
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
""" """
@ -108,8 +108,8 @@ def test_user_post_ad_hoc_command_list_without_inventory(alice, post_adhoc, inve
@pytest.mark.django_db @pytest.mark.django_db
def test_admin_post_inventory_ad_hoc_command_list(admin, post_adhoc, inventory): def test_admin_post_inventory_ad_hoc_command_list(admin, post_adhoc, inventory):
post_adhoc(reverse('api:inventory_ad_hoc_commands_list', args=(inventory.id,)), {'inventory': None}, admin, expect=201) post_adhoc(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inventory.id}), {'inventory': None}, admin, expect=201)
post_adhoc(reverse('api:inventory_ad_hoc_commands_list', args=(inventory.id,)), {}, admin, expect=201) post_adhoc(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inventory.id}), {}, admin, expect=201)
@pytest.mark.django_db @pytest.mark.django_db
@ -121,19 +121,19 @@ def test_get_inventory_ad_hoc_command_list(admin, alice, post_adhoc, get, invent
post_adhoc(reverse('api:ad_hoc_command_list'), {'inventory': inv2.id}, admin, expect=201) post_adhoc(reverse('api:ad_hoc_command_list'), {'inventory': inv2.id}, admin, expect=201)
res = get(reverse('api:ad_hoc_command_list'), admin, expect=200) res = get(reverse('api:ad_hoc_command_list'), admin, expect=200)
assert res.data['count'] == 2 assert res.data['count'] == 2
res = get(reverse('api:inventory_ad_hoc_commands_list', args=(inv1.id,)), admin, expect=200) res = get(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inv1.id}), admin, expect=200)
assert res.data['count'] == 1 assert res.data['count'] == 1
res = get(reverse('api:inventory_ad_hoc_commands_list', args=(inv2.id,)), admin, expect=200) res = get(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inv2.id}), admin, expect=200)
assert res.data['count'] == 1 assert res.data['count'] == 1
inv1.adhoc_role.members.add(alice) inv1.adhoc_role.members.add(alice)
res = get(reverse('api:inventory_ad_hoc_commands_list', args=(inv1.id,)), alice, expect=200) res = get(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inv1.id}), alice, expect=200)
assert res.data['count'] == 1 assert res.data['count'] == 1
machine_credential.use_role.members.add(alice) machine_credential.use_role.members.add(alice)
res = get(reverse('api:inventory_ad_hoc_commands_list', args=(inv1.id,)), alice, expect=200) res = get(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inv1.id}), alice, expect=200)
assert res.data['count'] == 1 assert res.data['count'] == 1
res = get(reverse('api:inventory_ad_hoc_commands_list', args=(inv2.id,)), alice, expect=403) res = get(reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': inv2.id}), alice, expect=403)
@pytest.mark.django_db @pytest.mark.django_db

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.mark.django_db @pytest.mark.django_db
@ -10,7 +10,7 @@ def test_user_role_view_access(rando, inventory, mocker, post):
data = {"id": role_pk} data = {"id": role_pk}
mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False))
with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access): with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access):
post(url=reverse('api:user_roles_list', args=(rando.pk,)), post(url=reverse('api:user_roles_list', kwargs={'pk': rando.pk}),
data=data, user=rando, expect=403) data=data, user=rando, expect=403)
mock_access.can_attach.assert_called_once_with( mock_access.can_attach.assert_called_once_with(
inventory.admin_role, rando, 'members', data, inventory.admin_role, rando, 'members', data,
@ -25,7 +25,7 @@ def test_team_role_view_access(rando, team, inventory, mocker, post):
data = {"id": role_pk} data = {"id": role_pk}
mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False))
with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access): with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access):
post(url=reverse('api:team_roles_list', args=(team.pk,)), post(url=reverse('api:team_roles_list', kwargs={'pk': team.pk}),
data=data, user=rando, expect=403) data=data, user=rando, expect=403)
mock_access.can_attach.assert_called_once_with( mock_access.can_attach.assert_called_once_with(
inventory.admin_role, team, 'member_role.parents', data, inventory.admin_role, team, 'member_role.parents', data,
@ -40,7 +40,7 @@ def test_role_team_view_access(rando, team, inventory, mocker, post):
data = {"id": team.pk} data = {"id": team.pk}
mock_access = mocker.MagicMock(return_value=False, __name__='mocked') mock_access = mocker.MagicMock(return_value=False, __name__='mocked')
with mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access): with mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access):
post(url=reverse('api:role_teams_list', args=(role_pk,)), post(url=reverse('api:role_teams_list', kwargs={'pk': role_pk}),
data=data, user=rando, expect=403) data=data, user=rando, expect=403)
mock_access.assert_called_once_with( mock_access.assert_called_once_with(
inventory.admin_role, team, 'member_role.parents', data, inventory.admin_role, team, 'member_role.parents', data,
@ -54,7 +54,7 @@ def test_org_associate_with_junk_data(rando, admin_user, organization, post):
will turn off if the action is an association will turn off if the action is an association
""" """
user_data = {'is_system_auditor': True, 'id': rando.pk} user_data = {'is_system_auditor': True, 'id': rando.pk}
post(url=reverse('api:organization_users_list', args=(organization.pk,)), post(url=reverse('api:organization_users_list', kwargs={'pk': organization.pk}),
data=user_data, expect=204, user=admin_user) data=user_data, expect=204, user=admin_user)
# assure user is now an org member # assure user is now an org member
assert rando in organization.member_role assert rando in organization.member_role

View File

@ -1,7 +1,7 @@
import mock # noqa import mock # noqa
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
# #
@ -36,14 +36,14 @@ def test_credential_validation_error_with_bad_user(post, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_create_user_credential_via_user_credentials_list(post, get, alice): def test_create_user_credential_via_user_credentials_list(post, get, alice):
response = post(reverse('api:user_credentials_list', args=(alice.pk,)), { response = post(reverse('api:user_credentials_list', kwargs={'pk': alice.pk}), {
'user': alice.pk, 'user': alice.pk,
'name': 'Some name', 'name': 'Some name',
'username': 'someusername', 'username': 'someusername',
}, alice) }, alice)
assert response.status_code == 201 assert response.status_code == 201
response = get(reverse('api:user_credentials_list', args=(alice.pk,)), alice) response = get(reverse('api:user_credentials_list', kwargs={'pk': alice.pk}), alice)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -60,7 +60,7 @@ def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
@pytest.mark.django_db @pytest.mark.django_db
def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob): def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob):
response = post(reverse('api:user_credentials_list', args=(bob.pk,)), { response = post(reverse('api:user_credentials_list', kwargs={'pk': bob.pk}), {
'user': bob.pk, 'user': bob.pk,
'name': 'Some name', 'name': 'Some name',
'username': 'someusername' 'username': 'someusername'
@ -82,7 +82,7 @@ def test_create_team_credential(post, get, team, organization, org_admin, team_m
}, org_admin) }, org_admin)
assert response.status_code == 201 assert response.status_code == 201
response = get(reverse('api:team_credentials_list', args=(team.pk,)), team_member) response = get(reverse('api:team_credentials_list', kwargs={'pk': team.pk}), team_member)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -92,14 +92,14 @@ def test_create_team_credential(post, get, team, organization, org_admin, team_m
@pytest.mark.django_db @pytest.mark.django_db
def test_create_team_credential_via_team_credentials_list(post, get, team, org_admin, team_member): def test_create_team_credential_via_team_credentials_list(post, get, team, org_admin, team_member):
response = post(reverse('api:team_credentials_list', args=(team.pk,)), { response = post(reverse('api:team_credentials_list', kwargs={'pk': team.pk}), {
'team': team.pk, 'team': team.pk,
'name': 'Some name', 'name': 'Some name',
'username': 'someusername', 'username': 'someusername',
}, org_admin) }, org_admin)
assert response.status_code == 201 assert response.status_code == 201
response = get(reverse('api:team_credentials_list', args=(team.pk,)), team_member) response = get(reverse('api:team_credentials_list', kwargs={'pk': team.pk}), team_member)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -136,7 +136,7 @@ def test_create_team_credential_by_team_member_xfail(post, team, organization, a
def test_grant_org_credential_to_org_user_through_role_users(post, credential, organization, org_admin, org_member): def test_grant_org_credential_to_org_user_through_role_users(post, credential, organization, org_admin, org_member):
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:role_users_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_users_list', kwargs={'pk': credential.use_role.id}), {
'id': org_member.id 'id': org_member.id
}, org_admin) }, org_admin)
assert response.status_code == 204 assert response.status_code == 204
@ -146,7 +146,7 @@ def test_grant_org_credential_to_org_user_through_role_users(post, credential, o
def test_grant_org_credential_to_org_user_through_user_roles(post, credential, organization, org_admin, org_member): def test_grant_org_credential_to_org_user_through_user_roles(post, credential, organization, org_admin, org_member):
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:user_roles_list', args=(org_member.id,)), { response = post(reverse('api:user_roles_list', kwargs={'pk': org_member.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, org_admin) }, org_admin)
assert response.status_code == 204 assert response.status_code == 204
@ -156,7 +156,7 @@ def test_grant_org_credential_to_org_user_through_user_roles(post, credential, o
def test_grant_org_credential_to_non_org_user_through_role_users(post, credential, organization, org_admin, alice): def test_grant_org_credential_to_non_org_user_through_role_users(post, credential, organization, org_admin, alice):
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:role_users_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_users_list', kwargs={'pk': credential.use_role.id}), {
'id': alice.id 'id': alice.id
}, org_admin) }, org_admin)
assert response.status_code == 400 assert response.status_code == 400
@ -166,7 +166,7 @@ def test_grant_org_credential_to_non_org_user_through_role_users(post, credentia
def test_grant_org_credential_to_non_org_user_through_user_roles(post, credential, organization, org_admin, alice): def test_grant_org_credential_to_non_org_user_through_user_roles(post, credential, organization, org_admin, alice):
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:user_roles_list', args=(alice.id,)), { response = post(reverse('api:user_roles_list', kwargs={'pk': alice.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, org_admin) }, org_admin)
assert response.status_code == 400 assert response.status_code == 400
@ -176,7 +176,7 @@ def test_grant_org_credential_to_non_org_user_through_user_roles(post, credentia
def test_grant_private_credential_to_user_through_role_users(post, credential, alice, bob): def test_grant_private_credential_to_user_through_role_users(post, credential, alice, bob):
# normal users can't do this # normal users can't do this
credential.admin_role.members.add(alice) credential.admin_role.members.add(alice)
response = post(reverse('api:role_users_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_users_list', kwargs={'pk': credential.use_role.id}), {
'id': bob.id 'id': bob.id
}, alice) }, alice)
assert response.status_code == 400 assert response.status_code == 400
@ -186,7 +186,7 @@ def test_grant_private_credential_to_user_through_role_users(post, credential, a
def test_grant_private_credential_to_org_user_through_role_users(post, credential, org_admin, org_member): def test_grant_private_credential_to_org_user_through_role_users(post, credential, org_admin, org_member):
# org admins can't either # org admins can't either
credential.admin_role.members.add(org_admin) credential.admin_role.members.add(org_admin)
response = post(reverse('api:role_users_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_users_list', kwargs={'pk': credential.use_role.id}), {
'id': org_member.id 'id': org_member.id
}, org_admin) }, org_admin)
assert response.status_code == 400 assert response.status_code == 400
@ -195,7 +195,7 @@ def test_grant_private_credential_to_org_user_through_role_users(post, credentia
@pytest.mark.django_db @pytest.mark.django_db
def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob): def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob):
# but system admins can # but system admins can
response = post(reverse('api:role_users_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_users_list', kwargs={'pk': credential.use_role.id}), {
'id': bob.id 'id': bob.id
}, admin) }, admin)
assert response.status_code == 204 assert response.status_code == 204
@ -205,7 +205,7 @@ def test_sa_grant_private_credential_to_user_through_role_users(post, credential
def test_grant_private_credential_to_user_through_user_roles(post, credential, alice, bob): def test_grant_private_credential_to_user_through_user_roles(post, credential, alice, bob):
# normal users can't do this # normal users can't do this
credential.admin_role.members.add(alice) credential.admin_role.members.add(alice)
response = post(reverse('api:user_roles_list', args=(bob.id,)), { response = post(reverse('api:user_roles_list', kwargs={'pk': bob.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, alice) }, alice)
assert response.status_code == 400 assert response.status_code == 400
@ -215,7 +215,7 @@ def test_grant_private_credential_to_user_through_user_roles(post, credential, a
def test_grant_private_credential_to_org_user_through_user_roles(post, credential, org_admin, org_member): def test_grant_private_credential_to_org_user_through_user_roles(post, credential, org_admin, org_member):
# org admins can't either # org admins can't either
credential.admin_role.members.add(org_admin) credential.admin_role.members.add(org_admin)
response = post(reverse('api:user_roles_list', args=(org_member.id,)), { response = post(reverse('api:user_roles_list', kwargs={'pk': org_member.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, org_admin) }, org_admin)
assert response.status_code == 400 assert response.status_code == 400
@ -224,7 +224,7 @@ def test_grant_private_credential_to_org_user_through_user_roles(post, credentia
@pytest.mark.django_db @pytest.mark.django_db
def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob): def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob):
# but system admins can # but system admins can
response = post(reverse('api:user_roles_list', args=(bob.id,)), { response = post(reverse('api:user_roles_list', kwargs={'pk': bob.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, admin) }, admin)
assert response.status_code == 204 assert response.status_code == 204
@ -235,7 +235,7 @@ def test_grant_org_credential_to_team_through_role_teams(post, credential, organ
assert org_auditor not in credential.read_role assert org_auditor not in credential.read_role
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:role_teams_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {
'id': team.id 'id': team.id
}, org_admin) }, org_admin)
assert response.status_code == 204 assert response.status_code == 204
@ -247,7 +247,7 @@ def test_grant_org_credential_to_team_through_team_roles(post, credential, organ
assert org_auditor not in credential.read_role assert org_auditor not in credential.read_role
credential.organization = organization credential.organization = organization
credential.save() credential.save()
response = post(reverse('api:team_roles_list', args=(team.id,)), { response = post(reverse('api:team_roles_list', kwargs={'pk': team.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, org_admin) }, org_admin)
assert response.status_code == 204 assert response.status_code == 204
@ -257,7 +257,7 @@ def test_grant_org_credential_to_team_through_team_roles(post, credential, organ
@pytest.mark.django_db @pytest.mark.django_db
def test_sa_grant_private_credential_to_team_through_role_teams(post, credential, admin, team): def test_sa_grant_private_credential_to_team_through_role_teams(post, credential, admin, team):
# not even a system admin can grant a private cred to a team though # not even a system admin can grant a private cred to a team though
response = post(reverse('api:role_teams_list', args=(credential.use_role.id,)), { response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {
'id': team.id 'id': team.id
}, admin) }, admin)
assert response.status_code == 400 assert response.status_code == 400
@ -266,7 +266,7 @@ def test_sa_grant_private_credential_to_team_through_role_teams(post, credential
@pytest.mark.django_db @pytest.mark.django_db
def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team): def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team):
# not even a system admin can grant a private cred to a team though # not even a system admin can grant a private cred to a team though
response = post(reverse('api:role_teams_list', args=(team.id,)), { response = post(reverse('api:role_teams_list', kwargs={'pk': team.id}), {
'id': credential.use_role.id 'id': credential.use_role.id
}, admin) }, admin)
assert response.status_code == 400 assert response.status_code == 400
@ -305,7 +305,7 @@ def test_credential_detail(post, get, organization, org_admin):
'organization': organization.id, 'organization': organization.id,
}, org_admin) }, org_admin)
assert response.status_code == 201 assert response.status_code == 201
response = get(reverse('api:credential_detail', args=(response.data['id'],)), org_admin) response = get(reverse('api:credential_detail', kwargs={'pk': response.data['id']}), org_admin)
assert response.status_code == 200 assert response.status_code == 200
summary_fields = response.data['summary_fields'] summary_fields = response.data['summary_fields']
assert 'organization' in summary_fields assert 'organization' in summary_fields
@ -330,11 +330,11 @@ def test_list_created_org_credentials(post, get, organization, org_admin, org_me
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 0 assert response.data['count'] == 0
response = get(reverse('api:organization_credential_list', args=(organization.pk,)), org_admin) response = get(reverse('api:organization_credential_list', kwargs={'pk': organization.pk}), org_admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
response = get(reverse('api:organization_credential_list', args=(organization.pk,)), org_member) response = get(reverse('api:organization_credential_list', kwargs={'pk': organization.pk}), org_member)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 0 assert response.data['count'] == 0

View File

@ -6,11 +6,11 @@ import urlparse
import urllib import urllib
# AWX # AWX
from awx.api.versioning import reverse
from awx.main.models.fact import Fact from awx.main.models.fact import Fact
from awx.main.utils import timestamp_apiformat from awx.main.utils import timestamp_apiformat
# Django # Django
from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
@ -26,7 +26,7 @@ def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params=
hosts = hosts(host_count=host_count) hosts = hosts(host_count=host_count)
fact_scans(fact_scans=3, timestamp_epoch=epoch) fact_scans(fact_scans=3, timestamp_epoch=epoch)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True), data=get_params) response = get(url, user('admin', True), data=get_params)
return (hosts[0], response) return (hosts[0], response)
@ -37,7 +37,7 @@ def check_url(url1_full, fact_known, module):
url1 = url1_split.path url1 = url1_split.path
url1_params = urlparse.parse_qsl(url1_split.query) url1_params = urlparse.parse_qsl(url1_split.query)
url2 = reverse('api:host_fact_compare_view', args=(fact_known.host.pk,)) url2 = reverse('api:host_fact_compare_view', kwargs={'pk': fact_known.host.pk})
url2_params = [('module', module), ('datetime', timestamp_apiformat(fact_known.timestamp))] url2_params = [('module', module), ('datetime', timestamp_apiformat(fact_known.timestamp))]
assert url1 == url2 assert url1 == url2
@ -64,7 +64,7 @@ def check_system_tracking_feature_forbidden(response):
@pytest.mark.license_feature @pytest.mark.license_feature
def test_system_tracking_license_get(hosts, get, user): def test_system_tracking_license_get(hosts, get, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
check_system_tracking_feature_forbidden(response) check_system_tracking_feature_forbidden(response)
@ -75,7 +75,7 @@ def test_system_tracking_license_get(hosts, get, user):
@pytest.mark.license_feature @pytest.mark.license_feature
def test_system_tracking_license_options(hosts, options, user): def test_system_tracking_license_options(hosts, options, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = options(url, None, user('admin', True)) response = options(url, None, user('admin', True))
check_system_tracking_feature_forbidden(response) check_system_tracking_feature_forbidden(response)
@ -86,7 +86,7 @@ def test_system_tracking_license_options(hosts, options, user):
@pytest.mark.license_feature @pytest.mark.license_feature
def test_no_facts_db(hosts, get, user): def test_no_facts_db(hosts, get, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
response_expected = { response_expected = {
@ -119,7 +119,7 @@ def test_basic_options_fields(hosts, fact_scans, options, user, monkeypatch_json
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
fact_scans(fact_scans=1) fact_scans(fact_scans=1)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = options(url, None, user('admin', True), pk=hosts[0].id) response = options(url, None, user('admin', True), pk=hosts[0].id)
assert 'related' in response.data['actions']['GET'] assert 'related' in response.data['actions']['GET']
@ -228,7 +228,7 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
team_obj.member_role.members.add(user_obj) team_obj.member_role.members.add(user_obj)
url = reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk})
response = get(url, user_obj) response = get(url, user_obj)
return response return response

View File

@ -2,8 +2,8 @@ import mock
import pytest import pytest
import json import json
from awx.api.versioning import reverse
from awx.main.utils import timestamp_apiformat from awx.main.utils import timestamp_apiformat
from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
@ -27,7 +27,7 @@ def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), module_name
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
facts = fact_scans(fact_scans=1, timestamp_epoch=epoch) facts = fact_scans(fact_scans=1, timestamp_epoch=epoch)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True), data=get_params) response = get(url, user('admin', True), data=get_params)
fact_known = find_fact(facts, hosts[0].id, module_name, epoch) fact_known = find_fact(facts, hosts[0].id, module_name, epoch)
@ -44,7 +44,7 @@ def check_system_tracking_feature_forbidden(response):
@pytest.mark.license_feature @pytest.mark.license_feature
def test_system_tracking_license_get(hosts, get, user): def test_system_tracking_license_get(hosts, get, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
check_system_tracking_feature_forbidden(response) check_system_tracking_feature_forbidden(response)
@ -55,7 +55,7 @@ def test_system_tracking_license_get(hosts, get, user):
@pytest.mark.license_feature @pytest.mark.license_feature
def test_system_tracking_license_options(hosts, options, user): def test_system_tracking_license_options(hosts, options, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = options(url, None, user('admin', True)) response = options(url, None, user('admin', True))
check_system_tracking_feature_forbidden(response) check_system_tracking_feature_forbidden(response)
@ -65,7 +65,7 @@ def test_system_tracking_license_options(hosts, options, user):
@pytest.mark.django_db @pytest.mark.django_db
def test_no_fact_found(hosts, get, user): def test_no_fact_found(hosts, get, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
expected_response = { expected_response = {
@ -81,7 +81,7 @@ def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
fact_scans(fact_scans=1) fact_scans(fact_scans=1)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
assert 'related' in response.data assert 'related' in response.data
@ -95,7 +95,7 @@ def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d
assert 'name' in response.data['summary_fields']['host'] assert 'name' in response.data['summary_fields']['host']
assert 'description' in response.data['summary_fields']['host'] assert 'description' in response.data['summary_fields']['host']
assert 'host' in response.data['related'] assert 'host' in response.data['related']
assert reverse('api:host_detail', args=(hosts[0].pk,)) == response.data['related']['host'] assert reverse('api:host_detail', kwargs={'pk': hosts[0].pk}) == response.data['related']['host']
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@ -149,7 +149,7 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
team_obj.member_role.members.add(user_obj) team_obj.member_role.members.add(user_obj)
url = reverse('api:host_fact_compare_view', args=(hosts[0].pk,)) url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk})
response = get(url, user_obj) response = get(url, user_obj)
return response return response

View File

@ -2,17 +2,17 @@
# Other host tests should live here to make this test suite more complete. # Other host tests should live here to make this test suite more complete.
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.mark.django_db @pytest.mark.django_db
def test_basic_fields(hosts, fact_scans, get, user): def test_basic_fields(hosts, fact_scans, get, user):
hosts = hosts(host_count=1) hosts = hosts(host_count=1)
url = reverse('api:host_detail', args=(hosts[0].pk,)) url = reverse('api:host_detail', kwargs={'pk': hosts[0].pk})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
assert 'related' in response.data assert 'related' in response.data
assert 'fact_versions' in response.data['related'] assert 'fact_versions' in response.data['related']
assert reverse('api:host_fact_versions_list', args=(hosts[0].pk,)) == response.data['related']['fact_versions'] assert reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk}) == response.data['related']['fact_versions']

View File

@ -0,0 +1,49 @@
# TODO: As of writing this our only concern is ensuring that the fact feature is reflected in the Host endpoint.
# Other host tests should live here to make this test suite more complete.
import pytest
import urllib
from awx.api.versioning import reverse
from awx.main.models import Organization, Host, Group, Inventory
@pytest.fixture
def inventory_structure():
org = Organization.objects.create(name="org")
inv = Inventory.objects.create(name="inv", organization=org)
Host.objects.create(name="host1", inventory=inv)
Host.objects.create(name="host2", inventory=inv)
Host.objects.create(name="host3", inventory=inv)
Group.objects.create(name="g1", inventory=inv)
Group.objects.create(name="g2", inventory=inv)
Group.objects.create(name="g3", inventory=inv)
@pytest.mark.django_db
def test_q1(inventory_structure, get, user):
def evaluate_query(query, expected_hosts):
url = reverse('api:host_list')
get_params = "?host_filter=%s" % urllib.quote(query, safe='')
response = get(url + get_params, user('admin', True))
hosts = response.data['results']
assert len(expected_hosts) == len(hosts)
host_ids = [host['id'] for host in hosts]
for i, expected_host in enumerate(expected_hosts):
assert expected_host.id in host_ids
hosts = Host.objects.all()
groups = Group.objects.all()
groups[0].hosts.add(hosts[0], hosts[1])
groups[1].hosts.add(hosts[0], hosts[1], hosts[2])
query = '(name="host1" and groups__name="g1")'
evaluate_query(query, [hosts[0]])
query = '(name="host1" and groups__name="g1") or (name="host3" and groups__name="g2")'
evaluate_query(query, [hosts[0], hosts[2]])

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.mark.django_db @pytest.mark.django_db
@ -12,10 +12,10 @@ def test_inventory_source_notification_on_cloud_only(get, post, group_factory, u
not_is = g_not.inventory_source not_is = g_not.inventory_source
cloud_is.source = 'ec2' cloud_is.source = 'ec2'
cloud_is.save() cloud_is.save()
url = reverse('api:inventory_source_notification_templates_any_list', args=(cloud_is.id,)) url = reverse('api:inventory_source_notification_templates_any_list', kwargs={'pk': cloud_is.id})
response = post(url, dict(id=notification_template.id), u) response = post(url, dict(id=notification_template.id), u)
assert response.status_code == 204 assert response.status_code == 204
url = reverse('api:inventory_source_notification_templates_success_list', args=(not_is.id,)) url = reverse('api:inventory_source_notification_templates_success_list', kwargs={'pk': not_is.id})
response = post(url, dict(id=notification_template.id), u) response = post(url, dict(id=notification_template.id), u)
assert response.status_code == 400 assert response.status_code == 400
@ -32,7 +32,7 @@ def test_edit_inventory(put, inventory, alice, role_field, expected_status_code)
data = { 'organization': inventory.organization.id, 'name': 'New name', 'description': 'Hello world', } data = { 'organization': inventory.organization.id, 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(inventory, role_field).members.add(alice) getattr(inventory, role_field).members.add(alice)
put(reverse('api:inventory_detail', args=(inventory.id,)), data, alice, expect=expected_status_code) put(reverse('api:inventory_detail', kwargs={'pk': inventory.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize('order_by', ('script', '-script', 'script,pk', '-script,pk')) @pytest.mark.parametrize('order_by', ('script', '-script', 'script,pk', '-script,pk'))
@ -62,7 +62,7 @@ def test_create_inventory_group(post, inventory, alice, role_field, expected_sta
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(inventory, role_field).members.add(alice) getattr(inventory, role_field).members.add(alice)
post(reverse('api:inventory_groups_list', args=(inventory.id,)), data, alice, expect=expected_status_code) post(reverse('api:inventory_groups_list', kwargs={'pk': inventory.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -77,7 +77,7 @@ def test_create_inventory_group_child(post, group, alice, role_field, expected_s
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(group.inventory, role_field).members.add(alice) getattr(group.inventory, role_field).members.add(alice)
post(reverse('api:group_children_list', args=(group.id,)), data, alice, expect=expected_status_code) post(reverse('api:group_children_list', kwargs={'pk': group.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -92,7 +92,7 @@ def test_edit_inventory_group(put, group, alice, role_field, expected_status_cod
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(group.inventory, role_field).members.add(alice) getattr(group.inventory, role_field).members.add(alice)
put(reverse('api:group_detail', args=(group.id,)), data, alice, expect=expected_status_code) put(reverse('api:group_detail', kwargs={'pk': group.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -106,7 +106,7 @@ def test_edit_inventory_group(put, group, alice, role_field, expected_status_cod
def test_delete_inventory_group(delete, group, alice, role_field, expected_status_code): def test_delete_inventory_group(delete, group, alice, role_field, expected_status_code):
if role_field: if role_field:
getattr(group.inventory, role_field).members.add(alice) getattr(group.inventory, role_field).members.add(alice)
delete(reverse('api:group_detail', args=(group.id,)), alice, expect=expected_status_code) delete(reverse('api:group_detail', kwargs={'pk': group.id}), alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -121,7 +121,7 @@ def test_create_inventory_host(post, inventory, alice, role_field, expected_stat
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(inventory, role_field).members.add(alice) getattr(inventory, role_field).members.add(alice)
post(reverse('api:inventory_hosts_list', args=(inventory.id,)), data, alice, expect=expected_status_code) post(reverse('api:inventory_hosts_list', kwargs={'pk': inventory.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -136,7 +136,7 @@ def test_create_inventory_group_host(post, group, alice, role_field, expected_st
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(group.inventory, role_field).members.add(alice) getattr(group.inventory, role_field).members.add(alice)
post(reverse('api:group_hosts_list', args=(group.id,)), data, alice, expect=expected_status_code) post(reverse('api:group_hosts_list', kwargs={'pk': group.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -151,7 +151,7 @@ def test_edit_inventory_host(put, host, alice, role_field, expected_status_code)
data = { 'name': 'New name', 'description': 'Hello world', } data = { 'name': 'New name', 'description': 'Hello world', }
if role_field: if role_field:
getattr(host.inventory, role_field).members.add(alice) getattr(host.inventory, role_field).members.add(alice)
put(reverse('api:host_detail', args=(host.id,)), data, alice, expect=expected_status_code) put(reverse('api:host_detail', kwargs={'pk': host.id}), data, alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -165,7 +165,7 @@ def test_edit_inventory_host(put, host, alice, role_field, expected_status_code)
def test_delete_inventory_host(delete, host, alice, role_field, expected_status_code): def test_delete_inventory_host(delete, host, alice, role_field, expected_status_code):
if role_field: if role_field:
getattr(host.inventory, role_field).members.add(alice) getattr(host.inventory, role_field).members.add(alice)
delete(reverse('api:host_detail', args=(host.id,)), alice, expect=expected_status_code) delete(reverse('api:host_detail', kwargs={'pk': host.id}), alice, expect=expected_status_code)
@pytest.mark.parametrize("role_field,expected_status_code", [ @pytest.mark.parametrize("role_field,expected_status_code", [
@ -179,4 +179,4 @@ def test_delete_inventory_host(delete, host, alice, role_field, expected_status_
def test_inventory_source_update(post, inventory_source, alice, role_field, expected_status_code): def test_inventory_source_update(post, inventory_source, alice, role_field, expected_status_code):
if role_field: if role_field:
getattr(inventory_source.group.inventory, role_field).members.add(alice) getattr(inventory_source.group.inventory, role_field).members.add(alice)
post(reverse('api:inventory_source_update_view', args=(inventory_source.id,)), {}, alice, expect=expected_status_code) post(reverse('api:inventory_source_update_view', kwargs={'pk': inventory_source.id}), {}, alice, expect=expected_status_code)

View File

@ -6,7 +6,7 @@ from awx.main.models.credential import Credential
from awx.main.models.inventory import Inventory from awx.main.models.inventory import Inventory
from awx.main.models.jobs import Job, JobTemplate from awx.main.models.jobs import Job, JobTemplate
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.fixture @pytest.fixture
@ -85,7 +85,7 @@ def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, ad
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
runtime_data, admin_user, expect=201) runtime_data, admin_user, expect=201)
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == ({'extra_vars':{}},) assert JobTemplate.create_unified_job.call_args == ({'extra_vars':{}},)
@ -116,7 +116,7 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admi
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
runtime_data, admin_user, expect=201) runtime_data, admin_user, expect=201)
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == (runtime_data,) assert JobTemplate.create_unified_job.call_args == (runtime_data,)
@ -136,7 +136,7 @@ def test_job_accept_null_tags(job_template_prompts, post, admin_user, mocker):
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
post(reverse('api:job_template_launch', args=[job_template.pk]), post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}),
{'job_tags': '', 'skip_tags': ''}, admin_user, expect=201) {'job_tags': '', 'skip_tags': ''}, admin_user, expect=201)
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == ({'job_tags':'', 'skip_tags':''},) assert JobTemplate.create_unified_job.call_args == ({'job_tags':'', 'skip_tags':''},)
@ -162,7 +162,7 @@ def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null,
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}),
runtime_data, rando, expect=201) runtime_data, rando, expect=201)
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
assert JobTemplate.create_unified_job.call_args == (runtime_data,) assert JobTemplate.create_unified_job.call_args == (runtime_data,)
@ -178,7 +178,7 @@ def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, po
job_template = job_template_prompts(True) job_template = job_template_prompts(True)
response = post( response = post(
reverse('api:job_template_launch', args=[job_template.pk]), reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(job_type='foobicate', # foobicate is not a valid job type dict(job_type='foobicate', # foobicate is not a valid job type
inventory=87865, credential=48474), admin_user, expect=400) inventory=87865, credential=48474), admin_user, expect=400)
@ -193,7 +193,7 @@ def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_promp
job_template = job_template_prompts(True) job_template = job_template_prompts(True)
response = post( response = post(
reverse('api:job_template_launch', args=[job_template.pk]), reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(extra_vars='{"unbalanced brackets":'), admin_user, expect=400) dict(extra_vars='{"unbalanced brackets":'), admin_user, expect=400)
assert 'extra_vars' in response.data assert 'extra_vars' in response.data
@ -207,7 +207,7 @@ def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, admin_user
deploy_jobtemplate.save() deploy_jobtemplate.save()
response = post(reverse('api:job_template_launch', response = post(reverse('api:job_template_launch',
args=[deploy_jobtemplate.pk]), {}, admin_user, expect=400) kwargs={'pk': deploy_jobtemplate.pk}), {}, admin_user, expect=400)
assert response.data['inventory'] == ["Job Template 'inventory' is missing or undefined."] assert response.data['inventory'] == ["Job Template 'inventory' is missing or undefined."]
@ -219,7 +219,7 @@ def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime
job_template.execute_role.members.add(rando) job_template.execute_role.members.add(rando)
# Assure that giving an inventory without access to the inventory blocks the launch # Assure that giving an inventory without access to the inventory blocks the launch
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(inventory=runtime_data['inventory']), rando, expect=403) dict(inventory=runtime_data['inventory']), rando, expect=403)
assert response.data['detail'] == u'You do not have permission to perform this action.' assert response.data['detail'] == u'You do not have permission to perform this action.'
@ -232,7 +232,7 @@ def test_job_launch_fails_without_credential_access(job_template_prompts, runtim
job_template.execute_role.members.add(rando) job_template.execute_role.members.add(rando)
# Assure that giving a credential without access blocks the launch # Assure that giving a credential without access blocks the launch
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(credential=runtime_data['credential']), rando, expect=403) dict(credential=runtime_data['credential']), rando, expect=403)
assert response.data['detail'] == u'You do not have permission to perform this action.' assert response.data['detail'] == u'You do not have permission to perform this action.'
@ -244,7 +244,7 @@ def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user):
job_template = job_template_prompts(True) job_template = job_template_prompts(True)
# Assure that changing the type of a scan job blocks the launch # Assure that changing the type of a scan job blocks the launch
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(job_type='scan'), admin_user, expect=400) dict(job_type='scan'), admin_user, expect=400)
assert 'job_type' in response.data assert 'job_type' in response.data
@ -255,7 +255,7 @@ def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user):
def test_job_block_scan_job_inv_change(mocker, bad_scan_JT, runtime_data, post, admin_user): def test_job_block_scan_job_inv_change(mocker, bad_scan_JT, runtime_data, post, admin_user):
# Assure that giving a new inventory for a scan job blocks the launch # Assure that giving a new inventory for a scan job blocks the launch
with mocker.patch('awx.main.access.BaseAccess.check_license'): with mocker.patch('awx.main.access.BaseAccess.check_license'):
response = post(reverse('api:job_template_launch', args=[bad_scan_JT.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk': bad_scan_JT.pk}),
dict(inventory=runtime_data['inventory']), admin_user, dict(inventory=runtime_data['inventory']), admin_user,
expect=400) expect=400)
@ -333,7 +333,7 @@ def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job
with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
response = post( response = post(
reverse('api:job_template_launch', args=[job_template.pk]), reverse('api:job_template_launch', kwargs={'pk':job_template.pk}),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}),
admin_user, expect=201) admin_user, expect=201)
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
@ -362,7 +362,7 @@ def test_callback_accept_prompted_extra_var(mocker, survey_spec_factory, job_tem
with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
post( post(
reverse('api:job_template_callback', args=[job_template.pk]), reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
admin_user, expect=201, format='json') admin_user, expect=201, format='json')
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called
@ -387,7 +387,7 @@ def test_callback_ignore_unprompted_extra_var(mocker, survey_spec_factory, job_t
with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
post( post(
reverse('api:job_template_callback', args=[job_template.pk]), reverse('api:job_template_callback', kwargs={'pk':job_template.pk}),
dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
admin_user, expect=201, format='json') admin_user, expect=201, format='json')
assert JobTemplate.create_unified_job.called assert JobTemplate.create_unified_job.called

View File

@ -2,11 +2,11 @@ import pytest
# AWX # AWX
from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer
from awx.api.versioning import reverse
from awx.main.models.jobs import Job from awx.main.models.jobs import Job
from awx.main.migrations import _save_password_keys as save_password_keys from awx.main.migrations import _save_password_keys as save_password_keys
# Django # Django
from django.core.urlresolvers import reverse
from django.apps import apps from django.apps import apps
@ -56,7 +56,7 @@ def test_edit_sensitive_fields(patch, job_template_factory, alice, grant_project
if grant_inventory: if grant_inventory:
objs.inventory.use_role.members.add(alice) objs.inventory.use_role.members.add(alice)
patch(reverse('api:job_template_detail', args=(objs.job_template.id,)), { patch(reverse('api:job_template_detail', kwargs={'pk': objs.job_template.id}), {
'name': 'Some name', 'name': 'Some name',
'project': objs.project.id, 'project': objs.project.id,
'credential': objs.credential.id, 'credential': objs.credential.id,
@ -72,7 +72,7 @@ def test_reject_dict_extra_vars_patch(patch, job_template_factory, admin_user):
jt = job_template_factory( jt = job_template_factory(
'jt', organization='org1', project='prj', inventory='inv', credential='cred' 'jt', organization='org1', project='prj', inventory='inv', credential='cred'
).job_template ).job_template
patch(reverse('api:job_template_detail', args=(jt.id,)), patch(reverse('api:job_template_detail', kwargs={'pk': jt.id}),
{'extra_vars': {'foo': 5}}, admin_user, expect=400) {'extra_vars': {'foo': 5}}, admin_user, expect=400)
@ -84,12 +84,12 @@ def test_edit_playbook(patch, job_template_factory, alice):
objs.credential.use_role.members.add(alice) objs.credential.use_role.members.add(alice)
objs.inventory.use_role.members.add(alice) objs.inventory.use_role.members.add(alice)
patch(reverse('api:job_template_detail', args=(objs.job_template.id,)), { patch(reverse('api:job_template_detail', kwargs={'pk': objs.job_template.id}), {
'playbook': 'alt-helloworld.yml', 'playbook': 'alt-helloworld.yml',
}, alice, expect=200) }, alice, expect=200)
objs.inventory.use_role.members.remove(alice) objs.inventory.use_role.members.remove(alice)
patch(reverse('api:job_template_detail', args=(objs.job_template.id,)), { patch(reverse('api:job_template_detail', kwargs={'pk': objs.job_template.id}), {
'playbook': 'helloworld.yml', 'playbook': 'helloworld.yml',
}, alice, expect=403) }, alice, expect=403)
@ -101,7 +101,7 @@ def test_invalid_json_body(patch, job_template_factory, alice, json_body):
objs = job_template_factory('jt', organization='org1') objs = job_template_factory('jt', organization='org1')
objs.job_template.admin_role.members.add(alice) objs.job_template.admin_role.members.add(alice)
resp = patch( resp = patch(
reverse('api:job_template_detail', args=(objs.job_template.id,)), reverse('api:job_template_detail', kwargs={'pk': objs.job_template.id}),
json_body, json_body,
alice, alice,
expect=400 expect=400
@ -117,7 +117,7 @@ def test_edit_nonsenstive(patch, job_template_factory, alice):
jt = objs.job_template jt = objs.job_template
jt.admin_role.members.add(alice) jt.admin_role.members.add(alice)
res = patch(reverse('api:job_template_detail', args=(jt.id,)), { res = patch(reverse('api:job_template_detail', kwargs={'pk': jt.id}), {
'name': 'updated', 'name': 'updated',
'description': 'bar', 'description': 'bar',
'forks': 14, 'forks': 14,
@ -157,7 +157,7 @@ def test_job_template_role_user(post, organization_factory, job_template_factory
inventory='test_inv', inventory='test_inv',
project='test_proj') project='test_proj')
url = reverse('api:user_roles_list', args=(objects.users.test.pk,)) url = reverse('api:user_roles_list', kwargs={'pk': objects.users.test.pk})
response = post(url, dict(id=jt_objects.job_template.execute_role.pk), objects.superusers.admin) response = post(url, dict(id=jt_objects.job_template.execute_role.pk), objects.superusers.admin)
assert response.status_code == 204 assert response.status_code == 204
@ -168,12 +168,12 @@ def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
jt_copy_edit.admin_role.members.add(rando) jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save() jt_copy_edit.save()
get_response = get(reverse('api:job_template_detail', args=[jt_copy_edit.pk]), user=rando) get_response = get(reverse('api:job_template_detail', kwargs={'pk':jt_copy_edit.pk}), user=rando)
assert get_response.status_code == 200 assert get_response.status_code == 200
post_data = get_response.data post_data = get_response.data
post_data['name'] = '%s @ 12:19:47 pm' % post_data['name'] post_data['name'] = '%s @ 12:19:47 pm' % post_data['name']
post_response = post(reverse('api:job_template_list', args=[]), user=rando, data=post_data) post_response = post(reverse('api:job_template_list'), user=rando, data=post_data)
assert post_response.status_code == 403 assert post_response.status_code == 403
@ -244,7 +244,7 @@ def test_disallow_template_delete_on_running_job(job_template_factory, delete, a
inventory='i', inventory='i',
organization='o') organization='o')
objects.job_template.create_unified_job() objects.job_template.create_unified_job()
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user) delete_response = delete(reverse('api:job_template_detail', kwargs={'pk': objects.job_template.pk}), user=admin_user)
assert delete_response.status_code == 409 assert delete_response.status_code == 409

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.fixture @pytest.fixture
@ -70,7 +70,7 @@ def test_org_counts_detail_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser # Check that all types of resources are counted by a superuser
external_admin = user('admin', True) external_admin = user('admin', True)
response = get(reverse('api:organization_detail', response = get(reverse('api:organization_detail',
args=[resourced_organization.pk]), external_admin) kwargs={'pk': resourced_organization.pk}), external_admin)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['summary_fields']['related_field_counts'] counts = response.data['summary_fields']['related_field_counts']
@ -82,7 +82,7 @@ def test_org_counts_detail_member(resourced_organization, user, get):
# Check that a non-admin org member can only see users / admin in detail view # Check that a non-admin org member can only see users / admin in detail view
member_user = resourced_organization.member_role.members.get(username='org-member 1') member_user = resourced_organization.member_role.members.get(username='org-member 1')
response = get(reverse('api:organization_detail', response = get(reverse('api:organization_detail',
args=[resourced_organization.pk]), member_user) kwargs={'pk': resourced_organization.pk}), member_user)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['summary_fields']['related_field_counts'] counts = response.data['summary_fields']['related_field_counts']
@ -100,7 +100,7 @@ def test_org_counts_detail_member(resourced_organization, user, get):
def test_org_counts_list_admin(resourced_organization, user, get): def test_org_counts_list_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser # Check that all types of resources are counted by a superuser
external_admin = user('admin', True) external_admin = user('admin', True)
response = get(reverse('api:organization_list', args=[]), external_admin) response = get(reverse('api:organization_list'), external_admin)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['results'][0]['summary_fields']['related_field_counts'] counts = response.data['results'][0]['summary_fields']['related_field_counts']
@ -112,7 +112,7 @@ def test_org_counts_list_member(resourced_organization, user, get):
# Check that a non-admin user can only see the full project and # Check that a non-admin user can only see the full project and
# user count, consistent with the RBAC rules # user count, consistent with the RBAC rules
member_user = resourced_organization.member_role.members.get(username='org-member 1') member_user = resourced_organization.member_role.members.get(username='org-member 1')
response = get(reverse('api:organization_list', args=[]), member_user) response = get(reverse('api:organization_list'), member_user)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['results'][0]['summary_fields']['related_field_counts'] counts = response.data['results'][0]['summary_fields']['related_field_counts']
@ -131,7 +131,7 @@ def test_org_counts_list_member(resourced_organization, user, get):
def test_new_org_zero_counts(user, post): def test_new_org_zero_counts(user, post):
# Check that a POST to the organization list endpoint returns # Check that a POST to the organization list endpoint returns
# correct counts, including the new record # correct counts, including the new record
org_list_url = reverse('api:organization_list', args=[]) org_list_url = reverse('api:organization_list')
post_response = post(url=org_list_url, data={'name': 'test organization', post_response = post(url=org_list_url, data={'name': 'test organization',
'description': ''}, user=user('admin', True)) 'description': ''}, user=user('admin', True))
assert post_response.status_code == 201 assert post_response.status_code == 201
@ -146,7 +146,7 @@ def test_two_organizations(resourced_organization, organizations, user, get):
# Check correct results for two organizations are returned # Check correct results for two organizations are returned
external_admin = user('admin', True) external_admin = user('admin', True)
organization_zero = organizations(1)[0] organization_zero = organizations(1)[0]
response = get(reverse('api:organization_list', args=[]), external_admin) response = get(reverse('api:organization_list'), external_admin)
assert response.status_code == 200 assert response.status_code == 200
org_id_full = resourced_organization.id org_id_full = resourced_organization.id
@ -171,12 +171,12 @@ def test_scan_JT_counted(resourced_organization, user, get):
counts_dict['job_templates'] += 1 counts_dict['job_templates'] += 1
# Test list view # Test list view
list_response = get(reverse('api:organization_list', args=[]), admin_user) list_response = get(reverse('api:organization_list'), admin_user)
assert list_response.status_code == 200 assert list_response.status_code == 200
assert list_response.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict assert list_response.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict
# Test detail view # Test detail view
detail_response = get(reverse('api:organization_detail', args=[resourced_organization.pk]), admin_user) detail_response = get(reverse('api:organization_detail', kwargs={'pk': resourced_organization.pk}), admin_user)
assert detail_response.status_code == 200 assert detail_response.status_code == 200
assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict
@ -194,12 +194,12 @@ def test_JT_not_double_counted(resourced_organization, user, get):
counts_dict['job_templates'] += 1 counts_dict['job_templates'] += 1
# Test list view # Test list view
list_response = get(reverse('api:organization_list', args=[]), admin_user) list_response = get(reverse('api:organization_list'), admin_user)
assert list_response.status_code == 200 assert list_response.status_code == 200
assert list_response.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict assert list_response.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict
# Test detail view # Test detail view
detail_response = get(reverse('api:organization_detail', args=[resourced_organization.pk]), admin_user) detail_response = get(reverse('api:organization_detail', kwargs={'pk': resourced_organization.pk}), admin_user)
assert detail_response.status_code == 200 assert detail_response.status_code == 200
assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict
@ -220,7 +220,7 @@ def test_JT_associated_with_project(organizations, project, user, get):
inventory=unrelated_inv, inventory=unrelated_inv,
playbook="test_playbook.yml") playbook="test_playbook.yml")
response = get(reverse('api:organization_list', args=[]), external_admin) response = get(reverse('api:organization_list'), external_admin)
assert response.status_code == 200 assert response.status_code == 200
org_id = organization.id org_id = organization.id

View File

@ -7,7 +7,7 @@ import mock
# Django # Django
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
# AWX # AWX
from awx.main.models import * # noqa from awx.main.models import * # noqa
@ -29,10 +29,10 @@ def test_organization_list_access_tests(options, head, get, admin, alice):
@pytest.mark.django_db @pytest.mark.django_db
def test_organization_access_tests(organization, get, admin, alice, bob): def test_organization_access_tests(organization, get, admin, alice, bob):
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
get(reverse('api:organization_detail', args=(organization.id,)), user=admin, expect=200) get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=admin, expect=200)
get(reverse('api:organization_detail', args=(organization.id,)), user=alice, expect=200) get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=200)
get(reverse('api:organization_detail', args=(organization.id,)), user=bob, expect=403) get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=bob, expect=403)
get(reverse('api:organization_detail', args=(organization.id,)), user=None, expect=401) get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=None, expect=401)
@pytest.mark.django_db @pytest.mark.django_db
@ -68,9 +68,9 @@ def test_organization_project_list(organization, project_factory, get, alice, bo
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
organization.member_role.members.add(bob) organization.member_role.members.add(bob)
prj1.use_role.members.add(bob) prj1.use_role.members.add(bob)
assert get(reverse('api:organization_projects_list', args=(organization.id,)), user=alice).data['count'] == 2 assert get(reverse('api:organization_projects_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 2
assert get(reverse('api:organization_projects_list', args=(organization.id,)), user=bob).data['count'] == 1 assert get(reverse('api:organization_projects_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1
assert get(reverse('api:organization_projects_list', args=(organization.id,)), user=rando).status_code == 403 assert get(reverse('api:organization_projects_list', kwargs={'pk': organization.id}), user=rando).status_code == 403
@pytest.mark.django_db @pytest.mark.django_db
@ -78,12 +78,12 @@ def test_organization_user_list(organization, get, admin, alice, bob):
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
organization.member_role.members.add(bob) organization.member_role.members.add(bob)
assert get(reverse('api:organization_users_list', args=(organization.id,)), user=admin).data['count'] == 2 assert get(reverse('api:organization_users_list', kwargs={'pk': organization.id}), user=admin).data['count'] == 2
assert get(reverse('api:organization_users_list', args=(organization.id,)), user=alice).data['count'] == 2 assert get(reverse('api:organization_users_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 2
assert get(reverse('api:organization_users_list', args=(organization.id,)), user=bob).data['count'] == 2 assert get(reverse('api:organization_users_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 2
assert get(reverse('api:organization_admins_list', args=(organization.id,)), user=admin).data['count'] == 1 assert get(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), user=admin).data['count'] == 1
assert get(reverse('api:organization_admins_list', args=(organization.id,)), user=alice).data['count'] == 1 assert get(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 1
assert get(reverse('api:organization_admins_list', args=(organization.id,)), user=bob).data['count'] == 1 assert get(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1
@pytest.mark.django_db @pytest.mark.django_db
@ -93,9 +93,9 @@ def test_organization_inventory_list(organization, inventory_factory, get, alice
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
organization.member_role.members.add(bob) organization.member_role.members.add(bob)
inv1.use_role.members.add(bob) inv1.use_role.members.add(bob)
assert get(reverse('api:organization_inventories_list', args=(organization.id,)), user=alice).data['count'] == 2 assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 2
assert get(reverse('api:organization_inventories_list', args=(organization.id,)), user=bob).data['count'] == 1 assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1
get(reverse('api:organization_inventories_list', args=(organization.id,)), user=rando, expect=403) get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=rando, expect=403)
@pytest.mark.django_db @pytest.mark.django_db
@ -123,25 +123,25 @@ def test_create_organization_xfail(post, alice):
@pytest.mark.django_db @pytest.mark.django_db
def test_add_user_to_organization(post, organization, alice, bob): def test_add_user_to_organization(post, organization, alice, bob):
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
post(reverse('api:organization_users_list', args=(organization.id,)), {'id': bob.id}, user=alice, expect=204) post(reverse('api:organization_users_list', kwargs={'pk': organization.id}), {'id': bob.id}, user=alice, expect=204)
assert bob in organization.member_role assert bob in organization.member_role
post(reverse('api:organization_users_list', args=(organization.id,)), {'id': bob.id, 'disassociate': True} , user=alice, expect=204) post(reverse('api:organization_users_list', kwargs={'pk': organization.id}), {'id': bob.id, 'disassociate': True} , user=alice, expect=204)
assert bob not in organization.member_role assert bob not in organization.member_role
@pytest.mark.django_db @pytest.mark.django_db
def test_add_user_to_organization_xfail(post, organization, alice, bob): def test_add_user_to_organization_xfail(post, organization, alice, bob):
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
post(reverse('api:organization_users_list', args=(organization.id,)), {'id': bob.id}, user=alice, expect=403) post(reverse('api:organization_users_list', kwargs={'pk': organization.id}), {'id': bob.id}, user=alice, expect=403)
@pytest.mark.django_db @pytest.mark.django_db
def test_add_admin_to_organization(post, organization, alice, bob): def test_add_admin_to_organization(post, organization, alice, bob):
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
post(reverse('api:organization_admins_list', args=(organization.id,)), {'id': bob.id}, user=alice, expect=204) post(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), {'id': bob.id}, user=alice, expect=204)
assert bob in organization.admin_role assert bob in organization.admin_role
assert bob in organization.member_role assert bob in organization.member_role
post(reverse('api:organization_admins_list', args=(organization.id,)), {'id': bob.id, 'disassociate': True} , user=alice, expect=204) post(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), {'id': bob.id, 'disassociate': True} , user=alice, expect=204)
assert bob not in organization.admin_role assert bob not in organization.admin_role
assert bob not in organization.member_role assert bob not in organization.member_role
@ -149,42 +149,42 @@ def test_add_admin_to_organization(post, organization, alice, bob):
@pytest.mark.django_db @pytest.mark.django_db
def test_add_admin_to_organization_xfail(post, organization, alice, bob): def test_add_admin_to_organization_xfail(post, organization, alice, bob):
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
post(reverse('api:organization_admins_list', args=(organization.id,)), {'id': bob.id}, user=alice, expect=403) post(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), {'id': bob.id}, user=alice, expect=403)
@pytest.mark.django_db @pytest.mark.django_db
def test_update_organization(get, put, organization, alice, bob): def test_update_organization(get, put, organization, alice, bob):
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
data = get(reverse('api:organization_detail', args=(organization.id,)), user=alice, expect=200).data data = get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=200).data
data['description'] = 'hi' data['description'] = 'hi'
put(reverse('api:organization_detail', args=(organization.id,)), data, user=alice, expect=200) put(reverse('api:organization_detail', kwargs={'pk': organization.id}), data, user=alice, expect=200)
organization.refresh_from_db() organization.refresh_from_db()
assert organization.description == 'hi' assert organization.description == 'hi'
data['description'] = 'bye' data['description'] = 'bye'
put(reverse('api:organization_detail', args=(organization.id,)), data, user=bob, expect=403) put(reverse('api:organization_detail', kwargs={'pk': organization.id}), data, user=bob, expect=403)
@pytest.mark.django_db @pytest.mark.django_db
@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True)
def test_delete_organization(delete, organization, admin): def test_delete_organization(delete, organization, admin):
delete(reverse('api:organization_detail', args=(organization.id,)), user=admin, expect=204) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=admin, expect=204)
@pytest.mark.django_db @pytest.mark.django_db
@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True)
def test_delete_organization2(delete, organization, alice): def test_delete_organization2(delete, organization, alice):
organization.admin_role.members.add(alice) organization.admin_role.members.add(alice)
delete(reverse('api:organization_detail', args=(organization.id,)), user=alice, expect=204) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=204)
@pytest.mark.django_db @pytest.mark.django_db
@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True)
def test_delete_organization_xfail1(delete, organization, alice): def test_delete_organization_xfail1(delete, organization, alice):
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
delete(reverse('api:organization_detail', args=(organization.id,)), user=alice, expect=403) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=403)
@pytest.mark.django_db @pytest.mark.django_db
@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True)
def test_delete_organization_xfail2(delete, organization): def test_delete_organization_xfail2(delete, organization):
delete(reverse('api:organization_detail', args=(organization.id,)), user=None, expect=401) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=None, expect=401)

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
from django.test.client import RequestFactory from django.test.client import RequestFactory
from awx.main.models import Role, Group, UnifiedJobTemplate, JobTemplate from awx.main.models import Role, Group, UnifiedJobTemplate, JobTemplate
@ -30,17 +30,17 @@ class TestOptionsRBAC:
def test_inventory_group_host_can_add(self, inventory, alice, options): def test_inventory_group_host_can_add(self, inventory, alice, options):
inventory.admin_role.members.add(alice) inventory.admin_role.members.add(alice)
response = options(reverse('api:inventory_hosts_list', args=[inventory.pk]), alice) response = options(reverse('api:inventory_hosts_list', kwargs={'pk': inventory.pk}), alice)
assert 'POST' in response.data['actions'] assert 'POST' in response.data['actions']
response = options(reverse('api:inventory_groups_list', args=[inventory.pk]), alice) response = options(reverse('api:inventory_groups_list', kwargs={'pk': inventory.pk}), alice)
assert 'POST' in response.data['actions'] assert 'POST' in response.data['actions']
def test_inventory_group_host_can_not_add(self, inventory, bob, options): def test_inventory_group_host_can_not_add(self, inventory, bob, options):
inventory.read_role.members.add(bob) inventory.read_role.members.add(bob)
response = options(reverse('api:inventory_hosts_list', args=[inventory.pk]), bob) response = options(reverse('api:inventory_hosts_list', kwargs={'pk': inventory.pk}), bob)
assert 'POST' not in response.data['actions'] assert 'POST' not in response.data['actions']
response = options(reverse('api:inventory_groups_list', args=[inventory.pk]), bob) response = options(reverse('api:inventory_groups_list', kwargs={'pk': inventory.pk}), bob)
assert 'POST' not in response.data['actions'] assert 'POST' not in response.data['actions']
def test_user_list_can_add(self, org_member, org_admin, options): def test_user_list_can_add(self, org_member, org_admin, options):
@ -192,7 +192,7 @@ class TestAccessListCapabilities:
inventory.admin_role.members.add(rando) inventory.admin_role.members.add(rando)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method): with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), rando) response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), rando)
mock_access_method.assert_called_once_with(inventory.admin_role, rando, 'members', **self.extra_kwargs) mock_access_method.assert_called_once_with(inventory.admin_role, rando, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data) self._assert_one_in_list(response.data)
@ -202,7 +202,7 @@ class TestAccessListCapabilities:
def test_access_list_indirect_access_capability( def test_access_list_indirect_access_capability(
self, inventory, organization, org_admin, get, mocker, mock_access_method): self, inventory, organization, org_admin, get, mocker, mock_access_method):
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method): with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), org_admin) response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), org_admin)
mock_access_method.assert_called_once_with(organization.admin_role, org_admin, 'members', **self.extra_kwargs) mock_access_method.assert_called_once_with(organization.admin_role, org_admin, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data, sublist='indirect_access') self._assert_one_in_list(response.data, sublist='indirect_access')
@ -214,7 +214,7 @@ class TestAccessListCapabilities:
team.member_role.children.add(inventory.admin_role) team.member_role.children.add(inventory.admin_role)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method): with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:inventory_access_list', args=(inventory.id,)), team_member) response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), team_member)
mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', **self.extra_kwargs) mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', **self.extra_kwargs)
self._assert_one_in_list(response.data) self._assert_one_in_list(response.data)
@ -223,7 +223,7 @@ class TestAccessListCapabilities:
def test_user_access_list_direct_access_capability(self, rando, get): def test_user_access_list_direct_access_capability(self, rando, get):
"When a user views their own access list, they cannot unattach their admin role" "When a user views their own access list, they cannot unattach their admin role"
response = get(reverse('api:user_access_list', args=(rando.id,)), rando) response = get(reverse('api:user_access_list', kwargs={'pk': rando.id}), rando)
direct_access_list = response.data['results'][0]['summary_fields']['direct_access'] direct_access_list = response.data['results'][0]['summary_fields']['direct_access']
assert not direct_access_list[0]['role']['user_capabilities']['unattach'] assert not direct_access_list[0]['role']['user_capabilities']['unattach']
@ -233,7 +233,7 @@ def test_team_roles_unattach(mocker, team, team_member, inventory, mock_access_m
team.member_role.children.add(inventory.admin_role) team.member_role.children.add(inventory.admin_role)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method): with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:team_roles_list', args=(team.id,)), team_member) response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member)
# Did we assess whether team_member can remove team's permission to the inventory? # Did we assess whether team_member can remove team's permission to the inventory?
mock_access_method.assert_called_once_with( mock_access_method.assert_called_once_with(
@ -248,7 +248,7 @@ def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_metho
organization.member_role.members.add(bob) organization.member_role.members.add(bob)
with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method): with mocker.patch.object(access_registry[Role][0], 'can_unattach', mock_access_method):
response = get(reverse('api:user_roles_list', args=(alice.id,)), bob) response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob)
# Did we assess whether bob can remove alice's permission to the inventory? # Did we assess whether bob can remove alice's permission to the inventory?
mock_access_method.assert_called_once_with( mock_access_method.assert_called_once_with(
@ -259,7 +259,7 @@ def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_metho
@pytest.mark.django_db @pytest.mark.django_db
def test_team_roles_unattach_functional(team, team_member, inventory, get): def test_team_roles_unattach_functional(team, team_member, inventory, get):
team.member_role.children.add(inventory.admin_role) team.member_role.children.add(inventory.admin_role)
response = get(reverse('api:team_roles_list', args=(team.id,)), team_member) response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member)
# Team member should be able to remove access to inventory, becauase # Team member should be able to remove access to inventory, becauase
# the inventory admin_role grants that ability # the inventory admin_role grants that ability
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
@ -269,7 +269,7 @@ def test_team_roles_unattach_functional(team, team_member, inventory, get):
def test_user_roles_unattach_functional(organization, alice, bob, get): def test_user_roles_unattach_functional(organization, alice, bob, get):
organization.member_role.members.add(alice) organization.member_role.members.add(alice)
organization.member_role.members.add(bob) organization.member_role.members.add(bob)
response = get(reverse('api:user_roles_list', args=(alice.id,)), bob) response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob)
# Org members cannot revoke the membership of other members # Org members cannot revoke the membership of other members
assert not response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] assert not response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
@ -336,7 +336,7 @@ def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_c
@pytest.mark.django_db @pytest.mark.django_db
def test_manual_projects_no_update(project, get, admin_user): def test_manual_projects_no_update(project, get, admin_user):
response = get(reverse('api:project_detail', args=[project.pk]), admin_user, expect=200) response = get(reverse('api:project_detail', kwargs={'pk': project.pk}), admin_user, expect=200)
assert not response.data['summary_fields']['user_capabilities']['start'] assert not response.data['summary_fields']['user_capabilities']['start']
assert not response.data['summary_fields']['user_capabilities']['schedule'] assert not response.data['summary_fields']['user_capabilities']['schedule']
@ -369,5 +369,5 @@ def test_license_check_not_called(mocker, job_template, project, org_admin, get)
job_template.save() # need this to make the JT visible job_template.save() # need this to make the JT visible
mock_license_check = mocker.MagicMock() mock_license_check = mocker.MagicMock()
with mocker.patch('awx.main.access.BaseAccess.check_license', mock_license_check): with mocker.patch('awx.main.access.BaseAccess.check_license', mock_license_check):
get(reverse('api:job_template_detail', args=[job_template.pk]), org_admin, expect=200) get(reverse('api:job_template_detail', kwargs={'pk': job_template.pk}), org_admin, expect=200)
assert not mock_license_check.called assert not mock_license_check.called

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
from awx.main.models import Role from awx.main.models import Role
@ -19,7 +19,7 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad
project_admin_team.admin_role.members.add(team_admin) project_admin_team.admin_role.members.add(team_admin)
result = get(reverse('api:project_access_list', args=(project.id,)), admin) result = get(reverse('api:project_access_list', kwargs={'pk': project.id}), admin)
assert result.status_code == 200 assert result.status_code == 200
# Result should be: # Result should be:

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.mark.django_db @pytest.mark.django_db

View File

@ -9,10 +9,8 @@ import os
# Mock # Mock
import mock import mock
# Django
from django.core.urlresolvers import reverse
# AWX # AWX
from awx.api.versioning import reverse
from awx.conf.models import Setting from awx.conf.models import Setting
from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException
@ -32,7 +30,7 @@ def mock_no_license_file(mocker):
@pytest.mark.django_db @pytest.mark.django_db
def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get, put, patch, delete, admin, enterprise_license): def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get, put, patch, delete, admin, enterprise_license):
url = reverse('api:setting_singleton_detail', args=('system',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'system'})
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
assert not response.data['LICENSE'] assert not response.data['LICENSE']
Setting.objects.create(key='TOWER_URL_BASE', value='https://towerhost') Setting.objects.create(key='TOWER_URL_BASE', value='https://towerhost')
@ -53,13 +51,13 @@ def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get
@pytest.mark.django_db @pytest.mark.django_db
def test_url_base_defaults_to_request(options, admin): def test_url_base_defaults_to_request(options, admin):
# If TOWER_URL_BASE is not set, default to the Tower request hostname # If TOWER_URL_BASE is not set, default to the Tower request hostname
resp = options(reverse('api:setting_singleton_detail', args=('system',)), user=admin, expect=200) resp = options(reverse('api:setting_singleton_detail', kwargs={'category_slug': 'system'}), user=admin, expect=200)
assert resp.data['actions']['PUT']['TOWER_URL_BASE']['default'] == 'http://testserver' assert resp.data['actions']['PUT']['TOWER_URL_BASE']['default'] == 'http://testserver'
@pytest.mark.django_db @pytest.mark.django_db
def test_jobs_settings(get, put, patch, delete, admin): def test_jobs_settings(get, put, patch, delete, admin):
url = reverse('api:setting_singleton_detail', args=('jobs',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'jobs'})
get(url, user=admin, expect=200) get(url, user=admin, expect=200)
delete(url, user=admin, expect=204) delete(url, user=admin, expect=204)
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
@ -80,7 +78,7 @@ def test_jobs_settings(get, put, patch, delete, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_ldap_settings(get, put, patch, delete, admin, enterprise_license): def test_ldap_settings(get, put, patch, delete, admin, enterprise_license):
url = reverse('api:setting_singleton_detail', args=('ldap',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
get(url, user=admin, expect=404) get(url, user=admin, expect=404)
Setting.objects.create(key='LICENSE', value=enterprise_license) Setting.objects.create(key='LICENSE', value=enterprise_license)
get(url, user=admin, expect=200) get(url, user=admin, expect=200)
@ -107,7 +105,7 @@ def test_ldap_settings(get, put, patch, delete, admin, enterprise_license):
@pytest.mark.django_db @pytest.mark.django_db
def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license, def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license,
setting): setting):
url = reverse('api:setting_singleton_detail', args=('ldap',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
Setting.objects.create(key='LICENSE', value=enterprise_license) Setting.objects.create(key='LICENSE', value=enterprise_license)
patch(url, user=admin, data={setting: ''}, expect=200) patch(url, user=admin, data={setting: ''}, expect=200)
@ -121,7 +119,7 @@ def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license,
@pytest.mark.django_db @pytest.mark.django_db
def test_radius_settings(get, put, patch, delete, admin, enterprise_license, settings): def test_radius_settings(get, put, patch, delete, admin, enterprise_license, settings):
url = reverse('api:setting_singleton_detail', args=('radius',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'radius'})
get(url, user=admin, expect=404) get(url, user=admin, expect=404)
Setting.objects.create(key='LICENSE', value=enterprise_license) Setting.objects.create(key='LICENSE', value=enterprise_license)
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
@ -155,7 +153,7 @@ def test_radius_settings(get, put, patch, delete, admin, enterprise_license, set
@pytest.mark.django_db @pytest.mark.django_db
def test_ui_settings(get, put, patch, delete, admin, enterprise_license): def test_ui_settings(get, put, patch, delete, admin, enterprise_license):
url = reverse('api:setting_singleton_detail', args=('ui',)) url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'})
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
assert 'CUSTOM_LOGO' not in response.data assert 'CUSTOM_LOGO' not in response.data
assert 'CUSTOM_LOGIN_INFO' not in response.data assert 'CUSTOM_LOGIN_INFO' not in response.data
@ -199,7 +197,6 @@ def test_logging_aggregrator_connection_test_requires_superuser(get, post, alice
@pytest.mark.parametrize('key', [ @pytest.mark.parametrize('key', [
'LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_TYPE',
'LOG_AGGREGATOR_HOST', 'LOG_AGGREGATOR_HOST',
'LOG_AGGREGATOR_PORT',
]) ])
@pytest.mark.django_db @pytest.mark.django_db
def test_logging_aggregrator_connection_test_bad_request(get, post, admin, key): def test_logging_aggregrator_connection_test_bad_request(get, post, admin, key):

View File

@ -2,8 +2,8 @@ import mock
import pytest import pytest
import json import json
from django.core.urlresolvers import reverse
from awx.api.versioning import reverse
from awx.main.models.jobs import JobTemplate, Job from awx.main.models.jobs import JobTemplate, Job
from awx.main.models.activity_stream import ActivityStream from awx.main.models.activity_stream import ActivityStream
from awx.conf.license import LicenseForbids from awx.conf.license import LicenseForbids
@ -30,7 +30,7 @@ def job_template_with_survey(job_template_factory):
def test_survey_spec_view_denied(job_template_with_survey, get, admin_user): def test_survey_spec_view_denied(job_template_with_survey, get, admin_user):
# TODO: Test non-enterprise license # TODO: Test non-enterprise license
response = get(reverse('api:job_template_survey_spec', response = get(reverse('api:job_template_survey_spec',
args=(job_template_with_survey.id,)), admin_user, expect=402) kwargs={'pk': job_template_with_survey.id}), admin_user, expect=402)
assert response.data['detail'] == 'Your license does not allow adding surveys.' assert response.data['detail'] == 'Your license does not allow adding surveys.'
@ -76,7 +76,7 @@ def test_deny_creating_with_survey(project, post, admin_user):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.survey @pytest.mark.survey
def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user): def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user):
get(reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)), get(reverse('api:job_template_survey_spec', kwargs={'pk': deploy_jobtemplate.id}),
admin_user, expect=200) admin_user, expect=200)
@ -85,7 +85,7 @@ def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user):
@pytest.mark.survey @pytest.mark.survey
def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, admin_user): def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, admin_user):
survey_input_data = survey_spec_factory('new_question') survey_input_data = survey_spec_factory('new_question')
post(url=reverse('api:job_template_survey_spec', args=(job_template.id,)), post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}),
data=survey_input_data, user=admin_user, expect=200) data=survey_input_data, user=admin_user, expect=200)
updated_jt = JobTemplate.objects.get(pk=job_template.pk) updated_jt = JobTemplate.objects.get(pk=job_template.pk)
assert updated_jt.survey_spec == survey_input_data assert updated_jt.survey_spec == survey_input_data
@ -98,10 +98,14 @@ def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post,
def test_survey_spec_non_dict_error(deploy_jobtemplate, post, admin_user): def test_survey_spec_non_dict_error(deploy_jobtemplate, post, admin_user):
"""When a question doesn't follow the standard format, verify error thrown.""" """When a question doesn't follow the standard format, verify error thrown."""
response = post( response = post(
url=reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)), url=reverse('api:job_template_survey_spec', kwargs={'pk': deploy_jobtemplate.id}),
data={"description": "Email of the submitter", data={
"spec": ["What is your email?"], "name": "Email survey"}, "description": "Email of the submitter",
user=admin_user, expect=400) "spec": ["What is your email?"], "name": "Email survey"
},
user=admin_user,
expect=400
)
assert response.data['error'] == "Survey question 0 is not a json object." assert response.data['error'] == "Survey question 0 is not a json object."
@ -110,9 +114,11 @@ def test_survey_spec_non_dict_error(deploy_jobtemplate, post, admin_user):
@pytest.mark.survey @pytest.mark.survey
def test_survey_spec_dual_names_error(survey_spec_factory, deploy_jobtemplate, post, user): def test_survey_spec_dual_names_error(survey_spec_factory, deploy_jobtemplate, post, user):
response = post( response = post(
url=reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)), url=reverse('api:job_template_survey_spec', kwargs={'pk': deploy_jobtemplate.id}),
data=survey_spec_factory(['submitter_email', 'submitter_email']), data=survey_spec_factory(['submitter_email', 'submitter_email']),
user=user('admin', True), expect=400) user=user('admin', True),
expect=400
)
assert response.data['error'] == "'variable' 'submitter_email' duplicated in survey question 1." assert response.data['error'] == "'variable' 'submitter_email' duplicated in survey question 1."
@ -166,7 +172,7 @@ def test_job_template_delete_access_with_survey(job_template_with_survey, admin_
@pytest.mark.survey @pytest.mark.survey
def test_delete_survey_spec_without_license(job_template_with_survey, delete, admin_user): def test_delete_survey_spec_without_license(job_template_with_survey, delete, admin_user):
"""Functional delete test through the survey_spec view.""" """Functional delete test through the survey_spec view."""
delete(reverse('api:job_template_survey_spec', args=[job_template_with_survey.pk]), delete(reverse('api:job_template_survey_spec', kwargs={'pk': job_template_with_survey.pk}),
admin_user, expect=200) admin_user, expect=200)
new_jt = JobTemplate.objects.get(pk=job_template_with_survey.pk) new_jt = JobTemplate.objects.get(pk=job_template_with_survey.pk)
assert new_jt.survey_spec == {} assert new_jt.survey_spec == {}
@ -185,7 +191,7 @@ def test_launch_survey_enabled_but_no_survey_spec(job_template_factory, post, ad
obj = objects.job_template obj = objects.job_template
obj.survey_enabled = True obj.survey_enabled = True
obj.save() obj.save()
response = post(reverse('api:job_template_launch', args=[obj.pk]), response = post(reverse('api:job_template_launch', kwargs={'pk':obj.pk}),
dict(extra_vars=dict(survey_var=7)), admin_user, expect=201) dict(extra_vars=dict(survey_var=7)), admin_user, expect=201)
assert 'survey_var' in response.data['ignored_fields']['extra_vars'] assert 'survey_var' in response.data['ignored_fields']['extra_vars']
@ -205,7 +211,7 @@ def test_launch_with_non_empty_survey_spec_no_license(job_template_factory, post
obj = objects.job_template obj = objects.job_template
obj.survey_enabled = False obj.survey_enabled = False
obj.save() obj.save()
post(reverse('api:job_template_launch', args=[obj.pk]), {}, admin_user, expect=201) post(reverse('api:job_template_launch', kwargs={'pk': obj.pk}), {}, admin_user, expect=201)
@pytest.mark.django_db @pytest.mark.django_db

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
@pytest.mark.django_db @pytest.mark.django_db

View File

@ -1,7 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
from awx.main.models import UnifiedJob, ProjectUpdate from awx.main.models import UnifiedJob, ProjectUpdate
from awx.main.tests.base import URI from awx.main.tests.base import URI
@ -60,7 +59,7 @@ formats = [
def test_project_update_redaction_enabled(get, format, content_type, test_cases, admin): def test_project_update_redaction_enabled(get, format, content_type, test_cases, admin):
for test_data in test_cases: for test_data in test_cases:
job = test_data['project'] job = test_data['project']
response = get(reverse("api:project_update_stdout", args=(job.pk,)) + "?format=" + format, user=admin, expect=200, accept=content_type) response = get(reverse("api:project_update_stdout", kwargs={'pk': job.pk}) + "?format=" + format, user=admin, expect=200, accept=content_type)
assert content_type in response['CONTENT-TYPE'] assert content_type in response['CONTENT-TYPE']
assert response.data is not None assert response.data is not None
content = response.data['content'] if format == 'json' else response.data content = response.data['content'] if format == 'json' else response.data
@ -74,7 +73,7 @@ def test_project_update_redaction_enabled(get, format, content_type, test_cases,
def test_job_redaction_disabled(get, format, content_type, negative_test_cases, admin): def test_job_redaction_disabled(get, format, content_type, negative_test_cases, admin):
for test_data in negative_test_cases: for test_data in negative_test_cases:
job = test_data['job'] job = test_data['job']
response = get(reverse("api:job_stdout", args=(job.pk,)) + "?format=" + format, user=admin, expect=200, format=format) response = get(reverse("api:job_stdout", kwargs={'pk': job.pk}) + "?format=" + format, user=admin, expect=200, format=format)
content = response.data['content'] if format == 'json' else response.data content = response.data['content'] if format == 'json' else response.data
assert response.data is not None assert response.data is not None
assert test_data['uri'].username in content assert test_data['uri'].username in content

View File

@ -1,6 +1,6 @@
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
# #
@ -39,7 +39,7 @@ def test_create_delete_create_user(post, delete, admin):
response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin) response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin)
assert response.status_code == 201 assert response.status_code == 201
response = delete(reverse('api:user_detail', args=(response.data['id'],)), admin) response = delete(reverse('api:user_detail', kwargs={'pk': response.data['id']}), admin)
assert response.status_code == 204 assert response.status_code == 204
response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin) response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin)

View File

@ -1,12 +1,11 @@
import mock import mock
import pytest import pytest
from awx.api.versioning import reverse
from awx.main.models.notifications import NotificationTemplate, Notification from awx.main.models.notifications import NotificationTemplate, Notification
from awx.main.models.inventory import Inventory, Group from awx.main.models.inventory import Inventory, Group
from awx.main.models.jobs import JobTemplate from awx.main.models.jobs import JobTemplate
from django.core.urlresolvers import reverse
@pytest.mark.django_db @pytest.mark.django_db
def test_get_notification_template_list(get, user, notification_template): def test_get_notification_template_list(get, user, notification_template):
@ -29,7 +28,7 @@ def test_basic_parameterization(get, post, user, organization):
headers={"Test": "Header"})), headers={"Test": "Header"})),
u) u)
assert response.status_code == 201 assert response.status_code == 201
url = reverse('api:notification_template_detail', args=(response.data['id'],)) url = reverse('api:notification_template_detail', kwargs={'pk': response.data['id']})
response = get(url, u) response = get(url, u)
assert 'related' in response.data assert 'related' in response.data
assert 'organization' in response.data['related'] assert 'organization' in response.data['related']
@ -60,7 +59,7 @@ def test_encrypted_subfields(get, post, user, organization):
u) u)
assert response.status_code == 201 assert response.status_code == 201
notification_template_actual = NotificationTemplate.objects.get(id=response.data['id']) notification_template_actual = NotificationTemplate.objects.get(id=response.data['id'])
url = reverse('api:notification_template_detail', args=(response.data['id'],)) url = reverse('api:notification_template_detail', kwargs={'pk': response.data['id']})
response = get(url, u) response = get(url, u)
assert response.data['notification_configuration']['account_token'] == "$encrypted$" assert response.data['notification_configuration']['account_token'] == "$encrypted$"
with mock.patch.object(notification_template_actual.notification_class, "send_messages", assert_send): with mock.patch.object(notification_template_actual.notification_class, "send_messages", assert_send):
@ -89,13 +88,13 @@ def test_inherited_notification_templates(get, post, user, organization, project
g.save() g.save()
jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml') jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml')
jt.save() jt.save()
url = reverse('api:organization_notification_templates_any_list', args=(organization.id,)) url = reverse('api:organization_notification_templates_any_list', kwargs={'pk': organization.id})
response = post(url, dict(id=notification_templates[0]), u) response = post(url, dict(id=notification_templates[0]), u)
assert response.status_code == 204 assert response.status_code == 204
url = reverse('api:project_notification_templates_any_list', args=(project.id,)) url = reverse('api:project_notification_templates_any_list', kwargs={'pk': project.id})
response = post(url, dict(id=notification_templates[1]), u) response = post(url, dict(id=notification_templates[1]), u)
assert response.status_code == 204 assert response.status_code == 204
url = reverse('api:job_template_notification_templates_any_list', args=(jt.id,)) url = reverse('api:job_template_notification_templates_any_list', kwargs={'pk': jt.id})
response = post(url, dict(id=notification_templates[2]), u) response = post(url, dict(id=notification_templates[2]), u)
assert response.status_code == 204 assert response.status_code == 204
assert len(jt.notification_templates['any']) == 3 assert len(jt.notification_templates['any']) == 3
@ -113,18 +112,18 @@ def test_notification_template_merging(get, post, user, organization, project, n
@pytest.mark.django_db @pytest.mark.django_db
def test_notification_template_simple_patch(patch, notification_template, admin): def test_notification_template_simple_patch(patch, notification_template, admin):
patch(reverse('api:notification_template_detail', args=(notification_template.id,)), { 'name': 'foo'}, admin, expect=200) patch(reverse('api:notification_template_detail', kwargs={'pk': notification_template.id}), { 'name': 'foo'}, admin, expect=200)
@pytest.mark.django_db @pytest.mark.django_db
def test_notification_template_invalid_notification_type(patch, notification_template, admin): def test_notification_template_invalid_notification_type(patch, notification_template, admin):
patch(reverse('api:notification_template_detail', args=(notification_template.id,)), { 'notification_type': 'invalid'}, admin, expect=400) patch(reverse('api:notification_template_detail', kwargs={'pk': notification_template.id}), { 'notification_type': 'invalid'}, admin, expect=400)
@pytest.mark.django_db @pytest.mark.django_db
def test_disallow_delete_when_notifications_pending(delete, user, notification_template): def test_disallow_delete_when_notifications_pending(delete, user, notification_template):
u = user('superuser', True) u = user('superuser', True)
url = reverse('api:notification_template_detail', args=(notification_template.id,)) url = reverse('api:notification_template_detail', kwargs={'pk': notification_template.id})
Notification.objects.create(notification_template=notification_template, Notification.objects.create(notification_template=notification_template,
status='pending') status='pending')
response = delete(url, user=u) response = delete(url, user=u)

View File

@ -3,7 +3,7 @@
import mock # noqa import mock # noqa
import pytest import pytest
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
from awx.main.models import Project from awx.main.models import Project
@ -38,13 +38,13 @@ def test_user_project_paged_list(get, organization_factory):
# first page has first project and no previous page # first page has first project and no previous page
pk = objects.users.alice.pk pk = objects.users.alice.pk
url = reverse('api:user_projects_list', args=(pk,)) url = reverse('api:user_projects_list', kwargs={'pk':pk,})
results = get(url, objects.users.alice, QUERY_STRING='page_size=1').data results = get(url, objects.users.alice, QUERY_STRING='page_size=1').data
assert results['count'] == 3 assert results['count'] == 3
assert len(results['results']) == 1 assert len(results['results']) == 1
assert results['previous'] is None assert results['previous'] is None
assert results['next'] == ( assert results['next'] == (
'/api/v1/users/%s/projects/?page=2&page_size=1' % pk '/api/v2/users/%s/projects/?page=2&page_size=1' % pk
) )
# second page has one more, a previous and next page # second page has one more, a previous and next page
@ -52,10 +52,10 @@ def test_user_project_paged_list(get, organization_factory):
QUERY_STRING='page=2&page_size=1').data QUERY_STRING='page=2&page_size=1').data
assert len(results['results']) == 1 assert len(results['results']) == 1
assert results['previous'] == ( assert results['previous'] == (
'/api/v1/users/%s/projects/?page=1&page_size=1' % pk '/api/v2/users/%s/projects/?page=1&page_size=1' % pk
) )
assert results['next'] == ( assert results['next'] == (
'/api/v1/users/%s/projects/?page=3&page_size=1' % pk '/api/v2/users/%s/projects/?page=3&page_size=1' % pk
) )
# third page has last project and a previous page # third page has last project and a previous page
@ -63,7 +63,7 @@ def test_user_project_paged_list(get, organization_factory):
QUERY_STRING='page=3&page_size=1').data QUERY_STRING='page=3&page_size=1').data
assert len(results['results']) == 1 assert len(results['results']) == 1
assert results['previous'] == ( assert results['previous'] == (
'/api/v1/users/%s/projects/?page=2&page_size=1' % pk '/api/v2/users/%s/projects/?page=2&page_size=1' % pk
) )
assert results['next'] is None assert results['next'] is None
@ -81,7 +81,7 @@ def test_user_project_paged_list_with_unicode(get, organization_factory):
roles=['project-☁-1.admin_role:alice','project-☁-2.admin_role:alice'], roles=['project-☁-1.admin_role:alice','project-☁-2.admin_role:alice'],
) )
pk = objects.users.alice.pk pk = objects.users.alice.pk
url = reverse('api:user_projects_list', args=(pk,)) url = reverse('api:user_projects_list', kwargs={'pk':pk,})
# first on first page, next page link contains unicode char # first on first page, next page link contains unicode char
results = get(url, objects.users.alice, results = get(url, objects.users.alice,
@ -89,7 +89,7 @@ def test_user_project_paged_list_with_unicode(get, organization_factory):
assert results['count'] == 2 assert results['count'] == 2
assert len(results['results']) == 1 assert len(results['results']) == 1
assert results['next'] == ( assert results['next'] == (
'/api/v1/users/%s/projects/?page=2&page_size=1&search=%%E2%%98%%81' % pk # noqa '/api/v2/users/%s/projects/?page=2&page_size=1&search=%%E2%%98%%81' % pk # noqa
) )
# second project on second page, previous page link contains unicode char # second project on second page, previous page link contains unicode char
@ -98,7 +98,7 @@ def test_user_project_paged_list_with_unicode(get, organization_factory):
assert results['count'] == 2 assert results['count'] == 2
assert len(results['results']) == 1 assert len(results['results']) == 1
assert results['previous'] == ( assert results['previous'] == (
'/api/v1/users/%s/projects/?page=1&page_size=1&search=%%E2%%98%%81' % pk # noqa '/api/v2/users/%s/projects/?page=1&page_size=1&search=%%E2%%98%%81' % pk # noqa
) )
@ -114,21 +114,23 @@ def test_user_project_list(get, organization_factory):
'bob project.admin_role:bob', 'bob project.admin_role:bob',
'shared project.admin_role:bob', 'shared project.admin_role:bob',
'shared project.admin_role:alice']) 'shared project.admin_role:alice'])
assert get(reverse(
assert get(reverse('api:user_projects_list', args=(objects.superusers.admin.pk,)), objects.superusers.admin).data['count'] == 3 'api:user_projects_list',
kwargs={'pk':objects.superusers.admin.pk,}
), objects.superusers.admin).data['count'] == 3
# admins can see everyones projects # admins can see everyones projects
assert get(reverse('api:user_projects_list', args=(objects.users.alice.pk,)), objects.superusers.admin).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':objects.users.alice.pk,}), objects.superusers.admin).data['count'] == 2
assert get(reverse('api:user_projects_list', args=(objects.users.bob.pk,)), objects.superusers.admin).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':objects.users.bob.pk,}), objects.superusers.admin).data['count'] == 2
# users can see their own projects # users can see their own projects
assert get(reverse('api:user_projects_list', args=(objects.users.alice.pk,)), objects.users.alice).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':objects.users.alice.pk,}), objects.users.alice).data['count'] == 2
# alice should only be able to see the shared project when looking at bobs projects # alice should only be able to see the shared project when looking at bobs projects
assert get(reverse('api:user_projects_list', args=(objects.users.bob.pk,)), objects.users.alice).data['count'] == 1 assert get(reverse('api:user_projects_list', kwargs={'pk':objects.users.bob.pk,}), objects.users.alice).data['count'] == 1
# alice should see all projects they can see when viewing an admin # alice should see all projects they can see when viewing an admin
assert get(reverse('api:user_projects_list', args=(objects.superusers.admin.pk,)), objects.users.alice).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':objects.superusers.admin.pk,}), objects.users.alice).data['count'] == 2
@pytest.mark.django_db @pytest.mark.django_db
@ -139,35 +141,35 @@ def test_team_project_list(get, team_project_list):
alice, bob, admin = objects.users.alice, objects.users.bob, objects.superusers.admin alice, bob, admin = objects.users.alice, objects.users.bob, objects.superusers.admin
# admins can see all projects on a team # admins can see all projects on a team
assert get(reverse('api:team_projects_list', args=(team1.pk,)), admin).data['count'] == 2 assert get(reverse('api:team_projects_list', kwargs={'pk':team1.pk,}), admin).data['count'] == 2
assert get(reverse('api:team_projects_list', args=(team2.pk,)), admin).data['count'] == 2 assert get(reverse('api:team_projects_list', kwargs={'pk':team2.pk,}), admin).data['count'] == 2
# users can see all projects on teams they are a member of # users can see all projects on teams they are a member of
assert get(reverse('api:team_projects_list', args=(team1.pk,)), alice).data['count'] == 2 assert get(reverse('api:team_projects_list', kwargs={'pk':team1.pk,}), alice).data['count'] == 2
# but if she does, then she should only see the shared project # but if she does, then she should only see the shared project
team2.read_role.members.add(alice) team2.read_role.members.add(alice)
assert get(reverse('api:team_projects_list', args=(team2.pk,)), alice).data['count'] == 1 assert get(reverse('api:team_projects_list', kwargs={'pk':team2.pk,}), alice).data['count'] == 1
team2.read_role.members.remove(alice) team2.read_role.members.remove(alice)
# admins can see all projects # admins can see all projects
assert get(reverse('api:user_projects_list', args=(admin.pk,)), admin).data['count'] == 3 assert get(reverse('api:user_projects_list', kwargs={'pk':admin.pk,}), admin).data['count'] == 3
# admins can see everyones projects # admins can see everyones projects
assert get(reverse('api:user_projects_list', args=(alice.pk,)), admin).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':alice.pk,}), admin).data['count'] == 2
assert get(reverse('api:user_projects_list', args=(bob.pk,)), admin).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':bob.pk,}), admin).data['count'] == 2
# users can see their own projects # users can see their own projects
assert get(reverse('api:user_projects_list', args=(alice.pk,)), alice).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':alice.pk,}), alice).data['count'] == 2
# alice should see all projects they can see when viewing an admin # alice should see all projects they can see when viewing an admin
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2 assert get(reverse('api:user_projects_list', kwargs={'pk':admin.pk,}), alice).data['count'] == 2
@pytest.mark.django_db @pytest.mark.django_db
def test_team_project_list_fail1(get, team_project_list): def test_team_project_list_fail1(get, team_project_list):
objects = team_project_list objects = team_project_list
res = get(reverse('api:team_projects_list', args=(objects.teams.team2.pk,)), objects.users.alice) res = get(reverse('api:team_projects_list', kwargs={'pk':objects.teams.team2.pk,}), objects.users.alice)
assert res.status_code == 403 assert res.status_code == 403
@ -210,19 +212,22 @@ def test_create_project_null_organization_xfail(post, organization, org_admin):
@pytest.mark.django_db() @pytest.mark.django_db()
def test_patch_project_null_organization(patch, organization, project, admin): def test_patch_project_null_organization(patch, organization, project, admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': organization.id}, admin, expect=200) patch(reverse('api:project_detail', kwargs={'pk':project.id,}), { 'name': 't', 'organization': organization.id}, admin, expect=200)
@pytest.mark.django_db() @pytest.mark.django_db()
def test_patch_project_null_organization_xfail(patch, project, org_admin): def test_patch_project_null_organization_xfail(patch, project, org_admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': None}, org_admin, expect=400) patch(reverse('api:project_detail', kwargs={'pk':project.id,}), { 'name': 't', 'organization': None}, org_admin, expect=400)
@pytest.mark.django_db @pytest.mark.django_db
def test_cannot_schedule_manual_project(project, admin_user, post): def test_cannot_schedule_manual_project(project, admin_user, post):
response = post( response = post(
reverse('api:project_schedules_list', args=(project.pk,)), reverse('api:project_schedules_list', kwargs={'pk':project.pk,}),
{"name": "foo", "description": "", "enabled": True, {
"name": "foo", "description": "", "enabled": True,
"rrule": "DTSTART:20160926T040000Z RRULE:FREQ=HOURLY;INTERVAL=1", "rrule": "DTSTART:20160926T040000Z RRULE:FREQ=HOURLY;INTERVAL=1",
"extra_data": {}}, admin_user, expect=400) "extra_data": {}
}, admin_user, expect=400
)
assert 'Manual' in response.data['unified_job_template'][0] assert 'Manual' in response.data['unified_job_template'][0]

View File

@ -2,7 +2,7 @@ import mock # noqa
import pytest import pytest
from django.db import transaction from django.db import transaction
from django.core.urlresolvers import reverse from awx.api.versioning import reverse
from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
@ -78,14 +78,14 @@ def test_roles_filter_visibility(get, organization, project, admin, alice, bob):
Role.singleton('system_auditor').members.add(alice) Role.singleton('system_auditor').members.add(alice)
project.update_role.members.add(admin) project.update_role.members.add(admin)
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1 assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1 assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0 assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0
organization.auditor_role.members.add(bob) organization.auditor_role.members.add(bob)
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1 assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1
organization.auditor_role.members.remove(bob) organization.auditor_role.members.remove(bob)
project.use_role.members.add(bob) # sibling role should still grant visibility project.use_role.members.add(bob) # sibling role should still grant visibility
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1 assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1
@pytest.mark.django_db @pytest.mark.django_db
@ -104,7 +104,7 @@ def test_cant_delete_role(delete, admin):
# Some day we might want to do this, but until that is speced out, lets # Some day we might want to do this, but until that is speced out, lets
# ensure we don't slip up and allow this implicitly through some helper or # ensure we don't slip up and allow this implicitly through some helper or
# another # another
response = delete(reverse('api:role_detail', args=(admin.admin_role.id,)), admin) response = delete(reverse('api:role_detail', kwargs={'pk': admin.admin_role.id}), admin)
assert response.status_code == 405 assert response.status_code == 405
@ -115,7 +115,7 @@ def test_cant_delete_role(delete, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_get_user_roles_list(get, admin): def test_get_user_roles_list(get, admin):
url = reverse('api:user_roles_list', args=(admin.id,)) url = reverse('api:user_roles_list', kwargs={'pk': admin.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
roles = response.data roles = response.data
@ -136,7 +136,7 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
# Bob is an org admin, alice can see this. # Bob is an org admin, alice can see this.
# Bob is in a team that alice is not, alice cannot see that bob is a member of that team. # Bob is in a team that alice is not, alice cannot see that bob is a member of that team.
url = reverse('api:user_roles_list', args=(bob.id,)) url = reverse('api:user_roles_list', kwargs={'pk': bob.id})
response = get(url, alice) response = get(url, alice)
assert response.status_code == 200 assert response.status_code == 200
roles = response.data roles = response.data
@ -171,7 +171,7 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
@pytest.mark.django_db @pytest.mark.django_db
def test_add_role_to_user(role, post, admin): def test_add_role_to_user(role, post, admin):
assert admin.roles.filter(id=role.id).count() == 0 assert admin.roles.filter(id=role.id).count() == 0
url = reverse('api:user_roles_list', args=(admin.id,)) url = reverse('api:user_roles_list', kwargs={'pk': admin.id})
response = post(url, {'id': role.id}, admin) response = post(url, {'id': role.id}, admin)
assert response.status_code == 204 assert response.status_code == 204
@ -189,7 +189,7 @@ def test_add_role_to_user(role, post, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_remove_role_from_user(role, post, admin): def test_remove_role_from_user(role, post, admin):
assert admin.roles.filter(id=role.id).count() == 0 assert admin.roles.filter(id=role.id).count() == 0
url = reverse('api:user_roles_list', args=(admin.id,)) url = reverse('api:user_roles_list', kwargs={'pk': admin.id})
response = post(url, {'id': role.id}, admin) response = post(url, {'id': role.id}, admin)
assert response.status_code == 204 assert response.status_code == 204
assert admin.roles.filter(id=role.id).count() == 1 assert admin.roles.filter(id=role.id).count() == 1
@ -207,7 +207,7 @@ def test_remove_role_from_user(role, post, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_get_teams_roles_list(get, team, organization, admin): def test_get_teams_roles_list(get, team, organization, admin):
team.member_role.children.add(organization.admin_role) team.member_role.children.add(organization.admin_role)
url = reverse('api:team_roles_list', args=(team.id,)) url = reverse('api:team_roles_list', kwargs={'pk': team.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
roles = response.data roles = response.data
@ -219,7 +219,7 @@ def test_get_teams_roles_list(get, team, organization, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_add_role_to_teams(team, post, admin): def test_add_role_to_teams(team, post, admin):
assert team.member_role.children.filter(id=team.member_role.id).count() == 0 assert team.member_role.children.filter(id=team.member_role.id).count() == 0
url = reverse('api:team_roles_list', args=(team.id,)) url = reverse('api:team_roles_list', kwargs={'pk': team.id})
response = post(url, {'id': team.member_role.id}, admin) response = post(url, {'id': team.member_role.id}, admin)
assert response.status_code == 204 assert response.status_code == 204
@ -237,7 +237,7 @@ def test_add_role_to_teams(team, post, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_remove_role_from_teams(team, post, admin): def test_remove_role_from_teams(team, post, admin):
assert team.member_role.children.filter(id=team.member_role.id).count() == 0 assert team.member_role.children.filter(id=team.member_role.id).count() == 0
url = reverse('api:team_roles_list', args=(team.id,)) url = reverse('api:team_roles_list', kwargs={'pk': team.id})
response = post(url, {'id': team.member_role.id}, admin) response = post(url, {'id': team.member_role.id}, admin)
assert response.status_code == 204 assert response.status_code == 204
assert team.member_role.children.filter(id=team.member_role.id).count() == 1 assert team.member_role.children.filter(id=team.member_role.id).count() == 1
@ -254,7 +254,7 @@ def test_remove_role_from_teams(team, post, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_get_role(get, admin, role): def test_get_role(get, admin, role):
url = reverse('api:role_detail', args=(role.id,)) url = reverse('api:role_detail', kwargs={'pk': role.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['id'] == role.id assert response.data['id'] == role.id
@ -262,7 +262,7 @@ def test_get_role(get, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_put_role_405(put, admin, role): def test_put_role_405(put, admin, role):
url = reverse('api:role_detail', args=(role.id,)) url = reverse('api:role_detail', kwargs={'pk': role.id})
response = put(url, {'name': 'Some new name'}, admin) response = put(url, {'name': 'Some new name'}, admin)
assert response.status_code == 405 assert response.status_code == 405
#r = Role.objects.get(id=role.id) #r = Role.objects.get(id=role.id)
@ -271,7 +271,7 @@ def test_put_role_405(put, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_put_role_access_denied(put, alice, role): def test_put_role_access_denied(put, alice, role):
url = reverse('api:role_detail', args=(role.id,)) url = reverse('api:role_detail', kwargs={'pk': role.id})
response = put(url, {'name': 'Some new name'}, alice) response = put(url, {'name': 'Some new name'}, alice)
assert response.status_code == 403 or response.status_code == 405 assert response.status_code == 403 or response.status_code == 405
@ -284,7 +284,7 @@ def test_put_role_access_denied(put, alice, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_get_role_users(get, admin, role): def test_get_role_users(get, admin, role):
role.members.add(admin) role.members.add(admin)
url = reverse('api:role_users_list', args=(role.id,)) url = reverse('api:role_users_list', kwargs={'pk': role.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -293,7 +293,7 @@ def test_get_role_users(get, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_add_user_to_role(post, admin, role): def test_add_user_to_role(post, admin, role):
url = reverse('api:role_users_list', args=(role.id,)) url = reverse('api:role_users_list', kwargs={'pk': role.id})
assert role.members.filter(id=admin.id).count() == 0 assert role.members.filter(id=admin.id).count() == 0
post(url, {'id': admin.id}, admin) post(url, {'id': admin.id}, admin)
assert role.members.filter(id=admin.id).count() == 1 assert role.members.filter(id=admin.id).count() == 1
@ -302,7 +302,7 @@ def test_add_user_to_role(post, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_remove_user_to_role(post, admin, role): def test_remove_user_to_role(post, admin, role):
role.members.add(admin) role.members.add(admin)
url = reverse('api:role_users_list', args=(role.id,)) url = reverse('api:role_users_list', kwargs={'pk': role.id})
assert role.members.filter(id=admin.id).count() == 1 assert role.members.filter(id=admin.id).count() == 1
post(url, {'disassociate': True, 'id': admin.id}, admin) post(url, {'disassociate': True, 'id': admin.id}, admin)
assert role.members.filter(id=admin.id).count() == 0 assert role.members.filter(id=admin.id).count() == 0
@ -318,7 +318,7 @@ def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplat
assert org_admin in check_jobtemplate.admin_role assert org_admin in check_jobtemplate.admin_role
assert joe not in check_jobtemplate.execute_role assert joe not in check_jobtemplate.execute_role
post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, org_admin) post(reverse('api:role_users_list', kwargs={'pk': check_jobtemplate.execute_role.id}), {'id': joe.id}, org_admin)
assert joe in check_jobtemplate.execute_role assert joe in check_jobtemplate.execute_role
@ -333,7 +333,7 @@ def test_org_admin_remove_user_from_job_template(post, organization, check_jobte
assert org_admin in check_jobtemplate.admin_role assert org_admin in check_jobtemplate.admin_role
assert joe in check_jobtemplate.execute_role assert joe in check_jobtemplate.execute_role
post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, org_admin) post(reverse('api:role_users_list', kwargs={'pk': check_jobtemplate.execute_role.id}), {'disassociate': True, 'id': joe.id}, org_admin)
assert joe not in check_jobtemplate.execute_role assert joe not in check_jobtemplate.execute_role
@ -347,7 +347,7 @@ def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemp
assert joe not in check_jobtemplate.execute_role assert joe not in check_jobtemplate.execute_role
with transaction.atomic(): with transaction.atomic():
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, rando) res = post(reverse('api:role_users_list', kwargs={'pk': check_jobtemplate.execute_role.id}), {'id': joe.id}, rando)
assert res.status_code == 403 assert res.status_code == 403
assert joe not in check_jobtemplate.execute_role assert joe not in check_jobtemplate.execute_role
@ -364,7 +364,7 @@ def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobt
assert joe in check_jobtemplate.execute_role assert joe in check_jobtemplate.execute_role
with transaction.atomic(): with transaction.atomic():
res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, rando) res = post(reverse('api:role_users_list', kwargs={'pk': check_jobtemplate.execute_role.id}), {'disassociate': True, 'id': joe.id}, rando)
assert res.status_code == 403 assert res.status_code == 403
assert joe in check_jobtemplate.execute_role assert joe in check_jobtemplate.execute_role
@ -378,7 +378,7 @@ def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobt
@pytest.mark.django_db @pytest.mark.django_db
def test_get_role_teams(get, team, admin, role): def test_get_role_teams(get, team, admin, role):
role.parents.add(team.member_role) role.parents.add(team.member_role)
url = reverse('api:role_teams_list', args=(role.id,)) url = reverse('api:role_teams_list', kwargs={'pk': role.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -387,7 +387,7 @@ def test_get_role_teams(get, team, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_add_team_to_role(post, team, admin, role): def test_add_team_to_role(post, team, admin, role):
url = reverse('api:role_teams_list', args=(role.id,)) url = reverse('api:role_teams_list', kwargs={'pk': role.id})
assert role.members.filter(id=admin.id).count() == 0 assert role.members.filter(id=admin.id).count() == 0
res = post(url, {'id': team.id}, admin) res = post(url, {'id': team.id}, admin)
assert res.status_code == 204 assert res.status_code == 204
@ -397,7 +397,7 @@ def test_add_team_to_role(post, team, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_remove_team_from_role(post, team, admin, role): def test_remove_team_from_role(post, team, admin, role):
role.members.add(admin) role.members.add(admin)
url = reverse('api:role_teams_list', args=(role.id,)) url = reverse('api:role_teams_list', kwargs={'pk': role.id})
assert role.members.filter(id=admin.id).count() == 1 assert role.members.filter(id=admin.id).count() == 1
res = post(url, {'disassociate': True, 'id': team.id}, admin) res = post(url, {'disassociate': True, 'id': team.id}, admin)
assert res.status_code == 204 assert res.status_code == 204
@ -412,7 +412,7 @@ def test_remove_team_from_role(post, team, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_role_parents(get, team, admin, role): def test_role_parents(get, team, admin, role):
role.parents.add(team.member_role) role.parents.add(team.member_role)
url = reverse('api:role_parents_list', args=(role.id,)) url = reverse('api:role_parents_list', kwargs={'pk': role.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 1
@ -427,7 +427,7 @@ def test_role_parents(get, team, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_role_children(get, team, admin, role): def test_role_children(get, team, admin, role):
role.parents.add(team.member_role) role.parents.add(team.member_role)
url = reverse('api:role_children_list', args=(team.member_role.id,)) url = reverse('api:role_children_list', kwargs={'pk': team.member_role.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 2 assert response.data['count'] == 2
@ -441,7 +441,7 @@ def test_role_children(get, team, admin, role):
@pytest.mark.django_db @pytest.mark.django_db
def test_ensure_rbac_fields_are_present(organization, get, admin): def test_ensure_rbac_fields_are_present(organization, get, admin):
url = reverse('api:organization_detail', args=(organization.id,)) url = reverse('api:organization_detail', kwargs={'pk': organization.id})
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
org = response.data org = response.data
@ -450,7 +450,7 @@ def test_ensure_rbac_fields_are_present(organization, get, admin):
assert 'object_roles' in org['summary_fields'] assert 'object_roles' in org['summary_fields']
role_pk = org['summary_fields']['object_roles']['admin_role']['id'] role_pk = org['summary_fields']['object_roles']['admin_role']['id']
role_url = reverse('api:role_detail', args=(role_pk,)) role_url = reverse('api:role_detail', kwargs={'pk': role_pk})
org_role_response = get(role_url, admin) org_role_response = get(role_url, admin)
assert org_role_response.status_code == 200 assert org_role_response.status_code == 200
@ -460,7 +460,7 @@ def test_ensure_rbac_fields_are_present(organization, get, admin):
@pytest.mark.django_db @pytest.mark.django_db
def test_ensure_role_summary_is_present(organization, get, user): def test_ensure_role_summary_is_present(organization, get, user):
url = reverse('api:organization_detail', args=(organization.id,)) url = reverse('api:organization_detail', kwargs={'pk': organization.id})
response = get(url, user('admin', True)) response = get(url, user('admin', True))
assert response.status_code == 200 assert response.status_code == 200
org = response.data org = response.data

View File

@ -1,6 +1,7 @@
import mock import mock
import pytest import pytest
from awx.api.versioning import reverse
from awx.main.access import ( from awx.main.access import (
BaseAccess, BaseAccess,
JobTemplateAccess, JobTemplateAccess,
@ -12,7 +13,6 @@ from awx.main.models.jobs import JobTemplate
from awx.main.models.schedules import Schedule from awx.main.models.schedules import Schedule
from django.apps import apps from django.apps import apps
from django.core.urlresolvers import reverse
@pytest.fixture @pytest.fixture
@ -250,7 +250,7 @@ def test_job_template_creator_access(project, rando, post):
with mock.patch( with mock.patch(
'awx.main.models.projects.ProjectOptions.playbooks', 'awx.main.models.projects.ProjectOptions.playbooks',
new_callable=mock.PropertyMock(return_value=['helloworld.yml'])): new_callable=mock.PropertyMock(return_value=['helloworld.yml'])):
response = post(reverse('api:job_template_list', args=[]), dict( response = post(reverse('api:job_template_list'), dict(
name='newly-created-jt', name='newly-created-jt',
job_type='run', job_type='run',
ask_inventory_on_launch=True, ask_inventory_on_launch=True,

View File

@ -6,7 +6,7 @@ import pytest
def get_related_assert(): def get_related_assert():
def fn(model_obj, related, resource_name, related_resource_name): def fn(model_obj, related, resource_name, related_resource_name):
assert related_resource_name in related assert related_resource_name in related
assert related[related_resource_name] == '/api/v1/%s/%d/%s/' % (resource_name, model_obj.pk, related_resource_name) assert related[related_resource_name] == '/api/v2/%s/%d/%s/' % (resource_name, model_obj.pk, related_resource_name)
return fn return fn

View File

@ -68,7 +68,7 @@ class TestJobSerializerGetRelated():
def test_job_template_present(self, get_related_mock_and_run, job): def test_job_template_present(self, get_related_mock_and_run, job):
related = get_related_mock_and_run(JobSerializer, job) related = get_related_mock_and_run(JobSerializer, job)
assert 'job_template' in related assert 'job_template' in related
assert related['job_template'] == '/api/v1/%s/%d/' % ('job_templates', job.job_template.pk) assert related['job_template'] == '/api/v2/%s/%d/' % ('job_templates', job.job_template.pk)
@mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: { @mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: {

View File

@ -104,7 +104,7 @@ class TestJobTemplateSerializerGetSummaryFields():
serializer.show_capabilities = ['copy', 'edit'] serializer.show_capabilities = ['copy', 'edit']
serializer._summary_field_labels = lambda self: [] serializer._summary_field_labels = lambda self: []
serializer._recent_jobs = lambda self: [] serializer._recent_jobs = lambda self: []
request = APIRequestFactory().get('/api/v1/job_templates/42/') request = APIRequestFactory().get('/api/v2/job_templates/42/')
request.user = user request.user = user
view = JobTemplateDetail() view = JobTemplateDetail()
view.request = request view.request = request

View File

@ -56,7 +56,7 @@ class TestWorkflowNodeBaseSerializerGetRelated():
def test_workflow_unified_job_template_present(self, get_related_mock_and_run, workflow_job_template_node_related): def test_workflow_unified_job_template_present(self, get_related_mock_and_run, workflow_job_template_node_related):
related = get_related_mock_and_run(WorkflowNodeBaseSerializer, workflow_job_template_node_related) related = get_related_mock_and_run(WorkflowNodeBaseSerializer, workflow_job_template_node_related)
assert 'unified_job_template' in related assert 'unified_job_template' in related
assert related['unified_job_template'] == '/api/v1/%s/%d/' % ('job_templates', workflow_job_template_node_related.unified_job_template.pk) assert related['unified_job_template'] == '/api/v2/%s/%d/' % ('job_templates', workflow_job_template_node_related.unified_job_template.pk)
def test_workflow_unified_job_template_absent(self, workflow_job_template_node): def test_workflow_unified_job_template_absent(self, workflow_job_template_node):
related = WorkflowJobTemplateNodeSerializer().get_related(workflow_job_template_node) related = WorkflowJobTemplateNodeSerializer().get_related(workflow_job_template_node)
@ -100,7 +100,7 @@ class TestWorkflowJobTemplateNodeSerializerGetRelated():
def test_workflow_job_template_present(self, get_related_mock_and_run, workflow_job_template_node_related): def test_workflow_job_template_present(self, get_related_mock_and_run, workflow_job_template_node_related):
related = get_related_mock_and_run(WorkflowJobTemplateNodeSerializer, workflow_job_template_node_related) related = get_related_mock_and_run(WorkflowJobTemplateNodeSerializer, workflow_job_template_node_related)
assert 'workflow_job_template' in related assert 'workflow_job_template' in related
assert related['workflow_job_template'] == '/api/v1/%s/%d/' % ('workflow_job_templates', workflow_job_template_node_related.workflow_job_template.pk) assert related['workflow_job_template'] == '/api/v2/%s/%d/' % ('workflow_job_templates', workflow_job_template_node_related.workflow_job_template.pk)
def test_workflow_job_template_absent(self, workflow_job_template_node): def test_workflow_job_template_absent(self, workflow_job_template_node):
related = WorkflowJobTemplateNodeSerializer().get_related(workflow_job_template_node) related = WorkflowJobTemplateNodeSerializer().get_related(workflow_job_template_node)
@ -179,7 +179,7 @@ class TestWorkflowJobNodeSerializerGetRelated():
def test_workflow_job_present(self, get_related_mock_and_run, workflow_job_node_related): def test_workflow_job_present(self, get_related_mock_and_run, workflow_job_node_related):
related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_related) related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_related)
assert 'workflow_job' in related assert 'workflow_job' in related
assert related['workflow_job'] == '/api/v1/%s/%d/' % ('workflow_jobs', workflow_job_node_related.workflow_job.pk) assert related['workflow_job'] == '/api/v2/%s/%d/' % ('workflow_jobs', workflow_job_node_related.workflow_job.pk)
def test_workflow_job_absent(self, workflow_job_node): def test_workflow_job_absent(self, workflow_job_node):
related = WorkflowJobNodeSerializer().get_related(workflow_job_node) related = WorkflowJobNodeSerializer().get_related(workflow_job_node)
@ -188,7 +188,7 @@ class TestWorkflowJobNodeSerializerGetRelated():
def test_job_present(self, get_related_mock_and_run, workflow_job_node_related): def test_job_present(self, get_related_mock_and_run, workflow_job_node_related):
related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_related) related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_related)
assert 'job' in related assert 'job' in related
assert related['job'] == '/api/v1/%s/%d/' % ('jobs', workflow_job_node_related.job.pk) assert related['job'] == '/api/v2/%s/%d/' % ('jobs', workflow_job_node_related.job.pk)
def test_job_absent(self, workflow_job_node): def test_job_absent(self, workflow_job_node):
related = WorkflowJobNodeSerializer().get_related(workflow_job_node) related = WorkflowJobNodeSerializer().get_related(workflow_job_node)

View File

@ -7,10 +7,19 @@ from awx.api.filters import FieldLookupBackend
from awx.main.models import (AdHocCommand, AuthToken, CustomInventoryScript, from awx.main.models import (AdHocCommand, AuthToken, CustomInventoryScript,
Credential, Job, JobTemplate, SystemJob, Credential, Job, JobTemplate, SystemJob,
UnifiedJob, User, WorkflowJob, UnifiedJob, User, WorkflowJob,
WorkflowJobTemplate, WorkflowJobOptions) WorkflowJobTemplate, WorkflowJobOptions,
InventorySource)
from awx.main.models.jobs import JobOptions from awx.main.models.jobs import JobOptions
def test_related():
field_lookup = FieldLookupBackend()
lookup = '__'.join(['inventory', 'organization', 'pk'])
field, new_lookup = field_lookup.get_field_from_lookup(InventorySource, lookup)
print(field)
print(new_lookup)
@pytest.mark.parametrize(u"empty_value", [u'', '']) @pytest.mark.parametrize(u"empty_value", [u'', ''])
def test_empty_in(empty_value): def test_empty_in(empty_value):
field_lookup = FieldLookupBackend() field_lookup = FieldLookupBackend()

View File

@ -4,7 +4,7 @@ import pytest
from collections import namedtuple from collections import namedtuple
from awx.api.views import ( from awx.api.views import (
ApiV1RootView, ApiVersionRootView,
JobTemplateLabelList, JobTemplateLabelList,
JobTemplateSurveySpec, JobTemplateSurveySpec,
) )
@ -17,7 +17,7 @@ def mock_response_new(mocker):
return m return m
class TestApiV1RootView: class TestApiRootView:
def test_get_endpoints(self, mocker, mock_response_new): def test_get_endpoints(self, mocker, mock_response_new):
endpoints = [ endpoints = [
'authtoken', 'authtoken',
@ -51,7 +51,7 @@ class TestApiV1RootView:
'workflow_job_templates', 'workflow_job_templates',
'workflow_jobs', 'workflow_jobs',
] ]
view = ApiV1RootView() view = ApiVersionRootView()
ret = view.get(mocker.MagicMock()) ret = view.get(mocker.MagicMock())
assert ret == mock_response_new assert ret == mock_response_new

View File

@ -4,6 +4,7 @@ import json
from awx.main.tasks import RunJob from awx.main.tasks import RunJob
from awx.main.models import ( from awx.main.models import (
Job, Job,
JobTemplate,
WorkflowJobTemplate WorkflowJobTemplate
) )
@ -78,6 +79,18 @@ def test_job_args_unredacted_passwords(job):
assert extra_vars['secret_key'] == 'my_password' assert extra_vars['secret_key'] == 'my_password'
def test_update_kwargs_survey_invalid_default(survey_spec_factory):
spec = survey_spec_factory('var2')
spec['spec'][0]['required'] = False
spec['spec'][0]['min'] = 3
spec['spec'][0]['default'] = 1
jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True, extra_vars="var2: 2")
defaulted_extra_vars = jt._update_unified_job_kwargs()
assert 'extra_vars' in defaulted_extra_vars
# Make sure we did not set the invalid default of 1
assert json.loads(defaulted_extra_vars['extra_vars'])['var2'] == 2
class TestWorkflowSurveys: class TestWorkflowSurveys:
def test_update_kwargs_survey_defaults(self, survey_spec_factory): def test_update_kwargs_survey_defaults(self, survey_spec_factory):
"Assure that the survey default over-rides a JT variable" "Assure that the survey default over-rides a JT variable"

View File

@ -0,0 +1,81 @@
# Python
import pytest
from pyparsing import ParseException
# AWX
from awx.main.fields import DynamicFilterField
# Django
from django.db.models import Q
class TestDynamicFilterFieldFilterStringToQ():
@pytest.mark.parametrize("filter_string,q_expected", [
('facts__facts__blank=""', Q(facts__facts__blank="")),
('"facts__facts__ space "="f"', Q(**{ "facts__facts__ space ": "f"})),
('"facts__facts__ e "=no_quotes_here', Q(**{ "facts__facts__ e ": "no_quotes_here"})),
('a__b__c=3', Q(**{ "a__b__c": 3})),
('a__b__c=3.14', Q(**{ "a__b__c": 3.14})),
('a__b__c=true', Q(**{ "a__b__c": True})),
('a__b__c=false', Q(**{ "a__b__c": False})),
('a__b__c="true"', Q(**{ "a__b__c": "true"})),
#('"a__b\"__c"="true"', Q(**{ "a__b\"__c": "true"})),
#('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})),
])
def test_query_generated(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected)
@pytest.mark.parametrize("filter_string", [
'facts__facts__blank='
'a__b__c__ space =ggg',
])
def test_invalid_filter_strings(self, filter_string):
with pytest.raises(ParseException):
DynamicFilterField.filter_string_to_q(filter_string)
@pytest.mark.parametrize("filter_string,q_expected", [
(u'(a=abc\u1F5E3def)', Q(**{u"a": u"abc\u1F5E3def"})),
])
def test_unicode(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected)
@pytest.mark.parametrize("filter_string,q_expected", [
('(a=b)', Q(**{"a": "b"})),
('a=b and c=d', Q(**{"a": "b"}) & Q(**{"c": "d"})),
('(a=b and c=d)', Q(**{"a": "b"}) & Q(**{"c": "d"})),
('a=b or c=d', Q(**{"a": "b"}) | Q(**{"c": "d"})),
('(a=b and c=d) or (e=f)', (Q(**{"a": "b"}) & Q(**{"c": "d"})) | (Q(**{"e": "f"}))),
('(a=b) and (c=d or (e=f and (g=h or i=j))) or (y=z)', Q(**{"a": "b"}) & (Q(**{"c": "d"}) | (Q(**{"e": "f"}) & (Q(**{"g": "h"}) | Q(**{"i": "j"})))) | Q(**{"y": "z"}))
])
def test_boolean_parenthesis(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected)
@pytest.mark.parametrize("filter_string,q_expected", [
('a__b__c[]=3', Q(**{ "a__b__c__contains": 3})),
('a__b__c[]=3.14', Q(**{ "a__b__c__contains": 3.14})),
('a__b__c[]=true', Q(**{ "a__b__c__contains": True})),
('a__b__c[]=false', Q(**{ "a__b__c__contains": False})),
('a__b__c[]="true"', Q(**{ "a__b__c__contains": "true"})),
('a__b__c[]__d[]="foobar"', Q(**{ "a__b__c__contains": [{"d": ["foobar"]}]})),
('a__b__c[]__d="foobar"', Q(**{ "a__b__c__contains": [{"d": "foobar"}]})),
('a__b__c[]__d__e="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": "foobar"}}]})),
('a__b__c[]__d__e[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": ["foobar"]}}]})),
('a__b__c[]__d__e__f[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": {"f": ["foobar"]}}}]})),
('(a__b__c[]__d__e__f[]="foobar") and (a__b__c[]__d__e[]="foobar")', Q(**{ "a__b__c__contains": [{"d": {"e": {"f": ["foobar"]}}}]}) & Q(**{ "a__b__c__contains": [{"d": {"e": ["foobar"]}}]})),
#('"a__b\"__c"="true"', Q(**{ "a__b\"__c": "true"})),
#('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})),
])
def test_contains_query_generated(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected)
'''
#('"facts__quoted_val"="f\"oo"', 1),
#('facts__facts__arr[]="foo"', 1),
#('facts__facts__arr_nested[]__a[]="foo"', 1),
'''

View File

@ -2,10 +2,12 @@ from contextlib import contextmanager
import pytest import pytest
import yaml import yaml
import mock
from awx.main.models import ( from awx.main.models import (
UnifiedJob, UnifiedJob,
Notification, Notification,
ProjectUpdate
) )
from awx.main import tasks from awx.main import tasks
@ -31,6 +33,13 @@ def test_send_notifications_job_id(mocker):
assert UnifiedJob.objects.get.called_with(id=1) assert UnifiedJob.objects.get.called_with(id=1)
def test_work_success_callback_missing_job():
task_data = {'type': 'project_update', 'id': 9999}
with mock.patch('django.db.models.query.QuerySet.get') as get_mock:
get_mock.side_effect = ProjectUpdate.DoesNotExist()
assert tasks.handle_work_success(None, task_data) is None
def test_send_notifications_list(mocker): def test_send_notifications_list(mocker):
patches = list() patches = list()

View File

@ -147,7 +147,17 @@ def test_https_logging_handler_splunk_auth_info():
('http://localhost', None, 'http://localhost'), ('http://localhost', None, 'http://localhost'),
('http://localhost', 80, 'http://localhost'), ('http://localhost', 80, 'http://localhost'),
('http://localhost', 8080, 'http://localhost:8080'), ('http://localhost', 8080, 'http://localhost:8080'),
('https://localhost', 443, 'https://localhost:443') ('https://localhost', 443, 'https://localhost:443'),
('ftp://localhost', 443, 'ftp://localhost:443'),
('https://localhost:550', 443, 'https://localhost:550'),
('https://localhost:yoho/foobar', 443, 'https://localhost:443/foobar'),
('https://localhost:yoho/foobar', None, 'https://localhost:yoho/foobar'),
('http://splunk.server:8088/services/collector/event', 80,
'http://splunk.server:8088/services/collector/event'),
('http://splunk.server/services/collector/event', 80,
'http://splunk.server/services/collector/event'),
('http://splunk.server/services/collector/event', 8088,
'http://splunk.server:8088/services/collector/event'),
]) ])
def test_https_logging_handler_http_host_format(host, port, normalized): def test_https_logging_handler_http_host_format(host, port, normalized):
handler = HTTPSHandler(host=host, port=port) handler = HTTPSHandler(host=host, port=port)

View File

@ -12,7 +12,6 @@ from awx.main.utils.common import ( # noqa
encrypt_field, encrypt_field,
parse_yaml_or_json, parse_yaml_or_json,
decrypt_field, decrypt_field,
build_url,
timestamp_apiformat, timestamp_apiformat,
model_instance_diff, model_instance_diff,
model_to_dict, model_to_dict,

View File

@ -31,7 +31,6 @@ from django.db.models import ManyToManyField
from rest_framework.exceptions import ParseError, PermissionDenied from rest_framework.exceptions import ParseError, PermissionDenied
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.utils.text import slugify from django.utils.text import slugify
from django.core.urlresolvers import reverse
from django.apps import apps from django.apps import apps
# PyCrypto # PyCrypto
@ -736,14 +735,6 @@ def get_pk_from_dict(_dict, key):
return None return None
def build_url(*args, **kwargs):
get = kwargs.pop('get', {})
url = reverse(*args, **kwargs)
if get:
url += '?' + urllib.urlencode(get)
return url
def timestamp_apiformat(timestamp): def timestamp_apiformat(timestamp):
timestamp = timestamp.isoformat() timestamp = timestamp.isoformat()
if timestamp.endswith('+00:00'): if timestamp.endswith('+00:00'):

View File

@ -6,6 +6,7 @@ import logging
import json import json
import requests import requests
import time import time
import urlparse
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from copy import copy from copy import copy
from requests.exceptions import RequestException from requests.exceptions import RequestException
@ -148,10 +149,21 @@ class BaseHTTPSHandler(logging.Handler):
def get_http_host(self): def get_http_host(self):
host = self.host or '' host = self.host or ''
if not host.startswith('http'): # urlparse requires scheme to be provided, default to use http if
host = 'http://%s' % self.host # missing
if self.port != 80 and self.port is not None: if not urlparse.urlsplit(host).scheme:
host = '%s:%s' % (host, str(self.port)) host = 'http://%s' % host
parsed = urlparse.urlsplit(host)
# Insert self.port if its special and port number is either not
# given in host or given as non-numerical
try:
port = parsed.port or self.port
except ValueError:
port = self.port
if port not in (80, None):
new_netloc = '%s:%s' % (parsed.hostname, port)
return urlparse.urlunsplit((parsed.scheme, new_netloc, parsed.path,
parsed.query, parsed.fragment))
return host return host
def get_post_kwargs(self, payload_input): def get_post_kwargs(self, payload_input):

View File

@ -108,8 +108,8 @@
- name: update project using insights - name: update project using insights
uri: uri:
url: "{{insights_url}}/r/insights/v1/maintenance?ansible=true" url: "{{insights_url}}/r/insights/v1/maintenance?ansible=true"
user: "{{scm_username|quote}}" user: "{{scm_username}}"
password: "{{scm_password|quote}}" password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
when: scm_type == 'insights' when: scm_type == 'insights'
register: insights_output register: insights_output
@ -124,8 +124,8 @@
get_url: get_url:
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook" url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
dest: "{{project_path|quote}}/{{item.name}}-{{item.maintenance_id}}.yml" dest: "{{project_path|quote}}/{{item.name}}-{{item.maintenance_id}}.yml"
url_username: "{{scm_username|quote}}" url_username: "{{scm_username}}"
url_password: "{{scm_password|quote}}" url_password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
force: yes force: yes
when: scm_type == 'insights' and item.name != None when: scm_type == 'insights' and item.name != None
@ -136,8 +136,8 @@
get_url: get_url:
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook" url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
dest: "{{project_path|quote}}/insights-plan-{{item.maintenance_id}}.yml" dest: "{{project_path|quote}}/insights-plan-{{item.maintenance_id}}.yml"
url_username: "{{scm_username|quote}}" url_username: "{{scm_username}}"
url_password: "{{scm_password|quote}}" url_password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
force: yes force: yes
when: scm_type == 'insights' and item.name == None when: scm_type == 'insights' and item.name == None

View File

@ -34,7 +34,7 @@
<div class="collapse navbar-collapse" id="navbar-collapse"> <div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'api:user_me_list' %}" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Logged in as {{ user }}{% if user.get_full_name %} ({{ user.get_full_name }}){% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="visible-xs-inline">Logged in as </span>{{ user }}{% if user.get_full_name %}<span class="visible-xs-inline"> ({{ user.get_full_name }})</span>{% endif %}</a></li> <li><a href="{% url 'api:user_me_list' version=request.version %}" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Logged in as {{ user }}{% if user.get_full_name %} ({{ user.get_full_name }}){% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="visible-xs-inline">Logged in as </span>{{ user }}{% if user.get_full_name %}<span class="visible-xs-inline"> ({{ user.get_full_name }})</span>{% endif %}</a></li>
{% endif %} {% endif %}
<li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Ansible Tower API Guide' %}"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline">{% trans 'Ansible Tower API Guide' %}</span></a></li> <li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Ansible Tower API Guide' %}"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline">{% trans 'Ansible Tower API Guide' %}</span></a></li>
<li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Back to Ansible Tower' %}"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline">{% trans 'Back to Ansible Tower' %}</span></a></li> <li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Back to Ansible Tower' %}"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline">{% trans 'Back to Ansible Tower' %}</span></a></li>

View File

@ -474,6 +474,20 @@ input[type='radio']:checked:before {
outline:none; outline:none;
} }
.Form-inputLabelContainer {
width: 100%;
display: block !important;
}
.Form-inputLabelContainer[for=variables] {
width: auto;
display: inline-block !important;
}
.Form-mixedInputGroup {
width: 100%;
}
.FormToggle {} .FormToggle {}
.FormToggle-container { .FormToggle-container {
margin: 0 0 0 10px; margin: 0 0 0 10px;

View File

@ -15,7 +15,7 @@ table, tbody {
} }
.List-well { .List-well {
margin-top: 25px; margin-top: 20px;
} }
.List-table{ .List-table{
@ -118,14 +118,12 @@ table, tbody {
.List-header { .List-header {
display: flex; display: flex;
min-height: 34px;
} }
.List-title { .List-title {
align-items: center; align-items: center;
flex: 1 0 auto; flex: 1 0 auto;
display: flex; display: flex;
height: 34px;
} }
.List-titleBadge { .List-titleBadge {

View File

@ -9,7 +9,7 @@
</a> </a>
<span class="HostEvent-title">{{event.host_name}}</span> <span class="HostEvent-title">{{event.host_name}}</span>
<!-- close --> <!-- close -->
<button ui-sref="jobResult" type="button" class="close"> <button ng-click="closeHostEvent()" type="button" class="close">
<i class="fa fa-times-circle"></i> <i class="fa fa-times-circle"></i>
</button> </button>
</div> </div>
@ -64,7 +64,7 @@
<!-- controls --> <!-- controls -->
<div class="HostEvent-controls"> <div class="HostEvent-controls">
<button ui-sref="jobResult" class="btn btn-sm btn-default HostEvent-close">Close</button> <button ng-click="closeHostEvent()" class="btn btn-sm btn-default HostEvent-close">Close</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,7 +19,8 @@
var container = document.getElementById(el); var container = document.getElementById(el);
var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line
lineNumbers: true, lineNumbers: true,
mode: mode mode: mode,
readOnly: true
}); });
editor.setSize("100%", 200); editor.setSize("100%", 200);
editor.getDoc().setValue(data); editor.getDoc().setValue(data);
@ -29,6 +30,19 @@
return $state.current.name === name; return $state.current.name === name;
}; };
$scope.getActiveHostIndex = function(){
var result = $scope.hostResults.filter(function( obj ) {
return obj.id === $scope.event.id;
});
return $scope.hostResults.indexOf(result[0]);
};
$scope.closeHostEvent = function() {
// Unbind the listener so it doesn't fire when we close the modal via navigation
$('#HostEvent').off('hidden.bs.modal');
$state.go('jobDetail');
};
var init = function(){ var init = function(){
hostEvent.event_name = hostEvent.event; hostEvent.event_name = hostEvent.event;
$scope.event = _.cloneDeep(hostEvent); $scope.event = _.cloneDeep(hostEvent);
@ -81,6 +95,10 @@
} }
} }
$('#HostEvent').modal('show'); $('#HostEvent').modal('show');
$('#HostEvent').on('hidden.bs.modal', function () {
$scope.closeHostEvent();
});
}; };
init(); init();
}]; }];

View File

@ -56,6 +56,7 @@
.HostStatusBar-tooltipBadge { .HostStatusBar-tooltipBadge {
border-radius: 5px; border-radius: 5px;
border: 1px solid @default-bg;
} }
.HostStatusBar-tooltipBadge--ok { .HostStatusBar-tooltipBadge--ok {

View File

@ -38,7 +38,8 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
// used for tag search // used for tag search
$scope.list = { $scope.list = {
basePath: jobData.related.job_events basePath: jobData.related.job_events,
name: 'job_events'
}; };
// used for tag search // used for tag search
@ -450,13 +451,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
var getSkeleton = function(url) { var getSkeleton = function(url) {
jobResultsService.getEvents(url) jobResultsService.getEvents(url)
.then(events => { .then(events => {
// old job check: if the job is complete, there is result stdout, and
// there are no job events, it's an old job
if ($scope.jobFinished) {
$scope.showLegacyJobErrorMessage = $scope.job.result_stdout.length &&
!events.results.length;
}
events.results.forEach(event => { events.results.forEach(event => {
if (event.start_line === 0 && event.end_line === 0) { if (event.start_line === 0 && event.end_line === 0) {
$scope.isOld++; $scope.isOld++;

View File

@ -8,6 +8,7 @@
.License-container{ .License-container{
.OnePlusTwo-container; .OnePlusTwo-container;
margin-bottom: 20px;
} }
.License-container--missing { .License-container--missing {

View File

@ -18,7 +18,8 @@
} }
.PortalMode-filterHolder { .PortalMode-filterHolder {
position: absolute; position: absolute;
right: 35px; right: 1px;
margin-right: 25px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@ -1368,4 +1368,33 @@ function(ConfigurationUtils, i18n, $rootScope) {
}); });
} }
}; };
}])
.directive('awRequireMultiple', [function() {
return {
require: 'ngModel',
link: function postLink(scope, element, attrs, ngModel) {
// Watch for changes to the required attribute
attrs.$observe('required', function(value) {
if(value) {
ngModel.$validators.required = function (value) {
if(angular.isArray(value)) {
if(value.length === 0) {
return false;
}
else {
return (!value[0] || value[0] === "") ? false : true;
}
}
else {
return false;
}
};
}
else {
delete ngModel.$validators.required;
}
});
}
};
}]); }]);

View File

@ -794,7 +794,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += (horizontal) ? "class=\"" + getFieldWidth() + "\"" : ""; html += (horizontal) ? "class=\"" + getFieldWidth() + "\"" : "";
html += ">\n"; html += ">\n";
html += (field.clear || field.genMD5) ? "<div class=\"input-group\">\n" : ""; html += (field.clear || field.genMD5) ? "<div class=\"input-group Form-mixedInputGroup\">\n" : "";
if (field.control === null || field.control === undefined || field.control) { if (field.control === null || field.control === undefined || field.control) {
html += "<input "; html += "<input ";
@ -878,7 +878,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "\t" + label(); html += "\t" + label();
if (field.hasShowInputButton) { if (field.hasShowInputButton) {
var tooltip = i18n._("Toggle the display of plaintext."); var tooltip = i18n._("Toggle the display of plaintext.");
html += "\<div class='input-group"; html += "\<div class='input-group Form-mixedInputGroup";
html += (horizontal) ? " " + getFieldWidth() : ""; html += (horizontal) ? " " + getFieldWidth() : "";
html += "'>\n"; html += "'>\n";
// TODO: make it so that the button won't show up if the mode is edit, hasShowInputButton !== true, and there are no contents in the field. // TODO: make it so that the button won't show up if the mode is edit, hasShowInputButton !== true, and there are no contents in the field.
@ -1357,7 +1357,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += (horizontal) ? "class=\"" + getFieldWidth() + "\"" : ""; html += (horizontal) ? "class=\"" + getFieldWidth() + "\"" : "";
html += ">\n"; html += ">\n";
html += `<div class="input-group">`; html += `<div class="input-group Form-mixedInputGroup">`;
html += "<span class=\"input-group-btn\">\n"; html += "<span class=\"input-group-btn\">\n";
html += `<button type="button" class="Form-lookupButton btn btn-default" ng-click="${field.ngClick || defaultLookupNgClick}" html += `<button type="button" class="Form-lookupButton btn btn-default" ng-click="${field.ngClick || defaultLookupNgClick}"
${field.readonly || field.showonly} ${field.readonly || field.showonly}

View File

@ -10,7 +10,7 @@ export default
this.canAdd = function(apiPath) { this.canAdd = function(apiPath) {
var canAddVal = $q.defer(); var canAddVal = $q.defer();
if (apiPath.indexOf("api/v1") > -1) { if (/api\/v[0-9]+\//.test(apiPath)) {
Rest.setUrl(apiPath); Rest.setUrl(apiPath);
} else { } else {
Rest.setUrl(GetBasePath(apiPath)); Rest.setUrl(GetBasePath(apiPath));

View File

@ -35,7 +35,7 @@
.SmartSearch-searchTermContainer { .SmartSearch-searchTermContainer {
flex: initial; flex: initial;
width: 50%; width: 100%;
border: 1px solid @d7grey; border: 1px solid @d7grey;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;

View File

@ -1,5 +1,5 @@
<div> <div>
<select class="form-control SurveyMaker-previewSelect" ng-model="selectedValue" multi-select ng-required="isRequired" ng-disabled="isDisabled"> <select class="form-control SurveyMaker-previewSelect" ng-model="selectedValue" multi-select ng-required="isRequired" ng-disabled="isDisabled" aw-require-multiple>
<option ng-repeat="choice in choices" value="{{choice}}">{{choice}}</option> <option ng-repeat="choice in choices" value="{{choice}}">{{choice}}</option>
</select> </select>
</div> </div>

View File

@ -207,7 +207,7 @@ export default ['$state','moment', '$timeout', '$window',
} }
function update() { function update() {
let userCanAddEdit = (scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate; let userCanAddEdit = (scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate;
if(scope.dimensionsSet) { if(scope.dimensionsSet) {
// Declare the nodes // Declare the nodes
let nodes = tree.nodes(scope.treeData), let nodes = tree.nodes(scope.treeData),
@ -813,7 +813,7 @@ export default ['$state','moment', '$timeout', '$window',
function add_node() { function add_node() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.addNode({ scope.addNode({
parent: d, parent: d,
betweenTwoNodes: false betweenTwoNodes: false
@ -824,7 +824,7 @@ export default ['$state','moment', '$timeout', '$window',
function add_node_between() { function add_node_between() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.addNode({ scope.addNode({
parent: d, parent: d,
betweenTwoNodes: true betweenTwoNodes: true
@ -835,7 +835,7 @@ export default ['$state','moment', '$timeout', '$window',
function remove_node() { function remove_node() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.deleteNode({ scope.deleteNode({
nodeToDelete: d nodeToDelete: d
}); });

View File

@ -1,8 +1,3 @@
-e git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
-e git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
-e git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
-e git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
-e git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
apache-libcloud==1.3.0 apache-libcloud==1.3.0
appdirs==1.4.2 appdirs==1.4.2
asgi-amqp==0.4.1 asgi-amqp==0.4.1

View File

@ -4,11 +4,6 @@
# #
# pip-compile --output-file requirements/requirements.txt requirements/requirements.in # pip-compile --output-file requirements/requirements.txt requirements/requirements.in
# #
git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
adal==0.4.3 # via msrestazure adal==0.4.3 # via msrestazure
amqp==1.4.9 # via kombu amqp==1.4.9 # via kombu
anyjson==0.3.3 # via kombu anyjson==0.3.3 # via kombu

View File

@ -1,4 +1,3 @@
-e git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
apache-libcloud==1.3.0 apache-libcloud==1.3.0
azure==2.0.0rc6 azure==2.0.0rc6
backports.ssl-match-hostname==3.5.0.1 backports.ssl-match-hostname==3.5.0.1

View File

@ -4,7 +4,6 @@
# #
# pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in # pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in
# #
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
adal==0.4.3 # via msrestazure adal==0.4.3 # via msrestazure
amqp==1.4.9 # via kombu amqp==1.4.9 # via kombu
anyjson==0.3.3 # via kombu anyjson==0.3.3 # via kombu

View File

@ -0,0 +1 @@
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax

View File

@ -0,0 +1,5 @@
git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax

View File

@ -126,7 +126,8 @@ setup(
("%s" % docdir, ["docs/licenses/*",]), ("%s" % docdir, ["docs/licenses/*",]),
("%s" % bindir, ["tools/scripts/ansible-tower-service", ("%s" % bindir, ["tools/scripts/ansible-tower-service",
"tools/scripts/failure-event-handler", "tools/scripts/failure-event-handler",
"tools/scripts/tower-python"]), "tools/scripts/tower-python",
"tools/scripts/ansible-tower-setup"]),
("%s" % sosconfig, ["tools/sosreport/tower.py"])]), ("%s" % sosconfig, ["tools/sosreport/tower.py"])]),
options = { options = {
'egg_info': { 'egg_info': {

View File

@ -67,3 +67,7 @@ services:
image: postgres:9.4.1 image: postgres:9.4.1
memcached: memcached:
image: memcached:alpine image: memcached:alpine
logstash:
build:
context: ./docker-compose
dockerfile: Dockerfile-logstash

View File

@ -3,7 +3,9 @@ FROM centos:7
ADD Makefile /tmp/Makefile ADD Makefile /tmp/Makefile
RUN mkdir /tmp/requirements RUN mkdir /tmp/requirements
ADD requirements/requirements.txt \ ADD requirements/requirements.txt \
requirements/requirements_git.txt \
requirements/requirements_ansible.txt \ requirements/requirements_ansible.txt \
requirements/requirements_ansible_git.txt \
requirements/requirements_dev.txt \ requirements/requirements_dev.txt \
requirements/requirements_ansible_uninstall.txt \ requirements/requirements_ansible_uninstall.txt \
requirements/requirements_tower_uninstall.txt \ requirements/requirements_tower_uninstall.txt \

View File

@ -0,0 +1,3 @@
#!/bin/bash
exec /var/lib/awx/setup/setup.sh "$@"