diff --git a/awx/api/generics.py b/awx/api/generics.py index fca4ce3582..895880b85e 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -31,6 +31,7 @@ from awx.api.filters import FieldLookupBackend from awx.main.models import * # noqa from awx.main.utils import * # noqa from awx.api.serializers import ResourceAccessListElementSerializer +from awx.api.versioning import URLPathVersioning __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView', @@ -86,6 +87,8 @@ def get_view_description(cls, html=False): class APIView(views.APIView): + versioning_class = URLPathVersioning + def initialize_request(self, request, *args, **kwargs): ''' 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_300': getattr(self, 'new_in_300', False), 'new_in_310': getattr(self, 'new_in_310', False), + 'new_in_320': getattr(self, 'new_in_320', False), 'deprecated': getattr(self, 'deprecated', False), } @@ -188,6 +192,23 @@ class APIView(views.APIView): 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): # Base class for all model-based views. @@ -424,7 +445,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView): obj = serializer.save() 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) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 2aeafa657f..6dc20e1215 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4,9 +4,10 @@ # Python import copy import json +import logging import re import six -import logging +import urllib from collections import OrderedDict from dateutil import rrule @@ -18,7 +19,6 @@ from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError from django.db import models 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.fields import ImplicitRoleField 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) from awx.main.validators import vars_validate_or_raise from awx.conf.license import feature_enabled +from awx.api.versioning import reverse from awx.api.fields import BooleanNullField, CharNullField, ChoiceNullField, EncryptedPasswordField, VerbatimField 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. @@ -116,7 +117,7 @@ def reverse_gfk(content_object): 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'): return '' elif isinstance(obj, User): - return reverse('api:user_detail', args=(obj.pk,)) + return self.reverse('api:user_detail', kwargs={'pk': obj.pk}) else: - return obj.get_absolute_url() + return obj.get_absolute_url(request=self.context.get('request')) def _get_related(self, obj): return {} if obj is None else self.get_related(obj) @@ -278,9 +279,9 @@ class BaseSerializer(serializers.ModelSerializer): def get_related(self, obj): res = OrderedDict() 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): - 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 def _get_summary_fields(self, obj): @@ -496,6 +497,10 @@ class BaseSerializer(serializers.ModelSerializer): raise ValidationError(d) return attrs + def reverse(self, *args, **kwargs): + kwargs['request'] = self.context.get('request') + return reverse(*args, **kwargs) + class EmptySerializer(serializers.Serializer): pass @@ -525,11 +530,11 @@ class UnifiedJobTemplateSerializer(BaseSerializer): def get_related(self, obj): res = super(UnifiedJobTemplateSerializer, self).get_related(obj) 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: - 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: - 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 def get_types(self): @@ -589,19 +594,19 @@ class UnifiedJobSerializer(BaseSerializer): def get_related(self, obj): res = super(UnifiedJobSerializer, self).get_related(obj) 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: - res['schedule'] = obj.schedule.get_absolute_url() + res['schedule'] = obj.schedule.get_absolute_url(request=self.context.get('request')) 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): - 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): - res['stdout'] = reverse('api:job_stdout', args=(obj.pk,)) + res['stdout'] = self.reverse('api:job_stdout', kwargs={'pk': obj.pk}) 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: - 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 def get_summary_fields(self, obj): @@ -811,14 +816,14 @@ class UserSerializer(BaseSerializer): def get_related(self, obj): res = super(UserSerializer, self).get_related(obj) res.update(dict( - teams = reverse('api:user_teams_list', args=(obj.pk,)), - organizations = reverse('api:user_organizations_list', args=(obj.pk,)), - admin_of_organizations = reverse('api:user_admin_of_organizations_list', args=(obj.pk,)), - projects = reverse('api:user_projects_list', args=(obj.pk,)), - credentials = reverse('api:user_credentials_list', args=(obj.pk,)), - roles = reverse('api:user_roles_list', args=(obj.pk,)), - activity_stream = reverse('api:user_activity_stream_list', args=(obj.pk,)), - access_list = reverse('api:user_access_list', args=(obj.pk,)), + teams = self.reverse('api:user_teams_list', kwargs={'pk': obj.pk}), + organizations = self.reverse('api:user_organizations_list', kwargs={'pk': obj.pk}), + admin_of_organizations = self.reverse('api:user_admin_of_organizations_list', kwargs={'pk': obj.pk}), + projects = self.reverse('api:user_projects_list', kwargs={'pk': obj.pk}), + credentials = self.reverse('api:user_credentials_list', kwargs={'pk': obj.pk}), + roles = self.reverse('api:user_roles_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:user_activity_stream_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:user_access_list', kwargs={'pk': obj.pk}), )) return res @@ -864,20 +869,20 @@ class OrganizationSerializer(BaseSerializer): def get_related(self, obj): res = super(OrganizationSerializer, self).get_related(obj) res.update(dict( - projects = reverse('api:organization_projects_list', args=(obj.pk,)), - inventories = reverse('api:organization_inventories_list', args=(obj.pk,)), - workflow_job_templates = reverse('api:organization_workflow_job_templates_list', args=(obj.pk,)), - users = reverse('api:organization_users_list', args=(obj.pk,)), - admins = reverse('api:organization_admins_list', args=(obj.pk,)), - teams = reverse('api:organization_teams_list', args=(obj.pk,)), - credentials = reverse('api:organization_credential_list', args=(obj.pk,)), - activity_stream = reverse('api:organization_activity_stream_list', args=(obj.pk,)), - notification_templates = reverse('api:organization_notification_templates_list', args=(obj.pk,)), - notification_templates_any = reverse('api:organization_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:organization_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:organization_notification_templates_error_list', args=(obj.pk,)), - object_roles = reverse('api:organization_object_roles_list', args=(obj.pk,)), - access_list = reverse('api:organization_access_list', args=(obj.pk,)), + projects = self.reverse('api:organization_projects_list', kwargs={'pk': obj.pk}), + inventories = self.reverse('api:organization_inventories_list', kwargs={'pk': obj.pk}), + workflow_job_templates = self.reverse('api:organization_workflow_job_templates_list', kwargs={'pk': obj.pk}), + users = self.reverse('api:organization_users_list', kwargs={'pk': obj.pk}), + admins = self.reverse('api:organization_admins_list', kwargs={'pk': obj.pk}), + teams = self.reverse('api:organization_teams_list', kwargs={'pk': obj.pk}), + credentials = self.reverse('api:organization_credential_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:organization_activity_stream_list', kwargs={'pk': obj.pk}), + notification_templates = self.reverse('api:organization_notification_templates_list', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:organization_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:organization_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:organization_notification_templates_error_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:organization_object_roles_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:organization_access_list', kwargs={'pk': obj.pk}), )) return res @@ -903,8 +908,8 @@ class ProjectOptionsSerializer(BaseSerializer): def get_related(self, obj): res = super(ProjectOptionsSerializer, self).get_related(obj) 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}) return res def validate(self, attrs): @@ -953,28 +958,28 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): def get_related(self, obj): res = super(ProjectSerializer, self).get_related(obj) res.update(dict( - teams = reverse('api:project_teams_list', args=(obj.pk,)), - playbooks = reverse('api:project_playbooks', args=(obj.pk,)), - update = reverse('api:project_update_view', args=(obj.pk,)), - project_updates = reverse('api:project_updates_list', args=(obj.pk,)), - schedules = reverse('api:project_schedules_list', args=(obj.pk,)), - activity_stream = reverse('api:project_activity_stream_list', args=(obj.pk,)), - notification_templates_any = reverse('api:project_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:project_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:project_notification_templates_error_list', args=(obj.pk,)), - access_list = reverse('api:project_access_list', args=(obj.pk,)), - object_roles = reverse('api:project_object_roles_list', args=(obj.pk,)), + teams = self.reverse('api:project_teams_list', kwargs={'pk': obj.pk}), + playbooks = self.reverse('api:project_playbooks', kwargs={'pk': obj.pk}), + update = self.reverse('api:project_update_view', kwargs={'pk': obj.pk}), + project_updates = self.reverse('api:project_updates_list', kwargs={'pk': obj.pk}), + schedules = self.reverse('api:project_schedules_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:project_activity_stream_list', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:project_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:project_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:project_notification_templates_error_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:project_access_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:project_object_roles_list', kwargs={'pk': obj.pk}), )) 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}) # Backwards compatibility. if obj.current_update: - res['current_update'] = reverse('api:project_update_detail', - args=(obj.current_update.pk,)) + res['current_update'] = self.reverse('api:project_update_detail', + kwargs={'pk': obj.current_update.pk}) if obj.last_update: - res['last_update'] = reverse('api:project_update_detail', - args=(obj.last_update.pk,)) + res['last_update'] = self.reverse('api:project_update_detail', + kwargs={'pk': obj.last_update.pk}) return res def to_representation(self, obj): @@ -1039,9 +1044,9 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer): def get_related(self, obj): res = super(ProjectUpdateSerializer, self).get_related(obj) res.update(dict( - project = reverse('api:project_detail', args=(obj.project.pk,)), - cancel = reverse('api:project_update_cancel', args=(obj.pk,)), - notifications = reverse('api:project_update_notifications_list', args=(obj.pk,)), + project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}), + cancel = self.reverse('api:project_update_cancel', kwargs={'pk': obj.pk}), + notifications = self.reverse('api:project_update_notifications_list', kwargs={'pk': obj.pk}), )) return res @@ -1078,23 +1083,22 @@ class InventorySerializer(BaseSerializerWithVariables): def get_related(self, obj): res = super(InventorySerializer, self).get_related(obj) res.update(dict( - hosts = reverse('api:inventory_hosts_list', args=(obj.pk,)), - groups = reverse('api:inventory_groups_list', args=(obj.pk,)), - root_groups = reverse('api:inventory_root_groups_list', args=(obj.pk,)), - variable_data = reverse('api:inventory_variable_data', args=(obj.pk,)), - script = reverse('api:inventory_script_view', args=(obj.pk,)), - tree = reverse('api:inventory_tree_view', args=(obj.pk,)), - inventory_sources = reverse('api:inventory_inventory_sources_list', args=(obj.pk,)), - activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)), - job_templates = reverse('api:inventory_job_template_list', args=(obj.pk,)), - scan_job_templates = reverse('api:inventory_scan_job_template_list', args=(obj.pk,)), - ad_hoc_commands = reverse('api:inventory_ad_hoc_commands_list', args=(obj.pk,)), - access_list = reverse('api:inventory_access_list', args=(obj.pk,)), - object_roles = reverse('api:inventory_object_roles_list', args=(obj.pk,)), - #single_fact = reverse('api:inventory_single_fact_view', args=(obj.pk,)), + hosts = self.reverse('api:inventory_hosts_list', kwargs={'pk': obj.pk}), + groups = self.reverse('api:inventory_groups_list', kwargs={'pk': obj.pk}), + root_groups = self.reverse('api:inventory_root_groups_list', kwargs={'pk': obj.pk}), + variable_data = self.reverse('api:inventory_variable_data', kwargs={'pk': obj.pk}), + script = self.reverse('api:inventory_script_view', kwargs={'pk': obj.pk}), + tree = self.reverse('api:inventory_tree_view', kwargs={'pk': obj.pk}), + inventory_sources = self.reverse('api:inventory_inventory_sources_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:inventory_activity_stream_list', kwargs={'pk': obj.pk}), + job_templates = self.reverse('api:inventory_job_template_list', kwargs={'pk': obj.pk}), + scan_job_templates = self.reverse('api:inventory_scan_job_template_list', kwargs={'pk': obj.pk}), + ad_hoc_commands = self.reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:inventory_access_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:inventory_object_roles_list', kwargs={'pk': obj.pk}), )) 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 def to_representation(self, obj): @@ -1143,24 +1147,23 @@ class HostSerializer(BaseSerializerWithVariables): def get_related(self, obj): res = super(HostSerializer, self).get_related(obj) res.update(dict( - variable_data = reverse('api:host_variable_data', args=(obj.pk,)), - groups = reverse('api:host_groups_list', args=(obj.pk,)), - all_groups = reverse('api:host_all_groups_list', args=(obj.pk,)), - job_events = reverse('api:host_job_events_list', args=(obj.pk,)), - job_host_summaries = reverse('api:host_job_host_summaries_list', args=(obj.pk,)), - activity_stream = reverse('api:host_activity_stream_list', args=(obj.pk,)), - inventory_sources = reverse('api:host_inventory_sources_list', args=(obj.pk,)), - ad_hoc_commands = reverse('api:host_ad_hoc_commands_list', args=(obj.pk,)), - ad_hoc_command_events = reverse('api:host_ad_hoc_command_events_list', args=(obj.pk,)), - fact_versions = reverse('api:host_fact_versions_list', args=(obj.pk,)), - #single_fact = reverse('api:host_single_fact_view', args=(obj.pk,)), + variable_data = self.reverse('api:host_variable_data', kwargs={'pk': obj.pk}), + groups = self.reverse('api:host_groups_list', kwargs={'pk': obj.pk}), + all_groups = self.reverse('api:host_all_groups_list', kwargs={'pk': obj.pk}), + job_events = self.reverse('api:host_job_events_list', kwargs={'pk': obj.pk}), + job_host_summaries = self.reverse('api:host_job_host_summaries_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:host_activity_stream_list', kwargs={'pk': obj.pk}), + inventory_sources = self.reverse('api:host_inventory_sources_list', kwargs={'pk': obj.pk}), + ad_hoc_commands = self.reverse('api:host_ad_hoc_commands_list', kwargs={'pk': obj.pk}), + ad_hoc_command_events = self.reverse('api:host_ad_hoc_command_events_list', kwargs={'pk': obj.pk}), + fact_versions = self.reverse('api:host_fact_versions_list', kwargs={'pk': obj.pk}), )) 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: - 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: - 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 def get_summary_fields(self, obj): @@ -1253,22 +1256,21 @@ class GroupSerializer(BaseSerializerWithVariables): def get_related(self, obj): res = super(GroupSerializer, self).get_related(obj) res.update(dict( - variable_data = reverse('api:group_variable_data', args=(obj.pk,)), - hosts = reverse('api:group_hosts_list', args=(obj.pk,)), - potential_children = reverse('api:group_potential_children_list', args=(obj.pk,)), - children = reverse('api:group_children_list', args=(obj.pk,)), - all_hosts = reverse('api:group_all_hosts_list', args=(obj.pk,)), - job_events = reverse('api:group_job_events_list', args=(obj.pk,)), - job_host_summaries = reverse('api:group_job_host_summaries_list', args=(obj.pk,)), - activity_stream = reverse('api:group_activity_stream_list', args=(obj.pk,)), - inventory_sources = reverse('api:group_inventory_sources_list', args=(obj.pk,)), - ad_hoc_commands = reverse('api:group_ad_hoc_commands_list', args=(obj.pk,)), - #single_fact = reverse('api:group_single_fact_view', args=(obj.pk,)), + variable_data = self.reverse('api:group_variable_data', kwargs={'pk': obj.pk}), + hosts = self.reverse('api:group_hosts_list', kwargs={'pk': obj.pk}), + potential_children = self.reverse('api:group_potential_children_list', kwargs={'pk': obj.pk}), + children = self.reverse('api:group_children_list', kwargs={'pk': obj.pk}), + all_hosts = self.reverse('api:group_all_hosts_list', kwargs={'pk': obj.pk}), + job_events = self.reverse('api:group_job_events_list', kwargs={'pk': obj.pk}), + job_host_summaries = self.reverse('api:group_job_host_summaries_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:group_activity_stream_list', kwargs={'pk': obj.pk}), + inventory_sources = self.reverse('api:group_inventory_sources_list', kwargs={'pk': obj.pk}), + ad_hoc_commands = self.reverse('api:group_ad_hoc_commands_list', kwargs={'pk': obj.pk}), )) 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: - 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 def validate_name(self, value): @@ -1363,11 +1365,11 @@ class CustomInventoryScriptSerializer(BaseSerializer): def get_related(self, obj): res = super(CustomInventoryScriptSerializer, self).get_related(obj) 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: - res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) + res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk}) return res @@ -1381,10 +1383,10 @@ class InventorySourceOptionsSerializer(BaseSerializer): def get_related(self, obj): res = super(InventorySourceOptionsSerializer, self).get_related(obj) 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.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 def validate_source_vars(self, value): @@ -1437,27 +1439,27 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt def get_related(self, obj): res = super(InventorySourceSerializer, self).get_related(obj) res.update(dict( - update = reverse('api:inventory_source_update_view', args=(obj.pk,)), - inventory_updates = reverse('api:inventory_source_updates_list', args=(obj.pk,)), - schedules = reverse('api:inventory_source_schedules_list', args=(obj.pk,)), - activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)), - hosts = reverse('api:inventory_source_hosts_list', args=(obj.pk,)), - groups = reverse('api:inventory_source_groups_list', args=(obj.pk,)), - notification_templates_any = reverse('api:inventory_source_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:inventory_source_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:inventory_source_notification_templates_error_list', args=(obj.pk,)), + update = self.reverse('api:inventory_source_update_view', kwargs={'pk': obj.pk}), + inventory_updates = self.reverse('api:inventory_source_updates_list', kwargs={'pk': obj.pk}), + schedules = self.reverse('api:inventory_source_schedules_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:inventory_activity_stream_list', kwargs={'pk': obj.pk}), + hosts = self.reverse('api:inventory_source_hosts_list', kwargs={'pk': obj.pk}), + groups = self.reverse('api:inventory_source_groups_list', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:inventory_source_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:inventory_source_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:inventory_source_notification_templates_error_list', kwargs={'pk': obj.pk}), )) 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: - res['group'] = reverse('api:group_detail', args=(obj.group.pk,)) + res['group'] = self.reverse('api:group_detail', kwargs={'pk': obj.group.pk}) # Backwards compatibility. if obj.current_update: - res['current_update'] = reverse('api:inventory_update_detail', - args=(obj.current_update.pk,)) + res['current_update'] = self.reverse('api:inventory_update_detail', + kwargs={'pk': obj.current_update.pk}) if obj.last_update: - res['last_update'] = reverse('api:inventory_update_detail', - args=(obj.last_update.pk,)) + res['last_update'] = self.reverse('api:inventory_update_detail', + kwargs={'pk': obj.last_update.pk}) return res def to_representation(self, obj): @@ -1488,9 +1490,9 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri def get_related(self, obj): res = super(InventoryUpdateSerializer, self).get_related(obj) res.update(dict( - inventory_source = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)), - cancel = reverse('api:inventory_update_cancel', args=(obj.pk,)), - notifications = reverse('api:inventory_update_notifications_list', args=(obj.pk,)), + inventory_source = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk}), + cancel = self.reverse('api:inventory_update_cancel', kwargs={'pk': obj.pk}), + notifications = self.reverse('api:inventory_update_notifications_list', kwargs={'pk': obj.pk}), )) return res @@ -1518,16 +1520,16 @@ class TeamSerializer(BaseSerializer): def get_related(self, obj): res = super(TeamSerializer, self).get_related(obj) res.update(dict( - projects = reverse('api:team_projects_list', args=(obj.pk,)), - users = reverse('api:team_users_list', args=(obj.pk,)), - credentials = reverse('api:team_credentials_list', args=(obj.pk,)), - roles = reverse('api:team_roles_list', args=(obj.pk,)), - object_roles = reverse('api:team_object_roles_list', args=(obj.pk,)), - activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)), - access_list = reverse('api:team_access_list', args=(obj.pk,)), + projects = self.reverse('api:team_projects_list', kwargs={'pk': obj.pk}), + users = self.reverse('api:team_users_list', kwargs={'pk': obj.pk}), + credentials = self.reverse('api:team_credentials_list', kwargs={'pk': obj.pk}), + roles = self.reverse('api:team_roles_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:team_object_roles_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:team_activity_stream_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:team_access_list', kwargs={'pk': obj.pk}), )) 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 def to_representation(self, obj): @@ -1564,11 +1566,11 @@ class RoleSerializer(BaseSerializer): def get_related(self, obj): ret = super(RoleSerializer, self).get_related(obj) - ret['users'] = reverse('api:role_users_list', args=(obj.pk,)) - ret['teams'] = reverse('api:role_teams_list', args=(obj.pk,)) + ret['users'] = self.reverse('api:role_users_list', kwargs={'pk': obj.pk}) + ret['teams'] = self.reverse('api:role_teams_list', kwargs={'pk': obj.pk}) try: if obj.content_object: - ret.update(reverse_gfk(obj.content_object)) + ret.update(reverse_gfk(obj.content_object, self.context.get('request'))) except AttributeError: # AttributeError's happen if our content_object is pointing at # a model that no longer exists. This is dirty data and ideally @@ -1610,7 +1612,7 @@ class ResourceAccessListElementSerializer(UserSerializer): try: role_dict['resource_name'] = role.content_object.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: pass if role.content_type is not None: @@ -1638,7 +1640,7 @@ class ResourceAccessListElementSerializer(UserSerializer): if role.content_type is not None: role_dict['resource_name'] = role.content_object.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, 'unattach', role, team_role, 'parents', data={}, skip_sub_obj_read_check=False)} else: @@ -1717,22 +1719,22 @@ class CredentialSerializer(BaseSerializer): res = super(CredentialSerializer, self).get_related(obj) 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( - activity_stream = reverse('api:credential_activity_stream_list', args=(obj.pk,)), - access_list = reverse('api:credential_access_list', args=(obj.pk,)), - object_roles = reverse('api:credential_object_roles_list', args=(obj.pk,)), - owner_users = reverse('api:credential_owner_users_list', args=(obj.pk,)), - owner_teams = reverse('api:credential_owner_teams_list', args=(obj.pk,)), + activity_stream = self.reverse('api:credential_activity_stream_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:credential_access_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:credential_object_roles_list', kwargs={'pk': obj.pk}), + owner_users = self.reverse('api:credential_owner_users_list', kwargs={'pk': 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] 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: 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 @@ -1746,7 +1748,7 @@ class CredentialSerializer(BaseSerializer): 'type': 'user', 'name': user.username, '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]: @@ -1755,7 +1757,7 @@ class CredentialSerializer(BaseSerializer): 'type': camelcase_to_underscore(parent.content_object.__class__.__name__), 'name': parent.content_object.name, '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 @@ -1862,19 +1864,19 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer): def get_related(self, 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: - 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: - 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: - 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: - res['cloud_credential'] = reverse('api:credential_detail', - args=(obj.cloud_credential.pk,)) + res['cloud_credential'] = self.reverse('api:credential_detail', + kwargs={'pk': obj.cloud_credential.pk}) if obj.network_credential: - res['network_credential'] = reverse('api:credential_detail', - args=(obj.network_credential.pk,)) + res['network_credential'] = self.reverse('api:credential_detail', + kwargs={'pk': obj.network_credential.pk}) return res def to_representation(self, obj): @@ -1947,20 +1949,20 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO def get_related(self, obj): res = super(JobTemplateSerializer, self).get_related(obj) res.update(dict( - jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)), - schedules = reverse('api:job_template_schedules_list', args=(obj.pk,)), - activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)), - launch = reverse('api:job_template_launch', args=(obj.pk,)), - notification_templates_any = reverse('api:job_template_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:job_template_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:job_template_notification_templates_error_list', args=(obj.pk,)), - access_list = reverse('api:job_template_access_list', args=(obj.pk,)), - survey_spec = reverse('api:job_template_survey_spec', args=(obj.pk,)), - labels = reverse('api:job_template_label_list', args=(obj.pk,)), - object_roles = reverse('api:job_template_object_roles_list', args=(obj.pk,)), + jobs = self.reverse('api:job_template_jobs_list', kwargs={'pk': obj.pk}), + schedules = self.reverse('api:job_template_schedules_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:job_template_activity_stream_list', kwargs={'pk': obj.pk}), + launch = self.reverse('api:job_template_launch', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:job_template_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:job_template_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:job_template_notification_templates_error_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:job_template_access_list', kwargs={'pk': obj.pk}), + survey_spec = self.reverse('api:job_template_survey_spec', kwargs={'pk': obj.pk}), + labels = self.reverse('api:job_template_label_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:job_template_object_roles_list', kwargs={'pk': obj.pk}), )) 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 def validate(self, attrs): @@ -2015,22 +2017,22 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer): def get_related(self, obj): res = super(JobSerializer, self).get_related(obj) res.update(dict( - job_events = reverse('api:job_job_events_list', args=(obj.pk,)), - job_host_summaries = reverse('api:job_job_host_summaries_list', args=(obj.pk,)), - activity_stream = reverse('api:job_activity_stream_list', args=(obj.pk,)), - notifications = reverse('api:job_notifications_list', args=(obj.pk,)), - labels = reverse('api:job_label_list', args=(obj.pk,)), + job_events = self.reverse('api:job_job_events_list', kwargs={'pk': obj.pk}), + job_host_summaries = self.reverse('api:job_job_host_summaries_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:job_activity_stream_list', kwargs={'pk': obj.pk}), + notifications = self.reverse('api:job_notifications_list', kwargs={'pk': obj.pk}), + labels = self.reverse('api:job_label_list', kwargs={'pk': obj.pk}), )) if obj.job_template: - res['job_template'] = reverse('api:job_template_detail', - args=(obj.job_template.pk,)) + res['job_template'] = self.reverse('api:job_template_detail', + kwargs={'pk': obj.job_template.pk}) 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: - res['cancel'] = reverse('api:job_cancel', args=(obj.pk,)) + res['cancel'] = self.reverse('api:job_cancel', kwargs={'pk': obj.pk}) if obj.project_update: - res['project_update'] = reverse('api:project_update_detail', args=(obj.project_update.pk,)) - res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,)) + res['project_update'] = self.reverse('api:project_update_detail', kwargs={'pk': obj.project_update.pk}) + res['relaunch'] = self.reverse('api:job_relaunch', kwargs={'pk': obj.pk}) return res def get_artifacts(self, obj): @@ -2175,16 +2177,16 @@ class AdHocCommandSerializer(UnifiedJobSerializer): def get_related(self, obj): res = super(AdHocCommandSerializer, self).get_related(obj) 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: - 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( - events = reverse('api:ad_hoc_command_ad_hoc_command_events_list', args=(obj.pk,)), - activity_stream = reverse('api:ad_hoc_command_activity_stream_list', args=(obj.pk,)), - notifications = reverse('api:ad_hoc_command_notifications_list', args=(obj.pk,)), + events = self.reverse('api:ad_hoc_command_ad_hoc_command_events_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:ad_hoc_command_activity_stream_list', kwargs={'pk': 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['relaunch'] = reverse('api:ad_hoc_command_relaunch', args=(obj.pk,)) + res['cancel'] = self.reverse('api:ad_hoc_command_cancel', kwargs={'pk': obj.pk}) + res['relaunch'] = self.reverse('api:ad_hoc_command_relaunch', kwargs={'pk': obj.pk}) return res def to_representation(self, obj): @@ -2229,12 +2231,12 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer): def get_related(self, obj): res = super(SystemJobTemplateSerializer, self).get_related(obj) res.update(dict( - jobs = reverse('api:system_job_template_jobs_list', args=(obj.pk,)), - schedules = reverse('api:system_job_template_schedules_list', args=(obj.pk,)), - launch = reverse('api:system_job_template_launch', args=(obj.pk,)), - notification_templates_any = reverse('api:system_job_template_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:system_job_template_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:system_job_template_notification_templates_error_list', args=(obj.pk,)), + jobs = self.reverse('api:system_job_template_jobs_list', kwargs={'pk': obj.pk}), + schedules = self.reverse('api:system_job_template_schedules_list', kwargs={'pk': obj.pk}), + launch = self.reverse('api:system_job_template_launch', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:system_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:system_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:system_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}), )) return res @@ -2249,11 +2251,11 @@ class SystemJobSerializer(UnifiedJobSerializer): def get_related(self, obj): res = super(SystemJobSerializer, self).get_related(obj) if obj.system_job_template: - res['system_job_template'] = reverse('api:system_job_template_detail', - args=(obj.system_job_template.pk,)) - res['notifications'] = reverse('api:system_job_notifications_list', args=(obj.pk,)) + res['system_job_template'] = self.reverse('api:system_job_template_detail', + kwargs={'pk': obj.system_job_template.pk}) + res['notifications'] = self.reverse('api:system_job_notifications_list', kwargs={'pk': obj.pk}) 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 @@ -2275,22 +2277,22 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo def get_related(self, obj): res = super(WorkflowJobTemplateSerializer, self).get_related(obj) res.update(dict( - workflow_jobs = reverse('api:workflow_job_template_jobs_list', args=(obj.pk,)), - schedules = reverse('api:workflow_job_template_schedules_list', args=(obj.pk,)), - launch = reverse('api:workflow_job_template_launch', args=(obj.pk,)), - copy = reverse('api:workflow_job_template_copy', args=(obj.pk,)), - workflow_nodes = reverse('api:workflow_job_template_workflow_nodes_list', args=(obj.pk,)), - labels = reverse('api:workflow_job_template_label_list', args=(obj.pk,)), - activity_stream = reverse('api:workflow_job_template_activity_stream_list', args=(obj.pk,)), - notification_templates_any = reverse('api:workflow_job_template_notification_templates_any_list', args=(obj.pk,)), - notification_templates_success = reverse('api:workflow_job_template_notification_templates_success_list', args=(obj.pk,)), - notification_templates_error = reverse('api:workflow_job_template_notification_templates_error_list', args=(obj.pk,)), - access_list = reverse('api:workflow_job_template_access_list', args=(obj.pk,)), - object_roles = reverse('api:workflow_job_template_object_roles_list', args=(obj.pk,)), - survey_spec = reverse('api:workflow_job_template_survey_spec', args=(obj.pk,)), + workflow_jobs = self.reverse('api:workflow_job_template_jobs_list', kwargs={'pk': obj.pk}), + schedules = self.reverse('api:workflow_job_template_schedules_list', kwargs={'pk': obj.pk}), + launch = self.reverse('api:workflow_job_template_launch', kwargs={'pk': obj.pk}), + copy = self.reverse('api:workflow_job_template_copy', kwargs={'pk': obj.pk}), + workflow_nodes = self.reverse('api:workflow_job_template_workflow_nodes_list', kwargs={'pk': obj.pk}), + labels = self.reverse('api:workflow_job_template_label_list', kwargs={'pk': obj.pk}), + activity_stream = self.reverse('api:workflow_job_template_activity_stream_list', kwargs={'pk': obj.pk}), + notification_templates_any = self.reverse('api:workflow_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}), + notification_templates_success = self.reverse('api:workflow_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}), + notification_templates_error = self.reverse('api:workflow_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}), + access_list = self.reverse('api:workflow_job_template_access_list', kwargs={'pk': obj.pk}), + object_roles = self.reverse('api:workflow_job_template_object_roles_list', kwargs={'pk': obj.pk}), + survey_spec = self.reverse('api:workflow_job_template_survey_spec', kwargs={'pk': obj.pk}), )) 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 def validate_extra_vars(self, value): @@ -2312,15 +2314,15 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer): def get_related(self, obj): res = super(WorkflowJobSerializer, self).get_related(obj) if obj.workflow_job_template: - res['workflow_job_template'] = reverse('api:workflow_job_template_detail', - args=(obj.workflow_job_template.pk,)) - res['notifications'] = reverse('api:workflow_job_notifications_list', args=(obj.pk,)) - res['workflow_nodes'] = reverse('api:workflow_job_workflow_nodes_list', args=(obj.pk,)) - res['labels'] = reverse('api:workflow_job_label_list', args=(obj.pk,)) - res['activity_stream'] = reverse('api:workflow_job_activity_stream_list', args=(obj.pk,)) - res['relaunch'] = reverse('api:workflow_job_relaunch', args=(obj.pk,)) + res['workflow_job_template'] = self.reverse('api:workflow_job_template_detail', + kwargs={'pk': obj.workflow_job_template.pk}) + res['notifications'] = self.reverse('api:workflow_job_notifications_list', kwargs={'pk': obj.pk}) + res['workflow_nodes'] = self.reverse('api:workflow_job_workflow_nodes_list', kwargs={'pk': obj.pk}) + res['labels'] = self.reverse('api:workflow_job_label_list', kwargs={'pk': obj.pk}) + res['activity_stream'] = self.reverse('api:workflow_job_activity_stream_list', kwargs={'pk': obj.pk}) + res['relaunch'] = self.reverse('api:workflow_job_relaunch', kwargs={'pk': obj.pk}) 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 def to_representation(self, obj): @@ -2362,7 +2364,7 @@ class WorkflowNodeBaseSerializer(BaseSerializer): def get_related(self, obj): res = super(WorkflowNodeBaseSerializer, self).get_related(obj) 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 def validate(self, attrs): @@ -2380,11 +2382,11 @@ class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer): def get_related(self, obj): res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) - res['success_nodes'] = reverse('api:workflow_job_template_node_success_nodes_list', args=(obj.pk,)) - res['failure_nodes'] = reverse('api:workflow_job_template_node_failure_nodes_list', args=(obj.pk,)) - res['always_nodes'] = reverse('api:workflow_job_template_node_always_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'] = self.reverse('api:workflow_job_template_node_failure_nodes_list', kwargs={'pk': obj.pk}) + res['always_nodes'] = self.reverse('api:workflow_job_template_node_always_nodes_list', kwargs={'pk': obj.pk}) 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 def to_internal_value(self, data): @@ -2440,13 +2442,13 @@ class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer): def get_related(self, obj): res = super(WorkflowJobNodeSerializer, self).get_related(obj) - res['success_nodes'] = reverse('api:workflow_job_node_success_nodes_list', args=(obj.pk,)) - res['failure_nodes'] = reverse('api:workflow_job_node_failure_nodes_list', args=(obj.pk,)) - res['always_nodes'] = reverse('api:workflow_job_node_always_nodes_list', args=(obj.pk,)) + res['success_nodes'] = self.reverse('api:workflow_job_node_success_nodes_list', kwargs={'pk': obj.pk}) + res['failure_nodes'] = self.reverse('api:workflow_job_node_failure_nodes_list', kwargs={'pk': obj.pk}) + res['always_nodes'] = self.reverse('api:workflow_job_node_always_nodes_list', kwargs={'pk': obj.pk}) 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: - 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 @@ -2500,10 +2502,10 @@ class JobHostSummarySerializer(BaseSerializer): def get_related(self, obj): res = super(JobHostSummarySerializer, self).get_related(obj) 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: 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 @@ -2533,16 +2535,16 @@ class JobEventSerializer(BaseSerializer): def get_related(self, obj): res = super(JobEventSerializer, self).get_related(obj) 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: - 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(): - 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: - 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(): - 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 def get_summary_fields(self, obj): @@ -2582,10 +2584,10 @@ class AdHocCommandEventSerializer(BaseSerializer): def get_related(self, obj): res = super(AdHocCommandEventSerializer, self).get_related(obj) 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: - res['host'] = reverse('api:host_detail', args=(obj.host.pk,)) + res['host'] = self.reverse('api:host_detail', kwargs={'pk': obj.host.pk}) return res def to_representation(self, obj): @@ -2809,11 +2811,11 @@ class NotificationTemplateSerializer(BaseSerializer): def get_related(self, obj): res = super(NotificationTemplateSerializer, self).get_related(obj) res.update(dict( - test = reverse('api:notification_template_test', args=(obj.pk,)), - notifications = reverse('api:notification_template_notification_list', args=(obj.pk,)), + test = self.reverse('api:notification_template_test', kwargs={'pk': obj.pk}), + notifications = self.reverse('api:notification_template_notification_list', kwargs={'pk': obj.pk}), )) 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 def _recent_notifications(self, obj): @@ -2883,7 +2885,7 @@ class NotificationSerializer(BaseSerializer): def get_related(self, obj): res = super(NotificationSerializer, self).get_related(obj) 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 @@ -2897,7 +2899,7 @@ class LabelSerializer(BaseSerializer): def get_related(self, obj): res = super(LabelSerializer, self).get_related(obj) 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 @@ -2911,10 +2913,10 @@ class ScheduleSerializer(BaseSerializer): def get_related(self, obj): res = super(ScheduleSerializer, self).get_related(obj) 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: - 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 def validate_unified_job_template(self, value): @@ -3038,7 +3040,7 @@ class ActivityStreamSerializer(BaseSerializer): def get_related(self, obj): rel = {} 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: if not hasattr(obj, fk): continue @@ -3051,12 +3053,12 @@ class ActivityStreamSerializer(BaseSerializer): continue id_list.append(getattr(thisItem, 'id', None)) 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: - 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': - 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 def get_summary_fields(self, obj): @@ -3137,7 +3139,10 @@ class FactVersionSerializer(BaseFactSerializer): 'datetime': timestamp_apiformat(obj.timestamp), '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 @@ -3151,7 +3156,7 @@ class FactSerializer(BaseFactSerializer): def get_related(self, 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 def to_representation(self, obj): diff --git a/awx/api/templates/api/_new_in_awx.md b/awx/api/templates/api/_new_in_awx.md index a113b9d5fa..3b50b9d433 100644 --- a/awx/api/templates/api/_new_in_awx.md +++ b/awx/api/templates/api/_new_in_awx.md @@ -9,5 +9,6 @@ {% 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_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 %} {% endif %} diff --git a/awx/api/urls.py b/awx/api/urls.py index f3abfae3fe..9e5e0f5895 100644 --- a/awx/api/urls.py +++ b/awx/api/urls.py @@ -331,7 +331,7 @@ activity_stream_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'^config/$', 'api_v1_config_view'), url(r'^auth/$', 'auth_view'), @@ -376,5 +376,5 @@ v1_urls = patterns('awx.api.views', urlpatterns = patterns('awx.api.views', url(r'^$', 'api_root_view'), - url(r'^v1/', include(v1_urls)), + url(r'^(?P(v1|v2))/', include(v1_urls)) ) diff --git a/awx/api/versioning.py b/awx/api/versioning.py new file mode 100644 index 0000000000..5eeef2d689 --- /dev/null +++ b/awx/api/versioning.py @@ -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 + ) diff --git a/awx/api/views.py b/awx/api/views.py index 33ef14826a..ce1eeeefd3 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -20,7 +20,6 @@ from collections import OrderedDict from django.conf import settings from django.contrib.auth.models import User, AnonymousUser from django.core.cache import cache -from django.core.urlresolvers import reverse from django.core.exceptions import FieldError from django.db.models import Q, Count, F 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.generics import get_view_name 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.main.models import * # noqa from awx.main.utils import * # noqa @@ -128,17 +128,17 @@ class ApiRootView(APIView): authentication_classes = [] permission_classes = (AllowAny,) view_name = _('REST API') + versioning_class = None def get(self, request, format=None): ''' 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( description = _('Ansible Tower REST API'), - current_version = current, - available_versions = dict( - v1 = current - ), + current_version = v2, + available_versions = dict(v1 = v1, v2 = v2), ) if feature_enabled('rebranding'): data['custom_logo'] = settings.CUSTOM_LOGO @@ -146,52 +146,52 @@ class ApiRootView(APIView): return Response(data) -class ApiV1RootView(APIView): +class ApiVersionRootView(APIView): authentication_classes = [] + view_name = _('Version') permission_classes = (AllowAny,) - view_name = _('Version 1') def get(self, request, format=None): ''' list top level resources ''' data = OrderedDict() - data['authtoken'] = reverse('api:auth_token_view') - data['ping'] = reverse('api:api_v1_ping_view') - data['config'] = reverse('api:api_v1_config_view') - data['settings'] = reverse('api:setting_category_list') - data['me'] = reverse('api:user_me_list') - data['dashboard'] = reverse('api:dashboard_view') - data['organizations'] = reverse('api:organization_list') - data['users'] = reverse('api:user_list') - data['projects'] = reverse('api:project_list') - data['project_updates'] = reverse('api:project_update_list') - data['teams'] = reverse('api:team_list') - data['credentials'] = reverse('api:credential_list') - data['inventory'] = reverse('api:inventory_list') - data['inventory_scripts'] = reverse('api:inventory_script_list') - data['inventory_sources'] = reverse('api:inventory_source_list') - data['inventory_updates'] = reverse('api:inventory_update_list') - data['groups'] = reverse('api:group_list') - data['hosts'] = reverse('api:host_list') - data['job_templates'] = reverse('api:job_template_list') - data['jobs'] = reverse('api:job_list') - data['job_events'] = reverse('api:job_event_list') - data['ad_hoc_commands'] = reverse('api:ad_hoc_command_list') - data['system_job_templates'] = reverse('api:system_job_template_list') - data['system_jobs'] = reverse('api:system_job_list') - data['schedules'] = reverse('api:schedule_list') - data['roles'] = reverse('api:role_list') - data['notification_templates'] = reverse('api:notification_template_list') - data['notifications'] = reverse('api:notification_list') - data['labels'] = reverse('api:label_list') - data['unified_job_templates'] = reverse('api:unified_job_template_list') - data['unified_jobs'] = reverse('api:unified_job_list') - data['activity_stream'] = reverse('api:activity_stream_list') - data['workflow_job_templates'] = reverse('api:workflow_job_template_list') - data['workflow_jobs'] = reverse('api:workflow_job_list') - data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list') - data['workflow_job_nodes'] = reverse('api:workflow_job_node_list') + data['authtoken'] = reverse('api:auth_token_view', request=request) + data['ping'] = reverse('api:api_v1_ping_view', request=request) + data['config'] = reverse('api:api_v1_config_view', request=request) + data['settings'] = reverse('api:setting_category_list', request=request) + data['me'] = reverse('api:user_me_list', request=request) + data['dashboard'] = reverse('api:dashboard_view', request=request) + data['organizations'] = reverse('api:organization_list', request=request) + data['users'] = reverse('api:user_list', request=request) + data['projects'] = reverse('api:project_list', request=request) + data['project_updates'] = reverse('api:project_update_list', request=request) + data['teams'] = reverse('api:team_list', request=request) + data['credentials'] = reverse('api:credential_list', request=request) + data['inventory'] = reverse('api:inventory_list', request=request) + data['inventory_scripts'] = reverse('api:inventory_script_list', request=request) + data['inventory_sources'] = reverse('api:inventory_source_list', request=request) + data['inventory_updates'] = reverse('api:inventory_update_list', request=request) + data['groups'] = reverse('api:group_list', request=request) + data['hosts'] = reverse('api:host_list', request=request) + data['job_templates'] = reverse('api:job_template_list', request=request) + data['jobs'] = reverse('api:job_list', request=request) + data['job_events'] = reverse('api:job_event_list', request=request) + data['ad_hoc_commands'] = reverse('api:ad_hoc_command_list', request=request) + data['system_job_templates'] = reverse('api:system_job_template_list', request=request) + data['system_jobs'] = reverse('api:system_job_list', request=request) + data['schedules'] = reverse('api:schedule_list', request=request) + data['roles'] = reverse('api:role_list', request=request) + data['notification_templates'] = reverse('api:notification_template_list', request=request) + data['notifications'] = reverse('api:notification_list', request=request) + data['labels'] = reverse('api:label_list', request=request) + data['unified_job_templates'] = reverse('api:unified_job_template_list', request=request) + data['unified_jobs'] = reverse('api:unified_job_list', request=request) + data['activity_stream'] = reverse('api:activity_stream_list', request=request) + data['workflow_job_templates'] = reverse('api:workflow_job_template_list', request=request) + data['workflow_jobs'] = reverse('api:workflow_job_list', request=request) + data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request) + data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request) return Response(data) @@ -336,12 +336,12 @@ class DashboardView(APIView): def get(self, request, format=None): ''' Show Dashboard Details ''' 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) inventory_with_failed_hosts = user_inventory.filter(hosts_with_active_failures__gt=0) user_inventory_external = user_inventory.filter(has_inventory_sources=True) 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_with_inventory_source': user_inventory_external.count(), 'job_failed': inventory_with_failed_hosts.count(), @@ -352,13 +352,13 @@ class DashboardView(APIView): ec2_inventory_sources = user_inventory_sources.filter(source='ec2') ec2_inventory_failed = ec2_inventory_sources.filter(status='failed') 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', - '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(), 'failed': rax_inventory_failed.count()} - data['inventory_sources']['ec2'] = {'url': reverse('api:inventory_source_list') + "?source=ec2", - 'failures_url': reverse('api:inventory_source_list') + "?source=ec2&status=failed", + data['inventory_sources']['ec2'] = {'url': reverse('api:inventory_source_list', request=request) + "?source=ec2", + 'failures_url': reverse('api:inventory_source_list', request=request) + "?source=ec2&status=failed", 'label': 'Amazon EC2', 'total': ec2_inventory_sources.count(), 'failed': ec2_inventory_failed.count()} @@ -366,23 +366,23 @@ class DashboardView(APIView): 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_inventory_failed = Group.objects.filter(inventory_sources__last_job_failed=True).count() - data['groups'] = {'url': reverse('api:group_list'), - 'failures_url': reverse('api:group_list') + "?has_active_failures=True", + data['groups'] = {'url': reverse('api:group_list', request=request), + 'failures_url': reverse('api:group_list', request=request) + "?has_active_failures=True", 'total': user_groups.count(), 'job_failed': groups_job_failed, 'inventory_failed': groups_inventory_failed} user_hosts = get_user_queryset(request.user, Host) user_hosts_failed = user_hosts.filter(has_active_failures=True) - data['hosts'] = {'url': reverse('api:host_list'), - 'failures_url': reverse('api:host_list') + "?has_active_failures=True", + data['hosts'] = {'url': reverse('api:host_list', request=request), + 'failures_url': reverse('api:host_list', request=request) + "?has_active_failures=True", 'total': user_hosts.count(), 'failed': user_hosts_failed.count()} user_projects = get_user_queryset(request.user, Project) user_projects_failed = user_projects.filter(last_job_failed=True) - data['projects'] = {'url': reverse('api:project_list'), - 'failures_url': reverse('api:project_list') + "?last_job_failed=True", + data['projects'] = {'url': reverse('api:project_list', request=request), + 'failures_url': reverse('api:project_list', request=request) + "?last_job_failed=True", 'total': user_projects.count(), 'failed': user_projects_failed.count()} @@ -393,26 +393,26 @@ class DashboardView(APIView): hg_projects = user_projects.filter(scm_type='hg') hg_failed_projects = hg_projects.filter(last_job_failed=True) 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', - '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(), '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', - '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(), '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', - '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(), 'failed': hg_failed_projects.count()} user_jobs = get_user_queryset(request.user, Job) user_failed_jobs = user_jobs.filter(failed=True) - data['jobs'] = {'url': reverse('api:job_list'), - 'failure_url': reverse('api:job_list') + "?failed=True", + data['jobs'] = {'url': reverse('api:job_list', request=request), + 'failure_url': reverse('api:job_list', request=request) + "?failed=True", 'total': user_jobs.count(), 'failed': user_failed_jobs.count()} @@ -421,15 +421,15 @@ class DashboardView(APIView): credential_list = get_user_queryset(request.user, Credential) job_template_list = get_user_queryset(request.user, JobTemplate) 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()} - data['organizations'] = {'url': reverse('api:organization_list'), + data['organizations'] = {'url': reverse('api:organization_list', request=request), 'total': organization_list.count()} - data['teams'] = {'url': reverse('api:team_list'), + data['teams'] = {'url': reverse('api:team_list', request=request), 'total': team_list.count()} - data['credentials'] = {'url': reverse('api:credential_list'), + data['credentials'] = {'url': reverse('api:credential_list', request=request), '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()} return Response(data) @@ -516,6 +516,7 @@ class AuthView(APIView): new_in_240 = True def get(self, request): + from rest_framework.reverse import reverse data = OrderedDict() err_backend, err_message = request.session.get('social_auth_error', (None, None)) auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True).items() @@ -527,6 +528,7 @@ class AuthView(APIView): (not feature_enabled('enterprise_auth') and name in ['saml', 'radius']): continue + login_url = reverse('social:begin', args=(name,)) complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,))) backend_data = { @@ -1167,7 +1169,7 @@ class ProjectUpdateView(RetrieveAPIView): if not project_update: return Response({}, status=status.HTTP_400_BAD_REQUEST) else: - headers = {'Location': project_update.get_absolute_url()} + headers = {'Location': project_update.get_absolute_url(request=request)} return Response({'project_update': project_update.id}, headers=headers, status=status.HTTP_202_ACCEPTED) @@ -2273,7 +2275,7 @@ class InventorySourceUpdateView(RetrieveAPIView): if not inventory_update: return Response({}, status=status.HTTP_400_BAD_REQUEST) 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) else: return self.http_method_not_allowed(request, *args, **kwargs) @@ -2741,7 +2743,7 @@ class JobTemplateCallback(GenericAPIView): return Response(data, status=status.HTTP_400_BAD_REQUEST) # 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) @@ -3035,7 +3037,7 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView): new_workflow_job.signal_start() 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) @@ -3419,7 +3421,7 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView): data = JobSerializer(new_job, context=self.get_serializer_context()).data # Add job key to match what old relaunch returned. 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) @@ -3693,7 +3695,7 @@ class AdHocCommandRelaunch(GenericAPIView): data = AdHocCommandSerializer(new_ad_hoc_command, context=self.get_serializer_context()).data # Add ad_hoc_command key to match what was previously returned. 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) @@ -3994,7 +3996,7 @@ class NotificationTemplateTest(GenericAPIView): return Response({}, status=status.HTTP_400_BAD_REQUEST) else: send_notifications.delay([notification.id]) - headers = {'Location': notification.get_absolute_url()} + headers = {'Location': notification.get_absolute_url(request=request)} return Response({"notification": notification.id}, headers=headers, status=status.HTTP_202_ACCEPTED) diff --git a/awx/conf/views.py b/awx/conf/views.py index 68b399444e..fccb910774 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -7,7 +7,6 @@ import sys # Django from django.conf import settings -from django.core.urlresolvers import reverse from django.http import Http404 from django.utils.translation import ugettext_lazy as _ @@ -20,6 +19,7 @@ from rest_framework import status # Tower from awx.api.generics import * # noqa from awx.api.permissions import IsSuperUser +from awx.api.versioning import reverse from awx.main.utils import * # noqa from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException from awx.conf.license import get_licensed_features @@ -49,7 +49,7 @@ class SettingCategoryList(ListAPIView): else: categories = {} 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])) return setting_categories diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index b0d58fc031..d0ca5d97b8 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -1,9 +1,11 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. +# Tower +from awx.api.versioning import reverse + # Django from django.db import models -from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ __all__ = ['ActivityStream'] @@ -63,8 +65,8 @@ class ActivityStream(models.Model): label = models.ManyToManyField("Label", blank=True) role = models.ManyToManyField("Role", blank=True) - def get_absolute_url(self): - return reverse('api:activity_stream_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request) def save(self, *args, **kwargs): # For compatibility with Django 1.4.x, attempt to handle any calls to diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py index ded2cd9b0f..57fb071c53 100644 --- a/awx/main/models/ad_hoc_commands.py +++ b/awx/main/models/ad_hoc_commands.py @@ -16,9 +16,9 @@ from django.utils.text import Truncator from django.utils.timezone import utc from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse # AWX +from awx.api.versioning import reverse from awx.main.models.base import * # noqa from awx.main.models.unified_jobs import * # noqa from awx.main.models.notifications import JobNotificationMixin, NotificationTemplate @@ -143,8 +143,8 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin): from awx.main.tasks import RunAdHocCommand return RunAdHocCommand - def get_absolute_url(self): - return reverse('api:ad_hoc_command_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:ad_hoc_command_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): return urljoin(settings.TOWER_URL_BASE, "/#/ad_hoc_commands/{}".format(self.pk)) @@ -318,8 +318,8 @@ class AdHocCommandEvent(CreatedModifiedModel): editable=False, ) - def get_absolute_url(self): - return reverse('api:ad_hoc_command_event_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:ad_hoc_command_event_detail', kwargs={'pk': self.pk}, request=request) def __unicode__(self): return u'%s @ %s' % (self.get_event_display(), self.created.isoformat()) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 3342c8b750..ab4d5f3050 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -5,9 +5,9 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse # AWX +from awx.api.versioning import reverse from awx.main.fields import ImplicitRoleField from awx.main.constants import CLOUD_PROVIDERS from awx.main.utils import decrypt_field @@ -271,8 +271,8 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): needed.append(field) return needed - def get_absolute_url(self): - return reverse('api:credential_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:credential_detail', kwargs={'pk': self.pk}, request=request) def clean_host(self): """Ensure that if this is a type of credential that requires a diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 81ba4fd50b..e91e67f134 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -14,10 +14,10 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.db import transaction from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse from django.utils.timezone import now # AWX +from awx.api.versioning import reverse from awx.main.constants import CLOUD_PROVIDERS from awx.main.fields import AutoOneToOneField, ImplicitRoleField from awx.main.managers import HostManager @@ -117,9 +117,8 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): 'admin_role', ]) - def get_absolute_url(self): - return reverse('api:inventory_detail', args=(self.pk,)) - + def get_absolute_url(self, request=None): + return reverse('api:inventory_detail', kwargs={'pk': self.pk}, request=request) variables_dict = VarsDictProperty('variables') @@ -389,8 +388,8 @@ class Host(CommonModelNameNotUnique): def __unicode__(self): return self.name - def get_absolute_url(self): - return reverse('api:host_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request) def update_computed_fields(self, update_inventory=True, update_groups=True): ''' @@ -520,8 +519,8 @@ class Group(CommonModelNameNotUnique): def __unicode__(self): return self.name - def get_absolute_url(self): - return reverse('api:group_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:group_detail', kwargs={'pk': self.pk}, request=request) @transaction.atomic def delete_recursive(self): @@ -1137,8 +1136,8 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): else: return 'none' - def get_absolute_url(self): - return reverse('api:inventory_source_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:inventory_source_detail', kwargs={'pk': self.pk}, request=request) def _can_update(self): if self.source == 'custom': @@ -1246,8 +1245,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): update_fields.append('name') super(InventoryUpdate, self).save(*args, **kwargs) - def get_absolute_url(self): - return reverse('api:inventory_update_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:inventory_update_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): 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'], ) - def get_absolute_url(self): - return reverse('api:inventory_script_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 5474eefd14..72791c5c7e 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -18,9 +18,9 @@ from django.utils.encoding import force_text from django.utils.timezone import utc from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse # AWX +from awx.api.versioning import reverse from awx.main.constants import CLOUD_PROVIDERS from awx.main.models.base import * # noqa from awx.main.models.unified_jobs import * # noqa @@ -293,8 +293,8 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour ''' return self.create_unified_job(**kwargs) - def get_absolute_url(self): - return reverse('api:job_template_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:job_template_detail', kwargs={'pk': self.pk}, request=request) def can_start_without_user_input(self, callback_extra_vars=None): ''' @@ -471,8 +471,8 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin): def _get_unified_job_template_class(cls): return JobTemplate - def get_absolute_url(self): - return reverse('api:job_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:job_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): return urljoin(settings.TOWER_URL_BASE, "/#/jobs/{}".format(self.pk)) @@ -688,8 +688,8 @@ class JobHostSummary(CreatedModifiedModel): (self.host.name, self.changed, self.dark, self.failures, self.ok, self.processed, self.skipped) - def get_absolute_url(self): - return reverse('api:job_host_summary_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:job_host_summary_detail', kwargs={'pk': self.pk}, request=request) def save(self, *args, **kwargs): # If update_fields has been specified, add our field names to it, @@ -906,8 +906,8 @@ class JobEvent(CreatedModifiedModel): editable=False, ) - def get_absolute_url(self): - return reverse('api:job_event_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:job_event_detail', kwargs={'pk': self.pk}, request=request) def __unicode__(self): return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat()) @@ -1220,8 +1220,8 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions): def _get_unified_job_field_names(cls): return ['name', 'description', 'job_type', 'extra_vars'] - def get_absolute_url(self): - return reverse('api:system_job_template_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:system_job_template_detail', kwargs={'pk': self.pk}, request=request) @property def cache_timeout_blocked(self): @@ -1276,8 +1276,8 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin): def websocket_emit_data(self): return {} - def get_absolute_url(self): - return reverse('api:system_job_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:system_job_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): return urljoin(settings.TOWER_URL_BASE, "/#/management_jobs/{}".format(self.pk)) diff --git a/awx/main/models/label.py b/awx/main/models/label.py index 665d0dd98e..4bd0c04679 100644 --- a/awx/main/models/label.py +++ b/awx/main/models/label.py @@ -3,10 +3,10 @@ # Django from django.db import models -from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ # AWX +from awx.api.versioning import reverse from awx.main.models.base import CommonModelNameNotUnique from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob @@ -30,8 +30,8 @@ class Label(CommonModelNameNotUnique): on_delete=models.CASCADE, ) - def get_absolute_url(self): - return reverse('api:label_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:label_detail', kwargs={'pk': self.pk}, request=request) @staticmethod def get_orphaned_labels(): diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index 31b96aa8dd..267e84afa0 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -4,11 +4,12 @@ import logging from django.db import models -from django.core.urlresolvers import reverse from django.core.mail.message import EmailMessage from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_str +# AWX +from awx.api.versioning import reverse from awx.main.models.base import * # noqa from awx.main.utils import encrypt_field, decrypt_field from awx.main.notifications.email_backend import CustomEmailBackend @@ -56,8 +57,8 @@ class NotificationTemplate(CommonModel): notification_configuration = JSONField(blank=False) - def get_absolute_url(self): - return reverse('api:notification_template_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:notification_template_detail', kwargs={'pk': self.pk}, request=request) @property def notification_class(self): @@ -169,9 +170,9 @@ class Notification(CreatedModifiedModel): editable=False, ) body = JSONField(blank=True) - - def get_absolute_url(self): - return reverse('api:notification_detail', args=(self.pk,)) + + def get_absolute_url(self, request=None): + return reverse('api:notification_detail', kwargs={'pk': self.pk}, request=request) class JobNotificationMixin(object): diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 99023c86e1..f0b59a6fb3 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -10,12 +10,12 @@ import uuid # Django from django.conf import settings from django.db import models -from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.utils.timezone import now as tz_now from django.utils.translation import ugettext_lazy as _ # AWX +from awx.api.versioning import reverse from awx.main.fields import AutoOneToOneField, ImplicitRoleField from awx.main.models.base import * # noqa from awx.main.models.rbac import ( @@ -65,8 +65,8 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin): ) - def get_absolute_url(self): - return reverse('api:organization_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:organization_detail', kwargs={'pk': self.pk}, request=request) def __unicode__(self): return self.name @@ -109,8 +109,8 @@ class Team(CommonModelNameNotUnique, ResourceMixin): parent_role=['organization.auditor_role', 'member_role'], ) - def get_absolute_url(self): - return reverse('api:team_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:team_detail', kwargs={'pk': self.pk}, request=request) class Permission(CommonModelNameNotUnique): @@ -167,8 +167,8 @@ class Permission(CommonModelNameNotUnique): '+adhoc' if self.run_ad_hoc_commands else '', )) - def get_absolute_url(self): - return reverse('api:permission_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:permission_detail', kwargs={'pk': self.pk}, request=request) class Profile(CreatedModifiedModel): @@ -326,6 +326,6 @@ class AuthToken(BaseModel): # Add get_absolute_url method to User model if not present. if not hasattr(User, 'get_absolute_url'): - def user_get_absolute_url(user): - return reverse('api:user_detail', args=(user.pk,)) + def user_get_absolute_url(user, request=None): + return reverse('api:user_detail', kwargs={'pk': user.pk}, request=request) User.add_to_class('get_absolute_url', user_get_absolute_url) diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index bf60e5b77c..36fca1d370 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -14,10 +14,10 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_str, smart_text from django.utils.text import slugify from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse from django.utils.timezone import now, make_aware, get_default_timezone # AWX +from awx.api.versioning import reverse from awx.main.models.base import * # noqa from awx.main.models.notifications import ( NotificationTemplate, @@ -401,8 +401,8 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin): success=list(success_notification_templates), any=list(any_notification_templates)) - def get_absolute_url(self): - return reverse('api:project_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:project_detail', kwargs={'pk': self.pk}, request=request) 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): return self._result_stdout_raw_limited(start_line, end_line, redact_sensitive=redact_sensitive, escape_ascii=True) - def get_absolute_url(self): - return reverse('api:project_update_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:project_update_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): return urlparse.urljoin(settings.TOWER_URL_BASE, "/#/scm_update/{}".format(self.pk)) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 39b57b96c5..3ceec36d17 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -9,13 +9,12 @@ import re # Django from django.db import models, transaction, connection -from django.core.urlresolvers import reverse from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from django.utils.translation import ugettext_lazy as _ - # AWX +from awx.api.versioning import reverse from django.contrib.auth.models import User # noqa from awx.main.models.base import * # noqa @@ -145,8 +144,8 @@ class Role(models.Model): super(Role, self).save(*args, **kwargs) self.rebuild_role_ancestor_list([self.id], []) - def get_absolute_url(self): - return reverse('api:role_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:role_detail', kwargs={'pk': self.pk}, request=request) def __contains__(self, accessor): if type(accessor) == User: diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 21ecf49916..ae0cc12987 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -13,10 +13,10 @@ from django.utils.timezone import now, make_aware, get_default_timezone from django.utils.translation import ugettext_lazy as _ # AWX +from awx.api.versioning import reverse from awx.main.models.base import * # noqa from awx.main.utils import ignore_inventory_computed_fields from awx.main.consumers import emit_channel_notification -from django.core.urlresolvers import reverse from awx.main.fields import JSONField logger = logging.getLogger('awx.main.models.schedule') @@ -98,8 +98,8 @@ class Schedule(CommonModel): def __unicode__(self): return u'%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run) - def get_absolute_url(self): - return reverse('api:schedule_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:schedule_detail', kwargs={'pk': self.pk}, request=request) def update_computed_fields(self): future_rs = dateutil.rrule.rrulestr(self.rrule, forceset=True) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 880789aafe..98dadc941b 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -153,10 +153,10 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio related_name='%(class)s_labels' ) - def get_absolute_url(self): + def get_absolute_url(self, request=None): real_instance = self.get_real_instance() if real_instance != self: - return real_instance.get_absolute_url() + return real_instance.get_absolute_url(request=request) else: return '' diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 874822e013..c48d0636a8 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -7,10 +7,10 @@ # Django from django.db import models from django.conf import settings -from django.core.urlresolvers import reverse #from django import settings as tower_settings # AWX +from awx.api.versioning import reverse from awx.main.models import prevent_search, UnifiedJobTemplate, UnifiedJob from awx.main.models.notifications import ( NotificationTemplate, @@ -177,8 +177,8 @@ class WorkflowJobTemplateNode(WorkflowNodeBase): on_delete=models.CASCADE, ) - def get_absolute_url(self): - return reverse('api:workflow_job_template_node_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + 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): ''' @@ -223,8 +223,8 @@ class WorkflowJobNode(WorkflowNodeBase): editable=False, ) - def get_absolute_url(self): - return reverse('api:workflow_job_node_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:workflow_job_node_detail', kwargs={'pk': self.pk}, request=request) def get_job_kwargs(self): ''' @@ -365,8 +365,8 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl return (base_list + ['survey_spec', 'survey_enabled', 'organization']) - def get_absolute_url(self): - return reverse('api:workflow_job_template_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:workflow_job_template_detail', kwargs={'pk': self.pk}, request=request) @property def cache_timeout_blocked(self): @@ -467,8 +467,8 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio def socketio_emit_data(self): return {} - def get_absolute_url(self): - return reverse('api:workflow_job_detail', args=(self.pk,)) + def get_absolute_url(self, request=None): + return reverse('api:workflow_job_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): return urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.pk)) diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index c368b5f631..1396ac3b48 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -1,12 +1,11 @@ import mock import pytest +from awx.api.versioning import reverse from awx.main.middleware import ActivityStreamMiddleware from awx.main.models.activity_stream import ActivityStream from awx.main.access import ActivityStreamAccess -from django.core.urlresolvers import reverse - def mock_feature_enabled(feature): return True @@ -37,7 +36,7 @@ def test_basic_fields(monkeypatch, organization, get, user, settings): activity_stream.save() 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)) assert response.status_code == 200 @@ -64,7 +63,7 @@ def test_middleware_actor_added(monkeypatch, post, get, user, settings): org_id = response.data['id'] 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) assert response.status_code == 200 @@ -147,14 +146,14 @@ def test_stream_user_direct_role_updates(get, post, organization_factory): users=['test'], 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) activity_stream = ActivityStream.objects.filter( inventory__pk=objects.inventories.inv1.pk, user__pk=objects.users.test.pk, 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) assert response.data['object1'] == 'user' diff --git a/awx/main/tests/functional/api/test_adhoc.py b/awx/main/tests/functional/api/test_adhoc.py index 02e30afb12..c315abb1b2 100644 --- a/awx/main/tests/functional/api/test_adhoc.py +++ b/awx/main/tests/functional/api/test_adhoc.py @@ -1,7 +1,7 @@ import mock # noqa 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 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', args=(inventory.id,)), {}, 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', kwargs={'pk': inventory.id}), {}, admin, expect=201) @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) res = get(reverse('api:ad_hoc_command_list'), admin, expect=200) 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 - 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 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 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 - 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 diff --git a/awx/main/tests/functional/api/test_create_attach_views.py b/awx/main/tests/functional/api/test_create_attach_views.py index b80cb4fa2c..d6d8415916 100644 --- a/awx/main/tests/functional/api/test_create_attach_views.py +++ b/awx/main/tests/functional/api/test_create_attach_views.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @pytest.mark.django_db @@ -10,7 +10,7 @@ def test_user_role_view_access(rando, inventory, mocker, post): data = {"id": role_pk} mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) 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) mock_access.can_attach.assert_called_once_with( 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} mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) 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) mock_access.can_attach.assert_called_once_with( 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} mock_access = mocker.MagicMock(return_value=False, __name__='mocked') 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) mock_access.assert_called_once_with( 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 """ 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) # assure user is now an org member assert rando in organization.member_role diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index 458a629cce..b51d6a6dfb 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -1,7 +1,7 @@ import mock # noqa 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 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, 'name': 'Some name', 'username': 'someusername', }, alice) 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.data['count'] == 1 @@ -60,7 +60,7 @@ def test_create_user_credential_via_credentials_list_xfail(post, alice, bob): @pytest.mark.django_db 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, 'name': 'Some name', 'username': 'someusername' @@ -82,7 +82,7 @@ def test_create_team_credential(post, get, team, organization, org_admin, team_m }, org_admin) 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.data['count'] == 1 @@ -92,14 +92,14 @@ def test_create_team_credential(post, get, team, organization, org_admin, team_m @pytest.mark.django_db 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, 'name': 'Some name', 'username': 'someusername', }, org_admin) 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.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): credential.organization = organization 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 }, org_admin) 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): credential.organization = organization 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 }, org_admin) 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): credential.organization = organization 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 }, org_admin) 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): credential.organization = organization 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 }, org_admin) 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): # normal users can't do this 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 }, alice) 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): # org admins can't either 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 }, org_admin) 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 def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob): # 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 }, admin) 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): # normal users can't do this 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 }, alice) 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): # org admins can't either 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 }, org_admin) 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 def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob): # 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 }, admin) 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 credential.organization = organization 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 }, org_admin) 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 credential.organization = organization 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 }, org_admin) 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 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 - 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 }, admin) 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 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 - 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 }, admin) assert response.status_code == 400 @@ -305,7 +305,7 @@ def test_credential_detail(post, get, organization, org_admin): 'organization': organization.id, }, org_admin) 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 summary_fields = response.data['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.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.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.data['count'] == 0 diff --git a/awx/main/tests/functional/api/test_fact_versions.py b/awx/main/tests/functional/api/test_fact_versions.py index b0b861525d..145ed749d4 100644 --- a/awx/main/tests/functional/api/test_fact_versions.py +++ b/awx/main/tests/functional/api/test_fact_versions.py @@ -6,11 +6,11 @@ import urlparse import urllib # AWX +from awx.api.versioning import reverse from awx.main.models.fact import Fact from awx.main.utils import timestamp_apiformat # Django -from django.core.urlresolvers import reverse 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) 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) return (hosts[0], response) @@ -37,7 +37,7 @@ def check_url(url1_full, fact_known, module): url1 = url1_split.path 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))] assert url1 == url2 @@ -64,7 +64,7 @@ def check_system_tracking_feature_forbidden(response): @pytest.mark.license_feature def test_system_tracking_license_get(hosts, get, user): 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)) check_system_tracking_feature_forbidden(response) @@ -75,7 +75,7 @@ def test_system_tracking_license_get(hosts, get, user): @pytest.mark.license_feature def test_system_tracking_license_options(hosts, options, user): 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)) check_system_tracking_feature_forbidden(response) @@ -86,7 +86,7 @@ def test_system_tracking_license_options(hosts, options, user): @pytest.mark.license_feature def test_no_facts_db(hosts, get, user): 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_expected = { @@ -119,7 +119,7 @@ def test_basic_options_fields(hosts, fact_scans, options, user, monkeypatch_json hosts = hosts(host_count=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) 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) - 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) return response diff --git a/awx/main/tests/functional/api/test_fact_view.py b/awx/main/tests/functional/api/test_fact_view.py index de0bfbb804..f1347508d3 100644 --- a/awx/main/tests/functional/api/test_fact_view.py +++ b/awx/main/tests/functional/api/test_fact_view.py @@ -2,8 +2,8 @@ import mock import pytest import json +from awx.api.versioning import reverse from awx.main.utils import timestamp_apiformat -from django.core.urlresolvers import reverse 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) 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) 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 def test_system_tracking_license_get(hosts, get, user): 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)) check_system_tracking_feature_forbidden(response) @@ -55,7 +55,7 @@ def test_system_tracking_license_get(hosts, get, user): @pytest.mark.license_feature def test_system_tracking_license_options(hosts, options, user): 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)) check_system_tracking_feature_forbidden(response) @@ -65,7 +65,7 @@ def test_system_tracking_license_options(hosts, options, user): @pytest.mark.django_db def test_no_fact_found(hosts, get, user): 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)) expected_response = { @@ -81,7 +81,7 @@ def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d hosts = hosts(host_count=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)) 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 'description' in response.data['summary_fields']['host'] 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) @@ -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) - 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) return response diff --git a/awx/main/tests/functional/api/test_host_detail.py b/awx/main/tests/functional/api/test_host_detail.py index acfe73f4b5..d9effaa91f 100644 --- a/awx/main/tests/functional/api/test_host_detail.py +++ b/awx/main/tests/functional/api/test_host_detail.py @@ -2,17 +2,17 @@ # Other host tests should live here to make this test suite more complete. import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @pytest.mark.django_db def test_basic_fields(hosts, fact_scans, get, user): 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)) assert 'related' in response.data 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'] diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index 839cffab9e..b6f23b6b0a 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @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 cloud_is.source = 'ec2' 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) 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) 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', } if role_field: 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')) @@ -62,7 +62,7 @@ def test_create_inventory_group(post, inventory, alice, role_field, expected_sta data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -77,7 +77,7 @@ def test_create_inventory_group_child(post, group, alice, role_field, expected_s data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -92,7 +92,7 @@ def test_edit_inventory_group(put, group, alice, role_field, expected_status_cod data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -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): if role_field: 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", [ @@ -121,7 +121,7 @@ def test_create_inventory_host(post, inventory, alice, role_field, expected_stat data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -136,7 +136,7 @@ def test_create_inventory_group_host(post, group, alice, role_field, expected_st data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -151,7 +151,7 @@ def test_edit_inventory_host(put, host, alice, role_field, expected_status_code) data = { 'name': 'New name', 'description': 'Hello world', } if role_field: 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", [ @@ -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): if role_field: 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", [ @@ -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): if role_field: 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) diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index e7cd739e19..99e4e6c0fd 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -6,7 +6,7 @@ from awx.main.models.credential import Credential from awx.main.models.inventory import Inventory from awx.main.models.jobs import Job, JobTemplate -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @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('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) assert JobTemplate.create_unified_job.called 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('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) assert JobTemplate.create_unified_job.called 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('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) assert JobTemplate.create_unified_job.called 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('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) assert JobTemplate.create_unified_job.called 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) 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 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) 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) 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() 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."] @@ -219,7 +219,7 @@ def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime job_template.execute_role.members.add(rando) # 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) 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) # 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) 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) # 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) 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): # Assure that giving a new inventory for a scan job blocks the launch 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, 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('awx.api.serializers.JobSerializer.to_representation', return_value={}): 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}), admin_user, expect=201) 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.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): 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"), admin_user, expect=201, format='json') 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.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): 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"), admin_user, expect=201, format='json') assert JobTemplate.create_unified_job.called diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 95f0d5d043..09e8be11dd 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -2,11 +2,11 @@ import pytest # AWX from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer +from awx.api.versioning import reverse from awx.main.models.jobs import Job from awx.main.migrations import _save_password_keys as save_password_keys # Django -from django.core.urlresolvers import reverse from django.apps import apps @@ -56,7 +56,7 @@ def test_edit_sensitive_fields(patch, job_template_factory, alice, grant_project if grant_inventory: 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', 'project': objs.project.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', organization='org1', project='prj', inventory='inv', credential='cred' ).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) @@ -84,12 +84,12 @@ def test_edit_playbook(patch, job_template_factory, alice): objs.credential.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', }, alice, expect=200) 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', }, 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.admin_role.members.add(alice) 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, alice, expect=400 @@ -117,7 +117,7 @@ def test_edit_nonsenstive(patch, job_template_factory, alice): jt = objs.job_template 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', 'description': 'bar', 'forks': 14, @@ -157,7 +157,7 @@ def test_job_template_role_user(post, organization_factory, job_template_factory inventory='test_inv', 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) 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.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 post_data = get_response.data 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 @@ -244,7 +244,7 @@ def test_disallow_template_delete_on_running_job(job_template_factory, delete, a inventory='i', organization='o') 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 diff --git a/awx/main/tests/functional/api/test_organization_counts.py b/awx/main/tests/functional/api/test_organization_counts.py index f08fd75d01..452db26dfd 100644 --- a/awx/main/tests/functional/api/test_organization_counts.py +++ b/awx/main/tests/functional/api/test_organization_counts.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @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 external_admin = user('admin', True) response = get(reverse('api:organization_detail', - args=[resourced_organization.pk]), external_admin) + kwargs={'pk': resourced_organization.pk}), external_admin) assert response.status_code == 200 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 member_user = resourced_organization.member_role.members.get(username='org-member 1') response = get(reverse('api:organization_detail', - args=[resourced_organization.pk]), member_user) + kwargs={'pk': resourced_organization.pk}), member_user) assert response.status_code == 200 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): # Check that all types of resources are counted by a superuser 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 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 # user count, consistent with the RBAC rules 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 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): # Check that a POST to the organization list endpoint returns # 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', 'description': ''}, user=user('admin', True)) 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 external_admin = user('admin', True) 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 org_id_full = resourced_organization.id @@ -171,12 +171,12 @@ def test_scan_JT_counted(resourced_organization, user, get): counts_dict['job_templates'] += 1 # 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.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict # 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.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 # 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.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict # 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.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, 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 org_id = organization.id diff --git a/awx/main/tests/functional/api/test_organizations.py b/awx/main/tests/functional/api/test_organizations.py index 2e153c56d5..9fb0e085d2 100644 --- a/awx/main/tests/functional/api/test_organizations.py +++ b/awx/main/tests/functional/api/test_organizations.py @@ -7,7 +7,7 @@ import mock # Django -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse # AWX 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 def test_organization_access_tests(organization, get, admin, alice, bob): organization.member_role.members.add(alice) - get(reverse('api:organization_detail', args=(organization.id,)), user=admin, expect=200) - get(reverse('api:organization_detail', args=(organization.id,)), user=alice, expect=200) - get(reverse('api:organization_detail', args=(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=admin, expect=200) + get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=200) + get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=bob, expect=403) + get(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=None, expect=401) @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.member_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', args=(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=alice).data['count'] == 2 + assert get(reverse('api:organization_projects_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1 + assert get(reverse('api:organization_projects_list', kwargs={'pk': organization.id}), user=rando).status_code == 403 @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.member_role.members.add(alice) 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', args=(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_admins_list', args=(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', args=(organization.id,)), user=bob).data['count'] == 1 + assert get(reverse('api:organization_users_list', kwargs={'pk': organization.id}), user=admin).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', kwargs={'pk': organization.id}), user=bob).data['count'] == 2 + assert get(reverse('api:organization_admins_list', kwargs={'pk': organization.id}), user=admin).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', kwargs={'pk': organization.id}), user=bob).data['count'] == 1 @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.member_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', args=(organization.id,)), user=bob).data['count'] == 1 - get(reverse('api:organization_inventories_list', args=(organization.id,)), user=rando, expect=403) + assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 2 + assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1 + get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=rando, expect=403) @pytest.mark.django_db @@ -123,25 +123,25 @@ def test_create_organization_xfail(post, alice): @pytest.mark.django_db def test_add_user_to_organization(post, organization, alice, bob): 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 - 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 @pytest.mark.django_db def test_add_user_to_organization_xfail(post, organization, alice, bob): 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 def test_add_admin_to_organization(post, organization, alice, bob): 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.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.member_role @@ -149,42 +149,42 @@ def test_add_admin_to_organization(post, organization, alice, bob): @pytest.mark.django_db def test_add_admin_to_organization_xfail(post, organization, alice, bob): 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 def test_update_organization(get, put, organization, alice, bob): 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' - 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() assert organization.description == 'hi' 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 @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) 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 @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization2(delete, organization, 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 @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization_xfail1(delete, organization, 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 @mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) 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) diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py index c0a3e463cf..6463fc533e 100644 --- a/awx/main/tests/functional/api/test_rbac_displays.py +++ b/awx/main/tests/functional/api/test_rbac_displays.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse from django.test.client import RequestFactory 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): 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'] - 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'] def test_inventory_group_host_can_not_add(self, inventory, bob, options): 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'] - 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'] def test_user_list_can_add(self, org_member, org_admin, options): @@ -192,7 +192,7 @@ class TestAccessListCapabilities: inventory.admin_role.members.add(rando) 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) self._assert_one_in_list(response.data) @@ -202,7 +202,7 @@ class TestAccessListCapabilities: def test_access_list_indirect_access_capability( self, inventory, organization, org_admin, get, mocker, 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) self._assert_one_in_list(response.data, sublist='indirect_access') @@ -214,7 +214,7 @@ class TestAccessListCapabilities: team.member_role.children.add(inventory.admin_role) 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) self._assert_one_in_list(response.data) @@ -223,7 +223,7 @@ class TestAccessListCapabilities: 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" - 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'] 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) 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? 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) 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? 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 def test_team_roles_unattach_functional(team, team_member, inventory, get): 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 # the inventory admin_role grants that ability 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): organization.member_role.members.add(alice) 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 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 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']['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 mock_license_check = mocker.MagicMock() 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 diff --git a/awx/main/tests/functional/api/test_resource_access_lists.py b/awx/main/tests/functional/api/test_resource_access_lists.py index 96806d4d72..8ec0ddc264 100644 --- a/awx/main/tests/functional/api/test_resource_access_lists.py +++ b/awx/main/tests/functional/api/test_resource_access_lists.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse 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) - 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 # Result should be: diff --git a/awx/main/tests/functional/api/test_role.py b/awx/main/tests/functional/api/test_role.py index 98610bf8e4..af673509e7 100644 --- a/awx/main/tests/functional/api/test_role.py +++ b/awx/main/tests/functional/api/test_role.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @pytest.mark.django_db diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index 77b5294203..dc887826d2 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -9,10 +9,8 @@ import os # Mock import mock -# Django -from django.core.urlresolvers import reverse - # AWX +from awx.api.versioning import reverse from awx.conf.models import Setting from awx.main.utils.handlers import BaseHTTPSHandler, LoggingConnectivityException @@ -32,7 +30,7 @@ def mock_no_license_file(mocker): @pytest.mark.django_db 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) assert not response.data['LICENSE'] 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 def test_url_base_defaults_to_request(options, admin): # 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' @pytest.mark.django_db 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) delete(url, user=admin, expect=204) response = get(url, user=admin, expect=200) @@ -80,7 +78,7 @@ def test_jobs_settings(get, put, patch, delete, admin): @pytest.mark.django_db 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) Setting.objects.create(key='LICENSE', value=enterprise_license) 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 def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license, 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) 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 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) Setting.objects.create(key='LICENSE', value=enterprise_license) 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 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) assert 'CUSTOM_LOGO' not in response.data assert 'CUSTOM_LOGIN_INFO' not in response.data diff --git a/awx/main/tests/functional/api/test_survey_spec.py b/awx/main/tests/functional/api/test_survey_spec.py index f954538973..f679d6806b 100644 --- a/awx/main/tests/functional/api/test_survey_spec.py +++ b/awx/main/tests/functional/api/test_survey_spec.py @@ -2,8 +2,8 @@ import mock import pytest 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.activity_stream import ActivityStream 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): # TODO: Test non-enterprise license 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.' @@ -76,7 +76,7 @@ def test_deny_creating_with_survey(project, post, admin_user): @pytest.mark.django_db @pytest.mark.survey 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) @@ -85,7 +85,7 @@ def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user): @pytest.mark.survey def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, admin_user): 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) updated_jt = JobTemplate.objects.get(pk=job_template.pk) 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): """When a question doesn't follow the standard format, verify error thrown.""" response = post( - url=reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)), - data={"description": "Email of the submitter", - "spec": ["What is your email?"], "name": "Email survey"}, - user=admin_user, expect=400) + url=reverse('api:job_template_survey_spec', kwargs={'pk': deploy_jobtemplate.id}), + data={ + "description": "Email of the submitter", + "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." @@ -110,9 +114,11 @@ def test_survey_spec_non_dict_error(deploy_jobtemplate, post, admin_user): @pytest.mark.survey def test_survey_spec_dual_names_error(survey_spec_factory, deploy_jobtemplate, post, user): 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']), - user=user('admin', True), expect=400) + user=user('admin', True), + expect=400 + ) 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 def test_delete_survey_spec_without_license(job_template_with_survey, delete, admin_user): """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) new_jt = JobTemplate.objects.get(pk=job_template_with_survey.pk) 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.survey_enabled = True 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) 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.survey_enabled = False 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 diff --git a/awx/main/tests/functional/api/test_unified_job_template.py b/awx/main/tests/functional/api/test_unified_job_template.py index 695bd51d23..faae3cce3c 100644 --- a/awx/main/tests/functional/api/test_unified_job_template.py +++ b/awx/main/tests/functional/api/test_unified_job_template.py @@ -1,6 +1,6 @@ import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse @pytest.mark.django_db diff --git a/awx/main/tests/functional/api/test_unified_jobs_view.py b/awx/main/tests/functional/api/test_unified_jobs_view.py index 0f0a2ca956..842f195c85 100644 --- a/awx/main/tests/functional/api/test_unified_jobs_view.py +++ b/awx/main/tests/functional/api/test_unified_jobs_view.py @@ -1,7 +1,6 @@ import pytest -from django.core.urlresolvers import reverse - +from awx.api.versioning import reverse from awx.main.models import UnifiedJob, ProjectUpdate 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): for test_data in test_cases: 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 response.data is not None 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): for test_data in negative_test_cases: 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 assert response.data is not None assert test_data['uri'].username in content diff --git a/awx/main/tests/functional/api/test_user.py b/awx/main/tests/functional/api/test_user.py index e3b7b4145c..80fe4783b2 100644 --- a/awx/main/tests/functional/api/test_user.py +++ b/awx/main/tests/functional/api/test_user.py @@ -1,6 +1,6 @@ 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) 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 response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin) diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py index cfa8ce76ee..f6edd42247 100644 --- a/awx/main/tests/functional/test_notifications.py +++ b/awx/main/tests/functional/test_notifications.py @@ -1,12 +1,11 @@ import mock import pytest +from awx.api.versioning import reverse from awx.main.models.notifications import NotificationTemplate, Notification from awx.main.models.inventory import Inventory, Group from awx.main.models.jobs import JobTemplate -from django.core.urlresolvers import reverse - @pytest.mark.django_db 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"})), u) 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) assert 'related' in response.data assert 'organization' in response.data['related'] @@ -60,7 +59,7 @@ def test_encrypted_subfields(get, post, user, organization): u) assert response.status_code == 201 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) assert response.data['notification_configuration']['account_token'] == "$encrypted$" 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() jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml') 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) 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) 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) assert response.status_code == 204 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 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 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 def test_disallow_delete_when_notifications_pending(delete, user, notification_template): 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, status='pending') response = delete(url, user=u) diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py index 8b66c396bd..5be0e6c3a3 100644 --- a/awx/main/tests/functional/test_projects.py +++ b/awx/main/tests/functional/test_projects.py @@ -3,7 +3,7 @@ import mock # noqa import pytest -from django.core.urlresolvers import reverse +from awx.api.versioning import reverse 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 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 assert results['count'] == 3 assert len(results['results']) == 1 assert results['previous'] is None 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 @@ -52,10 +52,10 @@ def test_user_project_paged_list(get, organization_factory): QUERY_STRING='page=2&page_size=1').data assert len(results['results']) == 1 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'] == ( - '/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 @@ -63,7 +63,7 @@ def test_user_project_paged_list(get, organization_factory): QUERY_STRING='page=3&page_size=1').data assert len(results['results']) == 1 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 @@ -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'], ) 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 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 len(results['results']) == 1 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 @@ -98,7 +98,7 @@ def test_user_project_paged_list_with_unicode(get, organization_factory): assert results['count'] == 2 assert len(results['results']) == 1 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', 'shared project.admin_role:bob', 'shared project.admin_role:alice']) - - assert get(reverse('api:user_projects_list', args=(objects.superusers.admin.pk,)), objects.superusers.admin).data['count'] == 3 + assert get(reverse( + 'api:user_projects_list', + kwargs={'pk':objects.superusers.admin.pk,} + ), objects.superusers.admin).data['count'] == 3 # 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', args=(objects.users.bob.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', kwargs={'pk':objects.users.bob.pk,}), objects.superusers.admin).data['count'] == 2 # 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 - 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 - 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 @@ -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 # 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', args=(team2.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', kwargs={'pk':team2.pk,}), admin).data['count'] == 2 # 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 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) # 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 - assert get(reverse('api:user_projects_list', args=(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':alice.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 - 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 - 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 def test_team_project_list_fail1(get, 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 @@ -210,19 +212,22 @@ def test_create_project_null_organization_xfail(post, organization, org_admin): @pytest.mark.django_db() 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() 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 def test_cannot_schedule_manual_project(project, admin_user, post): response = post( - reverse('api:project_schedules_list', args=(project.pk,)), - {"name": "foo", "description": "", "enabled": True, + reverse('api:project_schedules_list', kwargs={'pk':project.pk,}), + { + "name": "foo", "description": "", "enabled": True, "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] diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index ef303b8b1d..a789abd99f 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -2,7 +2,7 @@ import mock # noqa import pytest 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 @@ -78,14 +78,14 @@ def test_roles_filter_visibility(get, organization, project, admin, alice, bob): Role.singleton('system_auditor').members.add(alice) 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', args=(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=admin).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', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0 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) 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 @@ -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 # ensure we don't slip up and allow this implicitly through some helper or # 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 @@ -115,7 +115,7 @@ def test_cant_delete_role(delete, admin): @pytest.mark.django_db 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) assert response.status_code == 200 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 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) assert response.status_code == 200 roles = response.data @@ -171,7 +171,7 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b @pytest.mark.django_db def test_add_role_to_user(role, post, admin): 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) assert response.status_code == 204 @@ -189,7 +189,7 @@ def test_add_role_to_user(role, post, admin): @pytest.mark.django_db def test_remove_role_from_user(role, post, admin): 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) assert response.status_code == 204 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 def test_get_teams_roles_list(get, team, organization, admin): 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) assert response.status_code == 200 roles = response.data @@ -219,7 +219,7 @@ def test_get_teams_roles_list(get, team, organization, admin): @pytest.mark.django_db def test_add_role_to_teams(team, post, admin): 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) assert response.status_code == 204 @@ -237,7 +237,7 @@ def test_add_role_to_teams(team, post, admin): @pytest.mark.django_db def test_remove_role_from_teams(team, post, admin): 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) assert response.status_code == 204 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 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) assert response.status_code == 200 assert response.data['id'] == role.id @@ -262,7 +262,7 @@ def test_get_role(get, admin, role): @pytest.mark.django_db 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) assert response.status_code == 405 #r = Role.objects.get(id=role.id) @@ -271,7 +271,7 @@ def test_put_role_405(put, admin, role): @pytest.mark.django_db 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) 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 def test_get_role_users(get, admin, role): 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) assert response.status_code == 200 assert response.data['count'] == 1 @@ -293,7 +293,7 @@ def test_get_role_users(get, admin, role): @pytest.mark.django_db 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 post(url, {'id': admin.id}, admin) 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 def test_remove_user_to_role(post, admin, role): 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 post(url, {'disassociate': True, 'id': admin.id}, admin) 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 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 @@ -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 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 @@ -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 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 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 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 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 def test_get_role_teams(get, team, admin, 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) assert response.status_code == 200 assert response.data['count'] == 1 @@ -387,7 +387,7 @@ def test_get_role_teams(get, team, admin, role): @pytest.mark.django_db 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 res = post(url, {'id': team.id}, admin) assert res.status_code == 204 @@ -397,7 +397,7 @@ def test_add_team_to_role(post, team, admin, role): @pytest.mark.django_db def test_remove_team_from_role(post, team, admin, role): 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 res = post(url, {'disassociate': True, 'id': team.id}, admin) assert res.status_code == 204 @@ -412,7 +412,7 @@ def test_remove_team_from_role(post, team, admin, role): @pytest.mark.django_db def test_role_parents(get, team, admin, 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) assert response.status_code == 200 assert response.data['count'] == 1 @@ -427,7 +427,7 @@ def test_role_parents(get, team, admin, role): @pytest.mark.django_db def test_role_children(get, team, admin, 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) assert response.status_code == 200 assert response.data['count'] == 2 @@ -441,7 +441,7 @@ def test_role_children(get, team, admin, role): @pytest.mark.django_db 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) assert response.status_code == 200 org = response.data @@ -450,7 +450,7 @@ def test_ensure_rbac_fields_are_present(organization, get, admin): assert 'object_roles' in org['summary_fields'] 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) 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 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)) assert response.status_code == 200 org = response.data diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/test_rbac_job_templates.py index ef615a09d6..fff4bb0175 100644 --- a/awx/main/tests/functional/test_rbac_job_templates.py +++ b/awx/main/tests/functional/test_rbac_job_templates.py @@ -1,6 +1,7 @@ import mock import pytest +from awx.api.versioning import reverse from awx.main.access import ( BaseAccess, JobTemplateAccess, @@ -12,7 +13,6 @@ from awx.main.models.jobs import JobTemplate from awx.main.models.schedules import Schedule from django.apps import apps -from django.core.urlresolvers import reverse @pytest.fixture @@ -250,7 +250,7 @@ def test_job_template_creator_access(project, rando, post): with mock.patch( 'awx.main.models.projects.ProjectOptions.playbooks', 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', job_type='run', ask_inventory_on_launch=True, diff --git a/awx/main/tests/unit/api/serializers/conftest.py b/awx/main/tests/unit/api/serializers/conftest.py index af35a4a002..9aa601d591 100644 --- a/awx/main/tests/unit/api/serializers/conftest.py +++ b/awx/main/tests/unit/api/serializers/conftest.py @@ -6,7 +6,7 @@ import pytest def get_related_assert(): def fn(model_obj, related, resource_name, related_resource_name): 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 diff --git a/awx/main/tests/unit/api/serializers/test_job_serializers.py b/awx/main/tests/unit/api/serializers/test_job_serializers.py index fc1ae86a8d..9cdb82c428 100644 --- a/awx/main/tests/unit/api/serializers/test_job_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_serializers.py @@ -68,7 +68,7 @@ class TestJobSerializerGetRelated(): def test_job_template_present(self, get_related_mock_and_run, job): related = get_related_mock_and_run(JobSerializer, job) 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: { diff --git a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py index 50d190693a..11ddf3a2aa 100644 --- a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py @@ -104,7 +104,7 @@ class TestJobTemplateSerializerGetSummaryFields(): serializer.show_capabilities = ['copy', 'edit'] serializer._summary_field_labels = 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 view = JobTemplateDetail() view.request = request diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py index b8697db71f..55f2015b86 100644 --- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py @@ -56,7 +56,7 @@ class TestWorkflowNodeBaseSerializerGetRelated(): 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) 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): 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): related = get_related_mock_and_run(WorkflowJobTemplateNodeSerializer, workflow_job_template_node_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): 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): related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_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): 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): related = get_related_mock_and_run(WorkflowJobNodeSerializer, workflow_job_node_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): related = WorkflowJobNodeSerializer().get_related(workflow_job_node) diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py index 3bea19cedd..030150ade3 100644 --- a/awx/main/tests/unit/api/test_views.py +++ b/awx/main/tests/unit/api/test_views.py @@ -4,7 +4,7 @@ import pytest from collections import namedtuple from awx.api.views import ( - ApiV1RootView, + ApiVersionRootView, JobTemplateLabelList, JobTemplateSurveySpec, ) @@ -17,7 +17,7 @@ def mock_response_new(mocker): return m -class TestApiV1RootView: +class TestApiRootView: def test_get_endpoints(self, mocker, mock_response_new): endpoints = [ 'authtoken', @@ -51,7 +51,7 @@ class TestApiV1RootView: 'workflow_job_templates', 'workflow_jobs', ] - view = ApiV1RootView() + view = ApiVersionRootView() ret = view.get(mocker.MagicMock()) assert ret == mock_response_new diff --git a/awx/main/utils/__init__.py b/awx/main/utils/__init__.py index 45b0a0131a..360f5a65f2 100644 --- a/awx/main/utils/__init__.py +++ b/awx/main/utils/__init__.py @@ -12,7 +12,6 @@ from awx.main.utils.common import ( # noqa encrypt_field, parse_yaml_or_json, decrypt_field, - build_url, timestamp_apiformat, model_instance_diff, model_to_dict, diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 50ccb3bf89..053e1e76cb 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -31,7 +31,6 @@ from django.db.models import ManyToManyField from rest_framework.exceptions import ParseError, PermissionDenied from django.utils.encoding import smart_str from django.utils.text import slugify -from django.core.urlresolvers import reverse from django.apps import apps # PyCrypto @@ -736,14 +735,6 @@ def get_pk_from_dict(_dict, key): 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): timestamp = timestamp.isoformat() if timestamp.endswith('+00:00'): diff --git a/awx/templates/rest_framework/api.html b/awx/templates/rest_framework/api.html index 3b75c4a35c..ae0c63c74f 100644 --- a/awx/templates/rest_framework/api.html +++ b/awx/templates/rest_framework/api.html @@ -34,7 +34,7 @@