mirror of
https://github.com/ansible/awx.git
synced 2026-01-23 07:28:02 -03:30
Merge pull request #3967 from jbradberry/upgrade-drf-3.9.4
Upgrade DRF to 3.9.4 Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
commit
e70c7ab458
@ -119,39 +119,12 @@ class LoggedLogoutView(auth_views.LogoutView):
|
||||
return ret
|
||||
|
||||
|
||||
def get_view_name(cls, suffix=None):
|
||||
'''
|
||||
Wrapper around REST framework get_view_name() to support get_name() method
|
||||
and view_name property on a view class.
|
||||
'''
|
||||
name = ''
|
||||
if hasattr(cls, 'get_name') and callable(cls.get_name):
|
||||
name = cls().get_name()
|
||||
elif hasattr(cls, 'view_name'):
|
||||
if callable(cls.view_name):
|
||||
name = cls.view_name()
|
||||
else:
|
||||
name = cls.view_name
|
||||
if name:
|
||||
return ('%s %s' % (name, suffix)) if suffix else name
|
||||
return views.get_view_name(cls, suffix=None)
|
||||
def get_view_description(view, html=False):
|
||||
'''Wrapper around REST framework get_view_description() to continue
|
||||
to support our historical div.
|
||||
|
||||
|
||||
def get_view_description(cls, request, html=False):
|
||||
'''
|
||||
Wrapper around REST framework get_view_description() to support
|
||||
get_description() method and view_description property on a view class.
|
||||
'''
|
||||
if hasattr(cls, 'get_description') and callable(cls.get_description):
|
||||
desc = cls().get_description(request, html=html)
|
||||
cls = type(cls.__name__, (object,), {'__doc__': desc})
|
||||
elif hasattr(cls, 'view_description'):
|
||||
if callable(cls.view_description):
|
||||
view_desc = cls.view_description()
|
||||
else:
|
||||
view_desc = cls.view_description
|
||||
cls = type(cls.__name__, (object,), {'__doc__': view_desc})
|
||||
desc = views.get_view_description(cls, html=html)
|
||||
desc = views.get_view_description(view, html=html)
|
||||
if html:
|
||||
desc = '<div class="description">%s</div>' % desc
|
||||
return mark_safe(desc)
|
||||
@ -264,14 +237,6 @@ class APIView(views.APIView):
|
||||
# `curl https://user:pass@tower.example.org/api/v2/job_templates/N/launch/`
|
||||
return 'Bearer realm=api authorization_url=/api/o/authorize/'
|
||||
|
||||
def get_view_description(self, html=False):
|
||||
"""
|
||||
Return some descriptive text for the view, as used in OPTIONS responses
|
||||
and in the browsable API.
|
||||
"""
|
||||
func = self.settings.VIEW_DESCRIPTION_FUNCTION
|
||||
return func(self.__class__, getattr(self, '_request', None), html)
|
||||
|
||||
def get_description_context(self):
|
||||
return {
|
||||
'view': self,
|
||||
@ -280,8 +245,8 @@ class APIView(views.APIView):
|
||||
'swagger_method': getattr(self.request, 'swagger_method', None),
|
||||
}
|
||||
|
||||
def get_description(self, request, html=False):
|
||||
self.request = request
|
||||
@property
|
||||
def description(self):
|
||||
template_list = []
|
||||
for klass in inspect.getmro(type(self)):
|
||||
template_basename = camelcase_to_underscore(klass.__name__)
|
||||
@ -383,12 +348,14 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
'model_verbose_name_plural': smart_text(self.model._meta.verbose_name_plural),
|
||||
})
|
||||
serializer = self.get_serializer()
|
||||
metadata = self.metadata_class()
|
||||
metadata.request = self.request
|
||||
for method, key in [
|
||||
('GET', 'serializer_fields'),
|
||||
('POST', 'serializer_create_fields'),
|
||||
('PUT', 'serializer_update_fields')
|
||||
]:
|
||||
d[key] = self.metadata_class().get_serializer_info(serializer, method=method)
|
||||
d[key] = metadata.get_serializer_info(serializer, method=method)
|
||||
d['settings'] = settings
|
||||
return d
|
||||
|
||||
|
||||
@ -53,7 +53,6 @@ class AutoSchema(DRFAuthSchema):
|
||||
return link
|
||||
|
||||
def get_description(self, path, method):
|
||||
self.view._request = self.view.request
|
||||
setattr(self.view.request, 'swagger_method', method)
|
||||
description = super(AutoSchema, self).get_description(path, method)
|
||||
return description
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import cgi
|
||||
import dateutil
|
||||
import functools
|
||||
import html
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
@ -37,7 +37,7 @@ from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.renderers import JSONRenderer, StaticHTMLRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import exception_handler
|
||||
from rest_framework.views import exception_handler, get_view_name
|
||||
from rest_framework import status
|
||||
|
||||
# Django REST Framework YAML
|
||||
@ -69,7 +69,7 @@ from awx.api.generics import (
|
||||
RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, SimpleListAPIView,
|
||||
SubDetailAPIView, SubListAPIView, SubListAttachDetachAPIView,
|
||||
SubListCreateAPIView, SubListCreateAttachDetachAPIView,
|
||||
SubListDestroyAPIView, get_view_name
|
||||
SubListDestroyAPIView
|
||||
)
|
||||
from awx.api.versioning import reverse
|
||||
from awx.conf.license import get_license
|
||||
@ -168,7 +168,7 @@ class DashboardView(APIView):
|
||||
|
||||
deprecated = True
|
||||
|
||||
view_name = _("Dashboard")
|
||||
name = _("Dashboard")
|
||||
swagger_topic = 'Dashboard'
|
||||
|
||||
def get(self, request, format=None):
|
||||
@ -270,7 +270,7 @@ class DashboardView(APIView):
|
||||
|
||||
class DashboardJobsGraphView(APIView):
|
||||
|
||||
view_name = _("Dashboard Jobs Graphs")
|
||||
name = _("Dashboard Jobs Graphs")
|
||||
swagger_topic = 'Jobs'
|
||||
|
||||
def get(self, request, format=None):
|
||||
@ -320,7 +320,7 @@ class DashboardJobsGraphView(APIView):
|
||||
|
||||
class InstanceList(ListAPIView):
|
||||
|
||||
view_name = _("Instances")
|
||||
name = _("Instances")
|
||||
model = models.Instance
|
||||
serializer_class = serializers.InstanceSerializer
|
||||
search_fields = ('hostname',)
|
||||
@ -328,7 +328,7 @@ class InstanceList(ListAPIView):
|
||||
|
||||
class InstanceDetail(RetrieveUpdateAPIView):
|
||||
|
||||
view_name = _("Instance Detail")
|
||||
name = _("Instance Detail")
|
||||
model = models.Instance
|
||||
serializer_class = serializers.InstanceSerializer
|
||||
|
||||
@ -345,7 +345,7 @@ class InstanceDetail(RetrieveUpdateAPIView):
|
||||
|
||||
class InstanceUnifiedJobsList(SubListAPIView):
|
||||
|
||||
view_name = _("Instance Jobs")
|
||||
name = _("Instance Jobs")
|
||||
model = models.UnifiedJob
|
||||
serializer_class = serializers.UnifiedJobListSerializer
|
||||
parent_model = models.Instance
|
||||
@ -359,7 +359,7 @@ class InstanceUnifiedJobsList(SubListAPIView):
|
||||
|
||||
class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAttachDetachAPIView):
|
||||
|
||||
view_name = _("Instance's Instance Groups")
|
||||
name = _("Instance's Instance Groups")
|
||||
model = models.InstanceGroup
|
||||
serializer_class = serializers.InstanceGroupSerializer
|
||||
parent_model = models.Instance
|
||||
@ -368,7 +368,7 @@ class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAtta
|
||||
|
||||
class InstanceGroupList(ListCreateAPIView):
|
||||
|
||||
view_name = _("Instance Groups")
|
||||
name = _("Instance Groups")
|
||||
model = models.InstanceGroup
|
||||
serializer_class = serializers.InstanceGroupSerializer
|
||||
|
||||
@ -376,7 +376,7 @@ class InstanceGroupList(ListCreateAPIView):
|
||||
class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||
|
||||
always_allow_superuser = False
|
||||
view_name = _("Instance Group Detail")
|
||||
name = _("Instance Group Detail")
|
||||
model = models.InstanceGroup
|
||||
serializer_class = serializers.InstanceGroupSerializer
|
||||
permission_classes = (InstanceGroupTowerPermission,)
|
||||
@ -392,7 +392,7 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP
|
||||
|
||||
class InstanceGroupUnifiedJobsList(SubListAPIView):
|
||||
|
||||
view_name = _("Instance Group Running Jobs")
|
||||
name = _("Instance Group Running Jobs")
|
||||
model = models.UnifiedJob
|
||||
serializer_class = serializers.UnifiedJobListSerializer
|
||||
parent_model = models.InstanceGroup
|
||||
@ -401,7 +401,7 @@ class InstanceGroupUnifiedJobsList(SubListAPIView):
|
||||
|
||||
class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetachAPIView):
|
||||
|
||||
view_name = _("Instance Group's Instances")
|
||||
name = _("Instance Group's Instances")
|
||||
model = models.Instance
|
||||
serializer_class = serializers.InstanceSerializer
|
||||
parent_model = models.InstanceGroup
|
||||
@ -411,7 +411,7 @@ class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetac
|
||||
|
||||
class ScheduleList(ListCreateAPIView):
|
||||
|
||||
view_name = _("Schedules")
|
||||
name = _("Schedules")
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
|
||||
@ -425,7 +425,7 @@ class ScheduleDetail(RetrieveUpdateDestroyAPIView):
|
||||
class SchedulePreview(GenericAPIView):
|
||||
|
||||
model = models.Schedule
|
||||
view_name = _('Schedule Recurrence Rule Preview')
|
||||
name = _('Schedule Recurrence Rule Preview')
|
||||
serializer_class = serializers.SchedulePreviewSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
@ -508,7 +508,7 @@ class ScheduleUnifiedJobsList(SubListAPIView):
|
||||
serializer_class = serializers.UnifiedJobListSerializer
|
||||
parent_model = models.Schedule
|
||||
relationship = 'unifiedjob_set'
|
||||
view_name = _('Schedule Jobs List')
|
||||
name = _('Schedule Jobs List')
|
||||
|
||||
|
||||
class AuthView(APIView):
|
||||
@ -704,7 +704,7 @@ class ProjectTeamsList(ListAPIView):
|
||||
|
||||
class ProjectSchedulesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Project Schedules")
|
||||
name = _("Project Schedules")
|
||||
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
@ -715,7 +715,7 @@ class ProjectSchedulesList(SubListCreateAPIView):
|
||||
|
||||
class ProjectScmInventorySources(SubListAPIView):
|
||||
|
||||
view_name = _("Project SCM Inventory Sources")
|
||||
name = _("Project SCM Inventory Sources")
|
||||
model = models.InventorySource
|
||||
serializer_class = serializers.InventorySourceSerializer
|
||||
parent_model = models.Project
|
||||
@ -818,7 +818,7 @@ class ProjectUpdateEventsList(SubListAPIView):
|
||||
serializer_class = serializers.ProjectUpdateEventSerializer
|
||||
parent_model = models.ProjectUpdate
|
||||
relationship = 'project_update_events'
|
||||
view_name = _('Project Update Events List')
|
||||
name = _('Project Update Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
@ -832,7 +832,7 @@ class SystemJobEventsList(SubListAPIView):
|
||||
serializer_class = serializers.SystemJobEventSerializer
|
||||
parent_model = models.SystemJob
|
||||
relationship = 'system_job_events'
|
||||
view_name = _('System Job Events List')
|
||||
name = _('System Job Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
@ -868,7 +868,7 @@ class ProjectUpdateNotificationsList(SubListAPIView):
|
||||
|
||||
class ProjectUpdateScmInventoryUpdates(SubListAPIView):
|
||||
|
||||
view_name = _("Project Update SCM Inventory Updates")
|
||||
name = _("Project Update SCM Inventory Updates")
|
||||
model = models.InventoryUpdate
|
||||
serializer_class = serializers.InventoryUpdateListSerializer
|
||||
parent_model = models.ProjectUpdate
|
||||
@ -912,7 +912,7 @@ class UserMeList(ListAPIView):
|
||||
|
||||
model = models.User
|
||||
serializer_class = serializers.UserSerializer
|
||||
view_name = _('Me')
|
||||
name = _('Me')
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(pk=self.request.user.pk)
|
||||
@ -920,7 +920,7 @@ class UserMeList(ListAPIView):
|
||||
|
||||
class OAuth2ApplicationList(ListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth 2 Applications")
|
||||
name = _("OAuth 2 Applications")
|
||||
|
||||
model = models.OAuth2Application
|
||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||
@ -929,7 +929,7 @@ class OAuth2ApplicationList(ListCreateAPIView):
|
||||
|
||||
class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
view_name = _("OAuth 2 Application Detail")
|
||||
name = _("OAuth 2 Application Detail")
|
||||
|
||||
model = models.OAuth2Application
|
||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||
@ -942,7 +942,7 @@ class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
class ApplicationOAuth2TokenList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth 2 Application Tokens")
|
||||
name = _("OAuth 2 Application Tokens")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.OAuth2TokenSerializer
|
||||
@ -964,7 +964,7 @@ class OAuth2ApplicationActivityStreamList(SubListAPIView):
|
||||
|
||||
class OAuth2TokenList(ListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth2 Tokens")
|
||||
name = _("OAuth2 Tokens")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.OAuth2TokenSerializer
|
||||
@ -973,7 +973,7 @@ class OAuth2TokenList(ListCreateAPIView):
|
||||
|
||||
class OAuth2UserTokenList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth2 User Tokens")
|
||||
name = _("OAuth2 User Tokens")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.OAuth2TokenSerializer
|
||||
@ -985,7 +985,7 @@ class OAuth2UserTokenList(SubListCreateAPIView):
|
||||
|
||||
class UserAuthorizedTokenList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth2 User Authorized Access Tokens")
|
||||
name = _("OAuth2 User Authorized Access Tokens")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.UserAuthorizedTokenSerializer
|
||||
@ -1000,7 +1000,7 @@ class UserAuthorizedTokenList(SubListCreateAPIView):
|
||||
|
||||
class OrganizationApplicationList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Organization OAuth2 Applications")
|
||||
name = _("Organization OAuth2 Applications")
|
||||
|
||||
model = models.OAuth2Application
|
||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||
@ -1012,7 +1012,7 @@ class OrganizationApplicationList(SubListCreateAPIView):
|
||||
|
||||
class UserPersonalTokenList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("OAuth2 Personal Access Tokens")
|
||||
name = _("OAuth2 Personal Access Tokens")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.UserPersonalTokenSerializer
|
||||
@ -1027,7 +1027,7 @@ class UserPersonalTokenList(SubListCreateAPIView):
|
||||
|
||||
class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
view_name = _("OAuth Token Detail")
|
||||
name = _("OAuth Token Detail")
|
||||
|
||||
model = models.OAuth2AccessToken
|
||||
serializer_class = serializers.OAuth2TokenDetailSerializer
|
||||
@ -1373,7 +1373,7 @@ class CredentialExternalTest(SubDetailAPIView):
|
||||
before saving them.
|
||||
"""
|
||||
|
||||
view_name = _('External Credential Test')
|
||||
name = _('External Credential Test')
|
||||
|
||||
model = models.Credential
|
||||
serializer_class = serializers.EmptySerializer
|
||||
@ -1399,7 +1399,7 @@ class CredentialExternalTest(SubDetailAPIView):
|
||||
|
||||
class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
view_name = _("Credential Input Source Detail")
|
||||
name = _("Credential Input Source Detail")
|
||||
|
||||
model = models.CredentialInputSource
|
||||
serializer_class = serializers.CredentialInputSourceSerializer
|
||||
@ -1407,7 +1407,7 @@ class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
class CredentialInputSourceList(ListCreateAPIView):
|
||||
|
||||
view_name = _("Credential Input Sources")
|
||||
name = _("Credential Input Sources")
|
||||
|
||||
model = models.CredentialInputSource
|
||||
serializer_class = serializers.CredentialInputSourceSerializer
|
||||
@ -1415,7 +1415,7 @@ class CredentialInputSourceList(ListCreateAPIView):
|
||||
|
||||
class CredentialInputSourceSubList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Credential Input Sources")
|
||||
name = _("Credential Input Sources")
|
||||
|
||||
model = models.CredentialInputSource
|
||||
serializer_class = serializers.CredentialInputSourceSerializer
|
||||
@ -1430,7 +1430,7 @@ class CredentialTypeExternalTest(SubDetailAPIView):
|
||||
saving it.
|
||||
"""
|
||||
|
||||
view_name = _('External Credential Type Test')
|
||||
name = _('External Credential Type Test')
|
||||
|
||||
model = models.CredentialType
|
||||
serializer_class = serializers.EmptySerializer
|
||||
@ -2004,7 +2004,7 @@ class InventoryTreeView(RetrieveAPIView):
|
||||
|
||||
class InventoryInventorySourcesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _('Inventory Source List')
|
||||
name = _('Inventory Source List')
|
||||
|
||||
model = models.InventorySource
|
||||
serializer_class = serializers.InventorySourceSerializer
|
||||
@ -2016,7 +2016,7 @@ class InventoryInventorySourcesList(SubListCreateAPIView):
|
||||
|
||||
|
||||
class InventoryInventorySourcesUpdate(RetrieveAPIView):
|
||||
view_name = _('Inventory Sources Update')
|
||||
name = _('Inventory Sources Update')
|
||||
|
||||
model = models.Inventory
|
||||
obj_permission_type = 'start'
|
||||
@ -2079,7 +2079,7 @@ class InventorySourceDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroy
|
||||
|
||||
class InventorySourceSchedulesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Inventory Source Schedules")
|
||||
name = _("Inventory Source Schedules")
|
||||
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
@ -2448,7 +2448,7 @@ class JobTemplateLaunch(RetrieveAPIView):
|
||||
|
||||
class JobTemplateSchedulesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Job Template Schedules")
|
||||
name = _("Job Template Schedules")
|
||||
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
@ -3220,7 +3220,7 @@ class WorkflowJobTemplateJobsList(SubListAPIView):
|
||||
|
||||
class WorkflowJobTemplateSchedulesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("Workflow Job Template Schedules")
|
||||
name = _("Workflow Job Template Schedules")
|
||||
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
@ -3388,7 +3388,7 @@ class SystemJobTemplateLaunch(GenericAPIView):
|
||||
|
||||
class SystemJobTemplateSchedulesList(SubListCreateAPIView):
|
||||
|
||||
view_name = _("System Job Template Schedules")
|
||||
name = _("System Job Template Schedules")
|
||||
|
||||
model = models.Schedule
|
||||
serializer_class = serializers.ScheduleSerializer
|
||||
@ -3659,7 +3659,7 @@ class BaseJobHostSummariesList(SubListAPIView):
|
||||
serializer_class = serializers.JobHostSummarySerializer
|
||||
parent_model = None # Subclasses must define this attribute.
|
||||
relationship = 'job_host_summaries'
|
||||
view_name = _('Job Host Summaries List')
|
||||
name = _('Job Host Summaries List')
|
||||
search_fields = ('host_name',)
|
||||
|
||||
def get_queryset(self):
|
||||
@ -3708,7 +3708,7 @@ class JobEventChildrenList(SubListAPIView):
|
||||
serializer_class = serializers.JobEventSerializer
|
||||
parent_model = models.JobEvent
|
||||
relationship = 'children'
|
||||
view_name = _('Job Event Children List')
|
||||
name = _('Job Event Children List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def get_queryset(self):
|
||||
@ -3724,7 +3724,7 @@ class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView):
|
||||
serializer_class = serializers.HostSerializer
|
||||
parent_model = models.JobEvent
|
||||
relationship = 'hosts'
|
||||
view_name = _('Job Event Hosts List')
|
||||
name = _('Job Event Hosts List')
|
||||
|
||||
|
||||
class BaseJobEventsList(SubListAPIView):
|
||||
@ -3733,7 +3733,7 @@ class BaseJobEventsList(SubListAPIView):
|
||||
serializer_class = serializers.JobEventSerializer
|
||||
parent_model = None # Subclasses must define this attribute.
|
||||
relationship = 'job_events'
|
||||
view_name = _('Job Events List')
|
||||
name = _('Job Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
@ -3942,7 +3942,7 @@ class BaseAdHocCommandEventsList(SubListAPIView):
|
||||
serializer_class = serializers.AdHocCommandEventSerializer
|
||||
parent_model = None # Subclasses must define this attribute.
|
||||
relationship = 'ad_hoc_command_events'
|
||||
view_name = _('Ad Hoc Command Events List')
|
||||
name = _('Ad Hoc Command Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
|
||||
@ -4100,7 +4100,7 @@ class UnifiedJobStdout(RetrieveAPIView):
|
||||
# Remove any ANSI escape sequences containing job event data.
|
||||
content = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', content)
|
||||
|
||||
body = ansiconv.to_html(cgi.escape(content))
|
||||
body = ansiconv.to_html(html.escape(content))
|
||||
|
||||
context = {
|
||||
'title': get_view_name(self.__class__),
|
||||
@ -4195,7 +4195,7 @@ class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView):
|
||||
class NotificationTemplateTest(GenericAPIView):
|
||||
'''Test a Notification Template'''
|
||||
|
||||
view_name = _('Notification Template Test')
|
||||
name = _('Notification Template Test')
|
||||
model = models.NotificationTemplate
|
||||
obj_permission_type = 'start'
|
||||
serializer_class = serializers.EmptySerializer
|
||||
|
||||
@ -60,7 +60,7 @@ class InventoryUpdateEventsList(SubListAPIView):
|
||||
serializer_class = InventoryUpdateEventSerializer
|
||||
parent_model = InventoryUpdate
|
||||
relationship = 'inventory_update_events'
|
||||
view_name = _('Inventory Update Events List')
|
||||
name = _('Inventory Update Events List')
|
||||
search_fields = ('stdout',)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
|
||||
@ -27,7 +27,7 @@ logger = logging.getLogger('awx.main.analytics')
|
||||
|
||||
class MetricsView(APIView):
|
||||
|
||||
view_name = _('Metrics')
|
||||
name = _('Metrics')
|
||||
swagger_topic = 'Metrics'
|
||||
|
||||
renderer_classes = [renderers.PlainTextRenderer,
|
||||
|
||||
@ -42,7 +42,7 @@ logger = logging.getLogger('awx.api.views.root')
|
||||
class ApiRootView(APIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
view_name = _('REST API')
|
||||
name = _('REST API')
|
||||
versioning_class = None
|
||||
swagger_topic = 'Versioning'
|
||||
|
||||
@ -64,7 +64,7 @@ class ApiRootView(APIView):
|
||||
class ApiOAuthAuthorizationRootView(APIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
view_name = _("API OAuth 2 Authorization Root")
|
||||
name = _("API OAuth 2 Authorization Root")
|
||||
versioning_class = None
|
||||
swagger_topic = 'Authentication'
|
||||
|
||||
@ -130,7 +130,7 @@ class ApiVersionRootView(APIView):
|
||||
|
||||
|
||||
class ApiV2RootView(ApiVersionRootView):
|
||||
view_name = _('Version 2')
|
||||
name = _('Version 2')
|
||||
|
||||
|
||||
class ApiV2PingView(APIView):
|
||||
@ -139,7 +139,7 @@ class ApiV2PingView(APIView):
|
||||
"""
|
||||
permission_classes = (AllowAny,)
|
||||
authentication_classes = ()
|
||||
view_name = _('Ping')
|
||||
name = _('Ping')
|
||||
swagger_topic = 'System Configuration'
|
||||
|
||||
def get(self, request, format=None):
|
||||
@ -171,7 +171,7 @@ class ApiV2PingView(APIView):
|
||||
class ApiV2ConfigView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
view_name = _('Configuration')
|
||||
name = _('Configuration')
|
||||
swagger_topic = 'System Configuration'
|
||||
|
||||
def check_permissions(self, request):
|
||||
|
||||
@ -41,7 +41,7 @@ class SettingCategoryList(ListAPIView):
|
||||
model = Setting # Not exactly, but needed for the view.
|
||||
serializer_class = SettingCategorySerializer
|
||||
filter_backends = []
|
||||
view_name = _('Setting Categories')
|
||||
name = _('Setting Categories')
|
||||
|
||||
def get_queryset(self):
|
||||
setting_categories = []
|
||||
@ -63,7 +63,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
model = Setting # Not exactly, but needed for the view.
|
||||
serializer_class = SettingSingletonSerializer
|
||||
filter_backends = []
|
||||
view_name = _('Setting Detail')
|
||||
name = _('Setting Detail')
|
||||
|
||||
def get_queryset(self):
|
||||
self.category_slug = self.kwargs.get('category_slug', 'all')
|
||||
@ -154,7 +154,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
class SettingLoggingTest(GenericAPIView):
|
||||
|
||||
view_name = _('Logging Connectivity Test')
|
||||
name = _('Logging Connectivity Test')
|
||||
model = Setting
|
||||
serializer_class = SettingSingletonSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
@ -4,7 +4,11 @@ import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from pip._internal.req import parse_requirements
|
||||
|
||||
try:
|
||||
from pip._internal.req import parse_requirements
|
||||
except ImportError:
|
||||
from pip.req import parse_requirements
|
||||
|
||||
|
||||
def test_python_and_js_licenses():
|
||||
|
||||
@ -20,10 +20,8 @@ class ApiErrorView(views.APIView):
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
metadata_class = None
|
||||
exception_class = exceptions.APIException
|
||||
view_name = _('API Error')
|
||||
|
||||
def get_view_name(self):
|
||||
return self.view_name
|
||||
name = _('API Error')
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
response = super(ApiErrorView, self).finalize_response(request, response, *args, **kwargs)
|
||||
@ -46,7 +44,7 @@ def handle_error(request, status=404, **kwargs):
|
||||
class APIException(exceptions.APIException):
|
||||
status_code = status
|
||||
default_detail = kwargs['content']
|
||||
api_error_view = ApiErrorView.as_view(view_name=kwargs['name'], exception_class=APIException)
|
||||
api_error_view = ApiErrorView.as_view(exception_class=APIException)
|
||||
response = api_error_view(request)
|
||||
if hasattr(response, 'render'):
|
||||
response.render()
|
||||
|
||||
@ -307,7 +307,6 @@ REST_FRAMEWORK = {
|
||||
),
|
||||
'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata',
|
||||
'EXCEPTION_HANDLER': 'awx.api.views.api_exception_handler',
|
||||
'VIEW_NAME_FUNCTION': 'awx.api.generics.get_view_name',
|
||||
'VIEW_DESCRIPTION_FUNCTION': 'awx.api.generics.get_view_description',
|
||||
'NON_FIELD_ERRORS_KEY': '__all__',
|
||||
'DEFAULT_VERSION': 'v2',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import collections
|
||||
import copy
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
@ -8,8 +9,8 @@ import ldap
|
||||
import awx
|
||||
|
||||
# Django
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
# Django Auth LDAP
|
||||
import django_auth_ldap.config
|
||||
@ -18,7 +19,8 @@ from django_auth_ldap.config import (
|
||||
LDAPSearchUnion,
|
||||
)
|
||||
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import empty, Field, SkipField
|
||||
|
||||
# This must be imported so get_subclasses picks it up
|
||||
from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
|
||||
@ -74,6 +76,71 @@ class DependsOnMixin():
|
||||
return res
|
||||
|
||||
|
||||
class _Forbidden(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _('Invalid field.'),
|
||||
}
|
||||
|
||||
def run_validation(self, value):
|
||||
self.fail('invalid')
|
||||
|
||||
|
||||
class HybridDictField(fields.DictField):
|
||||
"""A DictField, but with defined fixed Fields for certain keys.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.allow_blank = kwargs.pop('allow_blank', False)
|
||||
|
||||
fields = [
|
||||
(field_name, obj)
|
||||
for field_name, obj in self.__class__.__dict__.items()
|
||||
if isinstance(obj, Field) and field_name != 'child'
|
||||
]
|
||||
fields.sort(key=lambda x: x[1]._creation_counter)
|
||||
self._declared_fields = collections.OrderedDict(fields)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
fields = copy.deepcopy(self._declared_fields)
|
||||
return {
|
||||
key: field.to_representation(val) if val is not None else None
|
||||
for key, val, field in (
|
||||
(six.text_type(key), val, fields.get(key, self.child))
|
||||
for key, val in value.items()
|
||||
)
|
||||
if not field.write_only
|
||||
}
|
||||
|
||||
def run_child_validation(self, data):
|
||||
result = {}
|
||||
|
||||
if not data and self.allow_blank:
|
||||
return result
|
||||
|
||||
errors = collections.OrderedDict()
|
||||
fields = copy.deepcopy(self._declared_fields)
|
||||
keys = set(fields.keys()) | set(data.keys())
|
||||
|
||||
for key in keys:
|
||||
value = data.get(key, empty)
|
||||
key = six.text_type(key)
|
||||
field = fields.get(key, self.child)
|
||||
try:
|
||||
if field.read_only:
|
||||
continue # Ignore read_only fields, as Serializer seems to do.
|
||||
result[key] = field.run_validation(value)
|
||||
except ValidationError as e:
|
||||
errors[key] = e.detail
|
||||
except SkipField:
|
||||
pass
|
||||
|
||||
if not errors:
|
||||
return result
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
||||
class AuthenticationBackendsField(fields.StringListField):
|
||||
|
||||
# Mapping of settings that must be set in order to enable each
|
||||
@ -459,70 +526,14 @@ class LDAPDNMapField(fields.StringListBooleanField):
|
||||
child = LDAPDNField()
|
||||
|
||||
|
||||
class BaseDictWithChildField(fields.DictField):
|
||||
class LDAPSingleOrganizationMapField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing key(s): {missing_keys}.'),
|
||||
'invalid_keys': _('Invalid key(s): {invalid_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
# 'key': fields.ChildField(),
|
||||
}
|
||||
allow_unknown_keys = False
|
||||
admins = LDAPDNMapField(allow_null=True, required=False)
|
||||
users = LDAPDNMapField(allow_null=True, required=False)
|
||||
remove_admins = fields.BooleanField(required=False)
|
||||
remove_users = fields.BooleanField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.allow_blank = kwargs.pop('allow_blank', False)
|
||||
super(BaseDictWithChildField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
value = super(BaseDictWithChildField, self).to_representation(value)
|
||||
for k, v in value.items():
|
||||
child_field = self.child_fields.get(k, None)
|
||||
if child_field:
|
||||
value[k] = child_field.to_representation(v)
|
||||
elif self.allow_unknown_keys:
|
||||
value[k] = v
|
||||
return value
|
||||
|
||||
def to_internal_value(self, data):
|
||||
data = super(BaseDictWithChildField, self).to_internal_value(data)
|
||||
missing_keys = set()
|
||||
for key, child_field in self.child_fields.items():
|
||||
if not child_field.required:
|
||||
continue
|
||||
elif key not in data:
|
||||
missing_keys.add(key)
|
||||
missing_keys = sorted(list(missing_keys))
|
||||
if missing_keys and (data or not self.allow_blank):
|
||||
missing_keys = sorted(list(missing_keys))
|
||||
keys_display = json.dumps(missing_keys).lstrip('[').rstrip(']')
|
||||
self.fail('missing_keys', missing_keys=keys_display)
|
||||
if not self.allow_unknown_keys:
|
||||
invalid_keys = set(data.keys()) - set(self.child_fields.keys())
|
||||
if invalid_keys:
|
||||
invalid_keys = sorted(list(invalid_keys))
|
||||
keys_display = json.dumps(invalid_keys).lstrip('[').rstrip(']')
|
||||
self.fail('invalid_keys', invalid_keys=keys_display)
|
||||
for k, v in data.items():
|
||||
child_field = self.child_fields.get(k, None)
|
||||
if child_field:
|
||||
data[k] = child_field.run_validation(v)
|
||||
elif self.allow_unknown_keys:
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
|
||||
class LDAPSingleOrganizationMapField(BaseDictWithChildField):
|
||||
|
||||
default_error_messages = {
|
||||
'invalid_keys': _('Invalid key(s) for organization map: {invalid_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'admins': LDAPDNMapField(allow_null=True, required=False),
|
||||
'users': LDAPDNMapField(allow_null=True, required=False),
|
||||
'remove_admins': fields.BooleanField(required=False),
|
||||
'remove_users': fields.BooleanField(required=False),
|
||||
}
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class LDAPOrganizationMapField(fields.DictField):
|
||||
@ -530,17 +541,13 @@ class LDAPOrganizationMapField(fields.DictField):
|
||||
child = LDAPSingleOrganizationMapField()
|
||||
|
||||
|
||||
class LDAPSingleTeamMapField(BaseDictWithChildField):
|
||||
class LDAPSingleTeamMapField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing required key for team map: {invalid_keys}.'),
|
||||
'invalid_keys': _('Invalid key(s) for team map: {invalid_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'organization': fields.CharField(),
|
||||
'users': LDAPDNMapField(allow_null=True, required=False),
|
||||
'remove': fields.BooleanField(required=False),
|
||||
}
|
||||
organization = fields.CharField()
|
||||
users = LDAPDNMapField(allow_null=True, required=False)
|
||||
remove = fields.BooleanField(required=False)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class LDAPTeamMapField(fields.DictField):
|
||||
@ -614,17 +621,14 @@ class SocialMapField(fields.ListField):
|
||||
self.fail('type_error', input_type=type(data))
|
||||
|
||||
|
||||
class SocialSingleOrganizationMapField(BaseDictWithChildField):
|
||||
class SocialSingleOrganizationMapField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'invalid_keys': _('Invalid key(s) for organization map: {invalid_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'admins': SocialMapField(allow_null=True, required=False),
|
||||
'users': SocialMapField(allow_null=True, required=False),
|
||||
'remove_admins': fields.BooleanField(required=False),
|
||||
'remove_users': fields.BooleanField(required=False),
|
||||
}
|
||||
admins = SocialMapField(allow_null=True, required=False)
|
||||
users = SocialMapField(allow_null=True, required=False)
|
||||
remove_admins = fields.BooleanField(required=False)
|
||||
remove_users = fields.BooleanField(required=False)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class SocialOrganizationMapField(fields.DictField):
|
||||
@ -632,17 +636,13 @@ class SocialOrganizationMapField(fields.DictField):
|
||||
child = SocialSingleOrganizationMapField()
|
||||
|
||||
|
||||
class SocialSingleTeamMapField(BaseDictWithChildField):
|
||||
class SocialSingleTeamMapField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing required key for team map: {missing_keys}.'),
|
||||
'invalid_keys': _('Invalid key(s) for team map: {invalid_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'organization': fields.CharField(),
|
||||
'users': SocialMapField(allow_null=True, required=False),
|
||||
'remove': fields.BooleanField(required=False),
|
||||
}
|
||||
organization = fields.CharField()
|
||||
users = SocialMapField(allow_null=True, required=False)
|
||||
remove = fields.BooleanField(required=False)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class SocialTeamMapField(fields.DictField):
|
||||
@ -650,17 +650,11 @@ class SocialTeamMapField(fields.DictField):
|
||||
child = SocialSingleTeamMapField()
|
||||
|
||||
|
||||
class SAMLOrgInfoValueField(BaseDictWithChildField):
|
||||
class SAMLOrgInfoValueField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing required key(s) for org info record: {missing_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'name': fields.CharField(),
|
||||
'displayname': fields.CharField(),
|
||||
'url': fields.URLField(),
|
||||
}
|
||||
allow_unknown_keys = True
|
||||
name = fields.CharField()
|
||||
displayname = fields.CharField()
|
||||
url = fields.URLField()
|
||||
|
||||
|
||||
class SAMLOrgInfoField(fields.DictField):
|
||||
@ -683,34 +677,22 @@ class SAMLOrgInfoField(fields.DictField):
|
||||
return data
|
||||
|
||||
|
||||
class SAMLContactField(BaseDictWithChildField):
|
||||
class SAMLContactField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing required key(s) for contact: {missing_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'givenName': fields.CharField(),
|
||||
'emailAddress': fields.EmailField(),
|
||||
}
|
||||
allow_unknown_keys = True
|
||||
givenName = fields.CharField()
|
||||
emailAddress = fields.EmailField()
|
||||
|
||||
|
||||
class SAMLIdPField(BaseDictWithChildField):
|
||||
class SAMLIdPField(HybridDictField):
|
||||
|
||||
default_error_messages = {
|
||||
'missing_keys': _('Missing required key(s) for IdP: {missing_keys}.'),
|
||||
}
|
||||
child_fields = {
|
||||
'entity_id': fields.CharField(),
|
||||
'url': fields.URLField(),
|
||||
'x509cert': fields.CharField(validators=[validate_certificate]),
|
||||
'attr_user_permanent_id': fields.CharField(required=False),
|
||||
'attr_first_name': fields.CharField(required=False),
|
||||
'attr_last_name': fields.CharField(required=False),
|
||||
'attr_username': fields.CharField(required=False),
|
||||
'attr_email': fields.CharField(required=False),
|
||||
}
|
||||
allow_unknown_keys = True
|
||||
entity_id = fields.CharField()
|
||||
url = fields.URLField()
|
||||
x509cert = fields.CharField(validators=[validate_certificate])
|
||||
attr_user_permanent_id = fields.CharField(required=False)
|
||||
attr_first_name = fields.CharField(required=False)
|
||||
attr_last_name = fields.CharField(required=False)
|
||||
attr_username = fields.CharField(required=False)
|
||||
attr_email = fields.CharField(required=False)
|
||||
|
||||
|
||||
class SAMLEnabledIdPsField(fields.DictField):
|
||||
@ -718,52 +700,49 @@ class SAMLEnabledIdPsField(fields.DictField):
|
||||
child = SAMLIdPField()
|
||||
|
||||
|
||||
class SAMLSecurityField(BaseDictWithChildField):
|
||||
class SAMLSecurityField(HybridDictField):
|
||||
|
||||
child_fields = {
|
||||
'nameIdEncrypted': fields.BooleanField(required=False),
|
||||
'authnRequestsSigned': fields.BooleanField(required=False),
|
||||
'logoutRequestSigned': fields.BooleanField(required=False),
|
||||
'logoutResponseSigned': fields.BooleanField(required=False),
|
||||
'signMetadata': fields.BooleanField(required=False),
|
||||
'wantMessagesSigned': fields.BooleanField(required=False),
|
||||
'wantAssertionsSigned': fields.BooleanField(required=False),
|
||||
'wantAssertionsEncrypted': fields.BooleanField(required=False),
|
||||
'wantNameId': fields.BooleanField(required=False),
|
||||
'wantNameIdEncrypted': fields.BooleanField(required=False),
|
||||
'wantAttributeStatement': fields.BooleanField(required=False),
|
||||
'requestedAuthnContext': fields.StringListBooleanField(required=False),
|
||||
'requestedAuthnContextComparison': fields.CharField(required=False),
|
||||
'metadataValidUntil': fields.CharField(allow_null=True, required=False),
|
||||
'metadataCacheDuration': fields.CharField(allow_null=True, required=False),
|
||||
'signatureAlgorithm': fields.CharField(allow_null=True, required=False),
|
||||
'digestAlgorithm': fields.CharField(allow_null=True, required=False),
|
||||
}
|
||||
allow_unknown_keys = True
|
||||
nameIdEncrypted = fields.BooleanField(required=False)
|
||||
authnRequestsSigned = fields.BooleanField(required=False)
|
||||
logoutRequestSigned = fields.BooleanField(required=False)
|
||||
logoutResponseSigned = fields.BooleanField(required=False)
|
||||
signMetadata = fields.BooleanField(required=False)
|
||||
wantMessagesSigned = fields.BooleanField(required=False)
|
||||
wantAssertionsSigned = fields.BooleanField(required=False)
|
||||
wantAssertionsEncrypted = fields.BooleanField(required=False)
|
||||
wantNameId = fields.BooleanField(required=False)
|
||||
wantNameIdEncrypted = fields.BooleanField(required=False)
|
||||
wantAttributeStatement = fields.BooleanField(required=False)
|
||||
requestedAuthnContext = fields.StringListBooleanField(required=False)
|
||||
requestedAuthnContextComparison = fields.CharField(required=False)
|
||||
metadataValidUntil = fields.CharField(allow_null=True, required=False)
|
||||
metadataCacheDuration = fields.CharField(allow_null=True, required=False)
|
||||
signatureAlgorithm = fields.CharField(allow_null=True, required=False)
|
||||
digestAlgorithm = fields.CharField(allow_null=True, required=False)
|
||||
|
||||
|
||||
class SAMLOrgAttrField(BaseDictWithChildField):
|
||||
class SAMLOrgAttrField(HybridDictField):
|
||||
|
||||
child_fields = {
|
||||
'remove': fields.BooleanField(required=False),
|
||||
'saml_attr': fields.CharField(required=False, allow_null=True),
|
||||
'remove_admins': fields.BooleanField(required=False),
|
||||
'saml_admin_attr': fields.CharField(required=False, allow_null=True),
|
||||
}
|
||||
remove = fields.BooleanField(required=False)
|
||||
saml_attr = fields.CharField(required=False, allow_null=True)
|
||||
remove_admins = fields.BooleanField(required=False)
|
||||
saml_admin_attr = fields.CharField(required=False, allow_null=True)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class SAMLTeamAttrTeamOrgMapField(BaseDictWithChildField):
|
||||
class SAMLTeamAttrTeamOrgMapField(HybridDictField):
|
||||
|
||||
child_fields = {
|
||||
'team': fields.CharField(required=True, allow_null=False),
|
||||
'organization': fields.CharField(required=True, allow_null=False),
|
||||
}
|
||||
team = fields.CharField(required=True, allow_null=False)
|
||||
organization = fields.CharField(required=True, allow_null=False)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
|
||||
class SAMLTeamAttrField(BaseDictWithChildField):
|
||||
class SAMLTeamAttrField(HybridDictField):
|
||||
|
||||
child_fields = {
|
||||
'team_org_map': fields.ListField(required=False, child=SAMLTeamAttrTeamOrgMapField(), allow_null=True),
|
||||
'remove': fields.BooleanField(required=False),
|
||||
'saml_attr': fields.CharField(required=False, allow_null=True),
|
||||
}
|
||||
team_org_map = fields.ListField(required=False, child=SAMLTeamAttrTeamOrgMapField(), allow_null=True)
|
||||
remove = fields.BooleanField(required=False)
|
||||
saml_attr = fields.CharField(required=False, allow_null=True)
|
||||
|
||||
child = _Forbidden()
|
||||
|
||||
@ -33,21 +33,23 @@ class TestSAMLOrgAttrField():
|
||||
|
||||
@pytest.mark.parametrize("data, expected", [
|
||||
({'remove': 'blah', 'saml_attr': 'foobar'},
|
||||
ValidationError('"blah" is not a valid boolean.')),
|
||||
{'remove': ['Must be a valid boolean.']}),
|
||||
({'remove': True, 'saml_attr': False},
|
||||
ValidationError('Not a valid string.')),
|
||||
{'saml_attr': ['Not a valid string.']}),
|
||||
({'remove': True, 'saml_attr': False, 'foo': 'bar', 'gig': 'ity'},
|
||||
ValidationError('Invalid key(s): "foo", "gig".')),
|
||||
{'saml_attr': ['Not a valid string.'],
|
||||
'foo': ['Invalid field.'],
|
||||
'gig': ['Invalid field.']}),
|
||||
({'remove_admins': True, 'saml_admin_attr': False},
|
||||
ValidationError('Not a valid string.')),
|
||||
{'saml_admin_attr': ['Not a valid string.']}),
|
||||
({'remove_admins': 'blah', 'saml_admin_attr': 'foobar'},
|
||||
ValidationError('"blah" is not a valid boolean.')),
|
||||
{'remove_admins': ['Must be a valid boolean.']}),
|
||||
])
|
||||
def test_internal_value_invalid(self, data, expected):
|
||||
field = SAMLOrgAttrField()
|
||||
with pytest.raises(type(expected)) as e:
|
||||
with pytest.raises(ValidationError) as e:
|
||||
field.to_internal_value(data)
|
||||
assert str(e.value) == str(expected)
|
||||
assert e.value.detail == expected
|
||||
|
||||
|
||||
class TestSAMLTeamAttrField():
|
||||
@ -77,36 +79,38 @@ class TestSAMLTeamAttrField():
|
||||
@pytest.mark.parametrize("data, expected", [
|
||||
({'remove': True, 'saml_attr': 'foobar', 'team_org_map': [
|
||||
{'team': 'foobar', 'not_a_valid_key': 'blah', 'organization': 'Ansible'},
|
||||
]}, ValidationError('Invalid key(s): "not_a_valid_key".')),
|
||||
]}, {'team_org_map': {0: {'not_a_valid_key': ['Invalid field.']}}}),
|
||||
({'remove': False, 'saml_attr': 'foobar', 'team_org_map': [
|
||||
{'organization': 'Ansible'},
|
||||
]}, ValidationError('Missing key(s): "team".')),
|
||||
]}, {'team_org_map': {0: {'team': ['This field is required.']}}}),
|
||||
({'remove': False, 'saml_attr': 'foobar', 'team_org_map': [
|
||||
{},
|
||||
]}, ValidationError('Missing key(s): "organization", "team".')),
|
||||
]}, {'team_org_map': {
|
||||
0: {'organization': ['This field is required.'],
|
||||
'team': ['This field is required.']}}}),
|
||||
])
|
||||
def test_internal_value_invalid(self, data, expected):
|
||||
field = SAMLTeamAttrField()
|
||||
with pytest.raises(type(expected)) as e:
|
||||
with pytest.raises(ValidationError) as e:
|
||||
field.to_internal_value(data)
|
||||
assert str(e.value) == str(expected)
|
||||
assert e.value.detail == expected
|
||||
|
||||
|
||||
class TestLDAPGroupTypeParamsField():
|
||||
|
||||
@pytest.mark.parametrize("group_type, data, expected", [
|
||||
('LDAPGroupType', {'name_attr': 'user', 'bob': ['a', 'b'], 'scooter': 'hello'},
|
||||
ValidationError('Invalid key(s): "bob", "scooter".')),
|
||||
['Invalid key(s): "bob", "scooter".']),
|
||||
('MemberDNGroupType', {'name_attr': 'user', 'member_attr': 'west', 'bob': ['a', 'b'], 'scooter': 'hello'},
|
||||
ValidationError('Invalid key(s): "bob", "scooter".')),
|
||||
['Invalid key(s): "bob", "scooter".']),
|
||||
('PosixUIDGroupType', {'name_attr': 'user', 'member_attr': 'west', 'ldap_group_user_attr': 'legacyThing',
|
||||
'bob': ['a', 'b'], 'scooter': 'hello'},
|
||||
ValidationError('Invalid key(s): "bob", "member_attr", "scooter".')),
|
||||
['Invalid key(s): "bob", "member_attr", "scooter".']),
|
||||
])
|
||||
def test_internal_value_invalid(self, group_type, data, expected):
|
||||
field = LDAPGroupTypeParamsField()
|
||||
field.get_depends_on = mock.MagicMock(return_value=group_type)
|
||||
|
||||
with pytest.raises(type(expected)) as e:
|
||||
with pytest.raises(ValidationError) as e:
|
||||
field.to_internal_value(data)
|
||||
assert str(e.value) == str(expected)
|
||||
assert e.value.detail == expected
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends 'rest_framework/base.html' %}
|
||||
{% load i18n staticfiles %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ name }} · {% trans 'AWX REST API' %}{% endblock %}
|
||||
|
||||
@ -48,6 +48,15 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if deprecated %}
|
||||
<div class="breadcrumb" style="background: #FCC;">
|
||||
<b>This resource has been deprecated and will be removed in a future release.</b>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
{% endblock content %}
|
||||
|
||||
{% block script %}
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
{# Copy of base.html from rest_framework with minor AWX change. #}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
{% load rest_framework %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
|
||||
{% block meta %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
{% endblock %}
|
||||
|
||||
<title>{% block title %}{% if name %}{{ name }} – {% endif %}Django REST framework{% endblock %}</title>
|
||||
|
||||
{% block style %}
|
||||
{% block bootstrap_theme %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/>
|
||||
{% endblock %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
{% block body %}
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
|
||||
<div class="wrapper">
|
||||
{% block navbar %}
|
||||
<div class="navbar navbar-static-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"
|
||||
role="navigation" aria-label="{% trans "navbar" %}">
|
||||
<div class="container">
|
||||
<span>
|
||||
{% block branding %}
|
||||
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
||||
Django REST framework
|
||||
</a>
|
||||
{% endblock %}
|
||||
</span>
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
{% block userlinks %}
|
||||
{% if user.is_authenticated %}
|
||||
{% optional_logout request user %}
|
||||
{% else %}
|
||||
{% optional_login request %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<div class="container">
|
||||
{% block breadcrumbs %}
|
||||
<ul class="breadcrumb">
|
||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||
{% if forloop.last %}
|
||||
<li class="active"><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
{% block breadcrumbs_empty %} {% endblock breadcrumbs_empty %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content" role="main" aria-label="{% trans "content" %}">
|
||||
{% if deprecated %}
|
||||
<div class="breadcrumb" style="background: #FCC;">
|
||||
<b>This resource has been deprecated and will be removed in a future release.</b>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
|
||||
<div class="region" aria-label="{% trans "request form" %}">
|
||||
{% if 'GET' in allowed_methods %}
|
||||
<form id="get-form" class="pull-right">
|
||||
<fieldset>
|
||||
{% if api_settings.URL_FORMAT_OVERRIDE %}
|
||||
<div class="btn-group format-selection">
|
||||
<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
|
||||
|
||||
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for format in available_formats %}
|
||||
<li>
|
||||
<a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if options_form %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
|
||||
<button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if delete_form %}
|
||||
<button class="btn btn-danger button-form js-tooltip" title="Make a DELETE request on the {{ name }} resource" data-toggle="modal" data-target="#deleteModal">DELETE</button>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<h4 class="text-center">Are you sure you want to delete this {{ name }}?</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
||||
<button class="btn btn-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if filter_form %}
|
||||
<button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
{% trans "Filters" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="content-main" role="main" aria-label="{% trans "main content" %}">
|
||||
<div class="page-header">
|
||||
<h1>{{ name }}</h1>
|
||||
</div>
|
||||
<div style="float:left">
|
||||
{% block description %}
|
||||
{{ description }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% if paginator %}
|
||||
<nav style="float: right">
|
||||
{% get_pagination_html paginator %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<div class="request-info" style="clear: both" aria-label="{% trans "request info" %}">
|
||||
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="response-info" aria-label="{% trans "response info" %}">
|
||||
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}{% if response_headers %}{% for key, val in response_headers|items %}
|
||||
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}{% endif %}
|
||||
{# Original line below had the side effect of also escaping content: #}
|
||||
{# </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} #}
|
||||
{# For AWX, disable automatic URL creation and move content outside of autoescape off block. #}
|
||||
{% endautoescape %}</span>{{ content }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if display_edit_forms %}
|
||||
{% if post_form or raw_data_post_form %}
|
||||
<div {% if post_form %}class="tabbable"{% endif %}>
|
||||
{% if post_form %}
|
||||
<ul class="nav nav-tabs form-switcher">
|
||||
<li>
|
||||
<a name='html-tab' href="#post-object-form" data-toggle="tab">HTML form</a>
|
||||
</li>
|
||||
<li>
|
||||
<a name='raw-tab' href="#post-generic-content-form" data-toggle="tab">Raw data</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="well tab-content">
|
||||
{% if post_form %}
|
||||
<div class="tab-pane" id="post-object-form">
|
||||
{% with form=post_form %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{{ post_form }}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div {% if post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
|
||||
{% with form=raw_data_post_form %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
||||
<fieldset>
|
||||
{% include "rest_framework/raw_data_form.html" %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if put_form or raw_data_put_form or raw_data_patch_form %}
|
||||
<div {% if put_form %}class="tabbable"{% endif %}>
|
||||
{% if put_form %}
|
||||
<ul class="nav nav-tabs form-switcher">
|
||||
<li>
|
||||
<a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a>
|
||||
</li>
|
||||
<li>
|
||||
<a name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="well tab-content">
|
||||
{% if put_form %}
|
||||
<div class="tab-pane" id="put-object-form">
|
||||
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<fieldset>
|
||||
{{ put_form }}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form">
|
||||
{% with form=raw_data_put_or_patch_form %}
|
||||
<form action="{{ request.get_full_path }}" data-method="PUT" class="form-horizontal">
|
||||
<fieldset>
|
||||
{% include "rest_framework/raw_data_form.html" %}
|
||||
<div class="form-actions">
|
||||
{% if raw_data_put_form %}
|
||||
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
{% endif %}
|
||||
{% if raw_data_patch_form %}
|
||||
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
</div><!-- /.content -->
|
||||
</div><!-- /.container -->
|
||||
</div><!-- ./wrapper -->
|
||||
|
||||
{% if filter_form %}
|
||||
{{ filter_form }}
|
||||
{% endif %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
window.drf = {
|
||||
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
|
||||
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
|
||||
};
|
||||
</script>
|
||||
<script src="{% static "rest_framework/js/jquery-1.12.4.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/default.js" %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('form').ajaxForm();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
</html>
|
||||
@ -20,7 +20,7 @@ django-radius==1.3.3
|
||||
django-solo==1.1.3
|
||||
django-split-settings==0.3.0
|
||||
django-taggit==0.22.2
|
||||
djangorestframework==3.7.7
|
||||
djangorestframework==3.9.4
|
||||
djangorestframework-yaml==1.0.3
|
||||
irc==16.2
|
||||
jinja2==2.10.1
|
||||
|
||||
@ -13,9 +13,9 @@ asgi-amqp==1.1.3
|
||||
asgiref==1.1.2 # via asgi-amqp, channels, daphne
|
||||
asn1crypto==0.24.0 # via cryptography
|
||||
attrs==19.1.0 # via automat, service-identity, twisted
|
||||
autobahn==19.3.3 # via daphne
|
||||
autobahn==19.5.1 # via daphne
|
||||
automat==0.7.0 # via twisted
|
||||
azure-common==1.1.20 # via azure-keyvault
|
||||
azure-common==1.1.21 # via azure-keyvault
|
||||
azure-keyvault==1.1.0
|
||||
azure-nspkg==3.0.2 # via azure-keyvault
|
||||
billiard==3.6.0.0 # via celery
|
||||
@ -43,7 +43,7 @@ django-split-settings==0.3.0
|
||||
django-taggit==0.22.2
|
||||
django==1.11.20
|
||||
djangorestframework-yaml==1.0.3
|
||||
djangorestframework==3.7.7
|
||||
djangorestframework==3.9.4
|
||||
future==0.16.0 # via django-radius
|
||||
hyperlink==19.0.0 # via twisted
|
||||
idna==2.8 # via hyperlink, requests, twisted
|
||||
@ -59,7 +59,7 @@ jaraco.logging==2.0 # via irc
|
||||
jaraco.stream==2.0 # via irc
|
||||
jaraco.text==3.0 # via irc, jaraco.collections
|
||||
jinja2==2.10.1
|
||||
jsonpickle==1.1 # via asgi-amqp
|
||||
jsonpickle==1.2 # via asgi-amqp
|
||||
jsonschema==2.6.0
|
||||
kombu==4.5.0 # via asgi-amqp, celery
|
||||
lockfile==0.12.2 # via python-daemon
|
||||
@ -88,7 +88,7 @@ pyjwt==1.7.1 # via adal, social-auth-core, twilio
|
||||
pyopenssl==19.0.0 # via twisted
|
||||
pyparsing==2.2.0
|
||||
pyrad==2.1 # via django-radius
|
||||
pysocks==1.6.8 # via twilio
|
||||
pysocks==1.7.0 # via twilio
|
||||
python-daemon==2.2.3 # via ansible-runner
|
||||
python-dateutil==2.7.2
|
||||
python-ldap==3.2.0 # via django-auth-ldap
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user