mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02: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:
@@ -119,39 +119,12 @@ class LoggedLogoutView(auth_views.LogoutView):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_view_name(cls, suffix=None):
|
def get_view_description(view, html=False):
|
||||||
'''
|
'''Wrapper around REST framework get_view_description() to continue
|
||||||
Wrapper around REST framework get_view_name() to support get_name() method
|
to support our historical div.
|
||||||
and view_name property on a view class.
|
|
||||||
'''
|
|
||||||
name = ''
|
|
||||||
if hasattr(cls, 'get_name') and callable(cls.get_name):
|
|
||||||
name = cls().get_name()
|
|
||||||
elif hasattr(cls, 'view_name'):
|
|
||||||
if callable(cls.view_name):
|
|
||||||
name = cls.view_name()
|
|
||||||
else:
|
|
||||||
name = cls.view_name
|
|
||||||
if name:
|
|
||||||
return ('%s %s' % (name, suffix)) if suffix else name
|
|
||||||
return views.get_view_name(cls, suffix=None)
|
|
||||||
|
|
||||||
|
|
||||||
def get_view_description(cls, request, html=False):
|
|
||||||
'''
|
'''
|
||||||
Wrapper around REST framework get_view_description() to support
|
desc = views.get_view_description(view, html=html)
|
||||||
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)
|
|
||||||
if html:
|
if html:
|
||||||
desc = '<div class="description">%s</div>' % desc
|
desc = '<div class="description">%s</div>' % desc
|
||||||
return mark_safe(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/`
|
# `curl https://user:pass@tower.example.org/api/v2/job_templates/N/launch/`
|
||||||
return 'Bearer realm=api authorization_url=/api/o/authorize/'
|
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):
|
def get_description_context(self):
|
||||||
return {
|
return {
|
||||||
'view': self,
|
'view': self,
|
||||||
@@ -280,8 +245,8 @@ class APIView(views.APIView):
|
|||||||
'swagger_method': getattr(self.request, 'swagger_method', None),
|
'swagger_method': getattr(self.request, 'swagger_method', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_description(self, request, html=False):
|
@property
|
||||||
self.request = request
|
def description(self):
|
||||||
template_list = []
|
template_list = []
|
||||||
for klass in inspect.getmro(type(self)):
|
for klass in inspect.getmro(type(self)):
|
||||||
template_basename = camelcase_to_underscore(klass.__name__)
|
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),
|
'model_verbose_name_plural': smart_text(self.model._meta.verbose_name_plural),
|
||||||
})
|
})
|
||||||
serializer = self.get_serializer()
|
serializer = self.get_serializer()
|
||||||
|
metadata = self.metadata_class()
|
||||||
|
metadata.request = self.request
|
||||||
for method, key in [
|
for method, key in [
|
||||||
('GET', 'serializer_fields'),
|
('GET', 'serializer_fields'),
|
||||||
('POST', 'serializer_create_fields'),
|
('POST', 'serializer_create_fields'),
|
||||||
('PUT', 'serializer_update_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
|
d['settings'] = settings
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class AutoSchema(DRFAuthSchema):
|
|||||||
return link
|
return link
|
||||||
|
|
||||||
def get_description(self, path, method):
|
def get_description(self, path, method):
|
||||||
self.view._request = self.view.request
|
|
||||||
setattr(self.view.request, 'swagger_method', method)
|
setattr(self.view.request, 'swagger_method', method)
|
||||||
description = super(AutoSchema, self).get_description(path, method)
|
description = super(AutoSchema, self).get_description(path, method)
|
||||||
return description
|
return description
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
import cgi
|
|
||||||
import dateutil
|
import dateutil
|
||||||
import functools
|
import functools
|
||||||
|
import html
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
@@ -37,7 +37,7 @@ from rest_framework.permissions import AllowAny, IsAuthenticated
|
|||||||
from rest_framework.renderers import JSONRenderer, StaticHTMLRenderer
|
from rest_framework.renderers import JSONRenderer, StaticHTMLRenderer
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
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
|
from rest_framework import status
|
||||||
|
|
||||||
# Django REST Framework YAML
|
# Django REST Framework YAML
|
||||||
@@ -69,7 +69,7 @@ from awx.api.generics import (
|
|||||||
RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, SimpleListAPIView,
|
RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, SimpleListAPIView,
|
||||||
SubDetailAPIView, SubListAPIView, SubListAttachDetachAPIView,
|
SubDetailAPIView, SubListAPIView, SubListAttachDetachAPIView,
|
||||||
SubListCreateAPIView, SubListCreateAttachDetachAPIView,
|
SubListCreateAPIView, SubListCreateAttachDetachAPIView,
|
||||||
SubListDestroyAPIView, get_view_name
|
SubListDestroyAPIView
|
||||||
)
|
)
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
from awx.conf.license import get_license
|
from awx.conf.license import get_license
|
||||||
@@ -168,7 +168,7 @@ class DashboardView(APIView):
|
|||||||
|
|
||||||
deprecated = True
|
deprecated = True
|
||||||
|
|
||||||
view_name = _("Dashboard")
|
name = _("Dashboard")
|
||||||
swagger_topic = 'Dashboard'
|
swagger_topic = 'Dashboard'
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
@@ -270,7 +270,7 @@ class DashboardView(APIView):
|
|||||||
|
|
||||||
class DashboardJobsGraphView(APIView):
|
class DashboardJobsGraphView(APIView):
|
||||||
|
|
||||||
view_name = _("Dashboard Jobs Graphs")
|
name = _("Dashboard Jobs Graphs")
|
||||||
swagger_topic = 'Jobs'
|
swagger_topic = 'Jobs'
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
@@ -320,7 +320,7 @@ class DashboardJobsGraphView(APIView):
|
|||||||
|
|
||||||
class InstanceList(ListAPIView):
|
class InstanceList(ListAPIView):
|
||||||
|
|
||||||
view_name = _("Instances")
|
name = _("Instances")
|
||||||
model = models.Instance
|
model = models.Instance
|
||||||
serializer_class = serializers.InstanceSerializer
|
serializer_class = serializers.InstanceSerializer
|
||||||
search_fields = ('hostname',)
|
search_fields = ('hostname',)
|
||||||
@@ -328,7 +328,7 @@ class InstanceList(ListAPIView):
|
|||||||
|
|
||||||
class InstanceDetail(RetrieveUpdateAPIView):
|
class InstanceDetail(RetrieveUpdateAPIView):
|
||||||
|
|
||||||
view_name = _("Instance Detail")
|
name = _("Instance Detail")
|
||||||
model = models.Instance
|
model = models.Instance
|
||||||
serializer_class = serializers.InstanceSerializer
|
serializer_class = serializers.InstanceSerializer
|
||||||
|
|
||||||
@@ -345,7 +345,7 @@ class InstanceDetail(RetrieveUpdateAPIView):
|
|||||||
|
|
||||||
class InstanceUnifiedJobsList(SubListAPIView):
|
class InstanceUnifiedJobsList(SubListAPIView):
|
||||||
|
|
||||||
view_name = _("Instance Jobs")
|
name = _("Instance Jobs")
|
||||||
model = models.UnifiedJob
|
model = models.UnifiedJob
|
||||||
serializer_class = serializers.UnifiedJobListSerializer
|
serializer_class = serializers.UnifiedJobListSerializer
|
||||||
parent_model = models.Instance
|
parent_model = models.Instance
|
||||||
@@ -359,7 +359,7 @@ class InstanceUnifiedJobsList(SubListAPIView):
|
|||||||
|
|
||||||
class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAttachDetachAPIView):
|
class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
view_name = _("Instance's Instance Groups")
|
name = _("Instance's Instance Groups")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
serializer_class = serializers.InstanceGroupSerializer
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
parent_model = models.Instance
|
parent_model = models.Instance
|
||||||
@@ -368,7 +368,7 @@ class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAtta
|
|||||||
|
|
||||||
class InstanceGroupList(ListCreateAPIView):
|
class InstanceGroupList(ListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Instance Groups")
|
name = _("Instance Groups")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
serializer_class = serializers.InstanceGroupSerializer
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ class InstanceGroupList(ListCreateAPIView):
|
|||||||
class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
always_allow_superuser = False
|
always_allow_superuser = False
|
||||||
view_name = _("Instance Group Detail")
|
name = _("Instance Group Detail")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
serializer_class = serializers.InstanceGroupSerializer
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
permission_classes = (InstanceGroupTowerPermission,)
|
permission_classes = (InstanceGroupTowerPermission,)
|
||||||
@@ -392,7 +392,7 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP
|
|||||||
|
|
||||||
class InstanceGroupUnifiedJobsList(SubListAPIView):
|
class InstanceGroupUnifiedJobsList(SubListAPIView):
|
||||||
|
|
||||||
view_name = _("Instance Group Running Jobs")
|
name = _("Instance Group Running Jobs")
|
||||||
model = models.UnifiedJob
|
model = models.UnifiedJob
|
||||||
serializer_class = serializers.UnifiedJobListSerializer
|
serializer_class = serializers.UnifiedJobListSerializer
|
||||||
parent_model = models.InstanceGroup
|
parent_model = models.InstanceGroup
|
||||||
@@ -401,7 +401,7 @@ class InstanceGroupUnifiedJobsList(SubListAPIView):
|
|||||||
|
|
||||||
class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetachAPIView):
|
class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetachAPIView):
|
||||||
|
|
||||||
view_name = _("Instance Group's Instances")
|
name = _("Instance Group's Instances")
|
||||||
model = models.Instance
|
model = models.Instance
|
||||||
serializer_class = serializers.InstanceSerializer
|
serializer_class = serializers.InstanceSerializer
|
||||||
parent_model = models.InstanceGroup
|
parent_model = models.InstanceGroup
|
||||||
@@ -411,7 +411,7 @@ class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetac
|
|||||||
|
|
||||||
class ScheduleList(ListCreateAPIView):
|
class ScheduleList(ListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Schedules")
|
name = _("Schedules")
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
|
|
||||||
@@ -425,7 +425,7 @@ class ScheduleDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
class SchedulePreview(GenericAPIView):
|
class SchedulePreview(GenericAPIView):
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
view_name = _('Schedule Recurrence Rule Preview')
|
name = _('Schedule Recurrence Rule Preview')
|
||||||
serializer_class = serializers.SchedulePreviewSerializer
|
serializer_class = serializers.SchedulePreviewSerializer
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ class ScheduleUnifiedJobsList(SubListAPIView):
|
|||||||
serializer_class = serializers.UnifiedJobListSerializer
|
serializer_class = serializers.UnifiedJobListSerializer
|
||||||
parent_model = models.Schedule
|
parent_model = models.Schedule
|
||||||
relationship = 'unifiedjob_set'
|
relationship = 'unifiedjob_set'
|
||||||
view_name = _('Schedule Jobs List')
|
name = _('Schedule Jobs List')
|
||||||
|
|
||||||
|
|
||||||
class AuthView(APIView):
|
class AuthView(APIView):
|
||||||
@@ -704,7 +704,7 @@ class ProjectTeamsList(ListAPIView):
|
|||||||
|
|
||||||
class ProjectSchedulesList(SubListCreateAPIView):
|
class ProjectSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Project Schedules")
|
name = _("Project Schedules")
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
@@ -715,7 +715,7 @@ class ProjectSchedulesList(SubListCreateAPIView):
|
|||||||
|
|
||||||
class ProjectScmInventorySources(SubListAPIView):
|
class ProjectScmInventorySources(SubListAPIView):
|
||||||
|
|
||||||
view_name = _("Project SCM Inventory Sources")
|
name = _("Project SCM Inventory Sources")
|
||||||
model = models.InventorySource
|
model = models.InventorySource
|
||||||
serializer_class = serializers.InventorySourceSerializer
|
serializer_class = serializers.InventorySourceSerializer
|
||||||
parent_model = models.Project
|
parent_model = models.Project
|
||||||
@@ -818,7 +818,7 @@ class ProjectUpdateEventsList(SubListAPIView):
|
|||||||
serializer_class = serializers.ProjectUpdateEventSerializer
|
serializer_class = serializers.ProjectUpdateEventSerializer
|
||||||
parent_model = models.ProjectUpdate
|
parent_model = models.ProjectUpdate
|
||||||
relationship = 'project_update_events'
|
relationship = 'project_update_events'
|
||||||
view_name = _('Project Update Events List')
|
name = _('Project Update Events List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
@@ -832,7 +832,7 @@ class SystemJobEventsList(SubListAPIView):
|
|||||||
serializer_class = serializers.SystemJobEventSerializer
|
serializer_class = serializers.SystemJobEventSerializer
|
||||||
parent_model = models.SystemJob
|
parent_model = models.SystemJob
|
||||||
relationship = 'system_job_events'
|
relationship = 'system_job_events'
|
||||||
view_name = _('System Job Events List')
|
name = _('System Job Events List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
@@ -868,7 +868,7 @@ class ProjectUpdateNotificationsList(SubListAPIView):
|
|||||||
|
|
||||||
class ProjectUpdateScmInventoryUpdates(SubListAPIView):
|
class ProjectUpdateScmInventoryUpdates(SubListAPIView):
|
||||||
|
|
||||||
view_name = _("Project Update SCM Inventory Updates")
|
name = _("Project Update SCM Inventory Updates")
|
||||||
model = models.InventoryUpdate
|
model = models.InventoryUpdate
|
||||||
serializer_class = serializers.InventoryUpdateListSerializer
|
serializer_class = serializers.InventoryUpdateListSerializer
|
||||||
parent_model = models.ProjectUpdate
|
parent_model = models.ProjectUpdate
|
||||||
@@ -912,7 +912,7 @@ class UserMeList(ListAPIView):
|
|||||||
|
|
||||||
model = models.User
|
model = models.User
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
view_name = _('Me')
|
name = _('Me')
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.model.objects.filter(pk=self.request.user.pk)
|
return self.model.objects.filter(pk=self.request.user.pk)
|
||||||
@@ -920,7 +920,7 @@ class UserMeList(ListAPIView):
|
|||||||
|
|
||||||
class OAuth2ApplicationList(ListCreateAPIView):
|
class OAuth2ApplicationList(ListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth 2 Applications")
|
name = _("OAuth 2 Applications")
|
||||||
|
|
||||||
model = models.OAuth2Application
|
model = models.OAuth2Application
|
||||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||||
@@ -929,7 +929,7 @@ class OAuth2ApplicationList(ListCreateAPIView):
|
|||||||
|
|
||||||
class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
|
class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth 2 Application Detail")
|
name = _("OAuth 2 Application Detail")
|
||||||
|
|
||||||
model = models.OAuth2Application
|
model = models.OAuth2Application
|
||||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||||
@@ -942,7 +942,7 @@ class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
class ApplicationOAuth2TokenList(SubListCreateAPIView):
|
class ApplicationOAuth2TokenList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth 2 Application Tokens")
|
name = _("OAuth 2 Application Tokens")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.OAuth2TokenSerializer
|
serializer_class = serializers.OAuth2TokenSerializer
|
||||||
@@ -964,7 +964,7 @@ class OAuth2ApplicationActivityStreamList(SubListAPIView):
|
|||||||
|
|
||||||
class OAuth2TokenList(ListCreateAPIView):
|
class OAuth2TokenList(ListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth2 Tokens")
|
name = _("OAuth2 Tokens")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.OAuth2TokenSerializer
|
serializer_class = serializers.OAuth2TokenSerializer
|
||||||
@@ -973,7 +973,7 @@ class OAuth2TokenList(ListCreateAPIView):
|
|||||||
|
|
||||||
class OAuth2UserTokenList(SubListCreateAPIView):
|
class OAuth2UserTokenList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth2 User Tokens")
|
name = _("OAuth2 User Tokens")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.OAuth2TokenSerializer
|
serializer_class = serializers.OAuth2TokenSerializer
|
||||||
@@ -985,7 +985,7 @@ class OAuth2UserTokenList(SubListCreateAPIView):
|
|||||||
|
|
||||||
class UserAuthorizedTokenList(SubListCreateAPIView):
|
class UserAuthorizedTokenList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth2 User Authorized Access Tokens")
|
name = _("OAuth2 User Authorized Access Tokens")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.UserAuthorizedTokenSerializer
|
serializer_class = serializers.UserAuthorizedTokenSerializer
|
||||||
@@ -1000,7 +1000,7 @@ class UserAuthorizedTokenList(SubListCreateAPIView):
|
|||||||
|
|
||||||
class OrganizationApplicationList(SubListCreateAPIView):
|
class OrganizationApplicationList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Organization OAuth2 Applications")
|
name = _("Organization OAuth2 Applications")
|
||||||
|
|
||||||
model = models.OAuth2Application
|
model = models.OAuth2Application
|
||||||
serializer_class = serializers.OAuth2ApplicationSerializer
|
serializer_class = serializers.OAuth2ApplicationSerializer
|
||||||
@@ -1012,7 +1012,7 @@ class OrganizationApplicationList(SubListCreateAPIView):
|
|||||||
|
|
||||||
class UserPersonalTokenList(SubListCreateAPIView):
|
class UserPersonalTokenList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth2 Personal Access Tokens")
|
name = _("OAuth2 Personal Access Tokens")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.UserPersonalTokenSerializer
|
serializer_class = serializers.UserPersonalTokenSerializer
|
||||||
@@ -1027,7 +1027,7 @@ class UserPersonalTokenList(SubListCreateAPIView):
|
|||||||
|
|
||||||
class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView):
|
class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
view_name = _("OAuth Token Detail")
|
name = _("OAuth Token Detail")
|
||||||
|
|
||||||
model = models.OAuth2AccessToken
|
model = models.OAuth2AccessToken
|
||||||
serializer_class = serializers.OAuth2TokenDetailSerializer
|
serializer_class = serializers.OAuth2TokenDetailSerializer
|
||||||
@@ -1373,7 +1373,7 @@ class CredentialExternalTest(SubDetailAPIView):
|
|||||||
before saving them.
|
before saving them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
view_name = _('External Credential Test')
|
name = _('External Credential Test')
|
||||||
|
|
||||||
model = models.Credential
|
model = models.Credential
|
||||||
serializer_class = serializers.EmptySerializer
|
serializer_class = serializers.EmptySerializer
|
||||||
@@ -1399,7 +1399,7 @@ class CredentialExternalTest(SubDetailAPIView):
|
|||||||
|
|
||||||
class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView):
|
class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
view_name = _("Credential Input Source Detail")
|
name = _("Credential Input Source Detail")
|
||||||
|
|
||||||
model = models.CredentialInputSource
|
model = models.CredentialInputSource
|
||||||
serializer_class = serializers.CredentialInputSourceSerializer
|
serializer_class = serializers.CredentialInputSourceSerializer
|
||||||
@@ -1407,7 +1407,7 @@ class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
class CredentialInputSourceList(ListCreateAPIView):
|
class CredentialInputSourceList(ListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Credential Input Sources")
|
name = _("Credential Input Sources")
|
||||||
|
|
||||||
model = models.CredentialInputSource
|
model = models.CredentialInputSource
|
||||||
serializer_class = serializers.CredentialInputSourceSerializer
|
serializer_class = serializers.CredentialInputSourceSerializer
|
||||||
@@ -1415,7 +1415,7 @@ class CredentialInputSourceList(ListCreateAPIView):
|
|||||||
|
|
||||||
class CredentialInputSourceSubList(SubListCreateAPIView):
|
class CredentialInputSourceSubList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Credential Input Sources")
|
name = _("Credential Input Sources")
|
||||||
|
|
||||||
model = models.CredentialInputSource
|
model = models.CredentialInputSource
|
||||||
serializer_class = serializers.CredentialInputSourceSerializer
|
serializer_class = serializers.CredentialInputSourceSerializer
|
||||||
@@ -1430,7 +1430,7 @@ class CredentialTypeExternalTest(SubDetailAPIView):
|
|||||||
saving it.
|
saving it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
view_name = _('External Credential Type Test')
|
name = _('External Credential Type Test')
|
||||||
|
|
||||||
model = models.CredentialType
|
model = models.CredentialType
|
||||||
serializer_class = serializers.EmptySerializer
|
serializer_class = serializers.EmptySerializer
|
||||||
@@ -2004,7 +2004,7 @@ class InventoryTreeView(RetrieveAPIView):
|
|||||||
|
|
||||||
class InventoryInventorySourcesList(SubListCreateAPIView):
|
class InventoryInventorySourcesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _('Inventory Source List')
|
name = _('Inventory Source List')
|
||||||
|
|
||||||
model = models.InventorySource
|
model = models.InventorySource
|
||||||
serializer_class = serializers.InventorySourceSerializer
|
serializer_class = serializers.InventorySourceSerializer
|
||||||
@@ -2016,7 +2016,7 @@ class InventoryInventorySourcesList(SubListCreateAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class InventoryInventorySourcesUpdate(RetrieveAPIView):
|
class InventoryInventorySourcesUpdate(RetrieveAPIView):
|
||||||
view_name = _('Inventory Sources Update')
|
name = _('Inventory Sources Update')
|
||||||
|
|
||||||
model = models.Inventory
|
model = models.Inventory
|
||||||
obj_permission_type = 'start'
|
obj_permission_type = 'start'
|
||||||
@@ -2079,7 +2079,7 @@ class InventorySourceDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroy
|
|||||||
|
|
||||||
class InventorySourceSchedulesList(SubListCreateAPIView):
|
class InventorySourceSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Inventory Source Schedules")
|
name = _("Inventory Source Schedules")
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
@@ -2448,7 +2448,7 @@ class JobTemplateLaunch(RetrieveAPIView):
|
|||||||
|
|
||||||
class JobTemplateSchedulesList(SubListCreateAPIView):
|
class JobTemplateSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Job Template Schedules")
|
name = _("Job Template Schedules")
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
@@ -3220,7 +3220,7 @@ class WorkflowJobTemplateJobsList(SubListAPIView):
|
|||||||
|
|
||||||
class WorkflowJobTemplateSchedulesList(SubListCreateAPIView):
|
class WorkflowJobTemplateSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("Workflow Job Template Schedules")
|
name = _("Workflow Job Template Schedules")
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
@@ -3388,7 +3388,7 @@ class SystemJobTemplateLaunch(GenericAPIView):
|
|||||||
|
|
||||||
class SystemJobTemplateSchedulesList(SubListCreateAPIView):
|
class SystemJobTemplateSchedulesList(SubListCreateAPIView):
|
||||||
|
|
||||||
view_name = _("System Job Template Schedules")
|
name = _("System Job Template Schedules")
|
||||||
|
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
@@ -3659,7 +3659,7 @@ class BaseJobHostSummariesList(SubListAPIView):
|
|||||||
serializer_class = serializers.JobHostSummarySerializer
|
serializer_class = serializers.JobHostSummarySerializer
|
||||||
parent_model = None # Subclasses must define this attribute.
|
parent_model = None # Subclasses must define this attribute.
|
||||||
relationship = 'job_host_summaries'
|
relationship = 'job_host_summaries'
|
||||||
view_name = _('Job Host Summaries List')
|
name = _('Job Host Summaries List')
|
||||||
search_fields = ('host_name',)
|
search_fields = ('host_name',)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -3708,7 +3708,7 @@ class JobEventChildrenList(SubListAPIView):
|
|||||||
serializer_class = serializers.JobEventSerializer
|
serializer_class = serializers.JobEventSerializer
|
||||||
parent_model = models.JobEvent
|
parent_model = models.JobEvent
|
||||||
relationship = 'children'
|
relationship = 'children'
|
||||||
view_name = _('Job Event Children List')
|
name = _('Job Event Children List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -3724,7 +3724,7 @@ class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView):
|
|||||||
serializer_class = serializers.HostSerializer
|
serializer_class = serializers.HostSerializer
|
||||||
parent_model = models.JobEvent
|
parent_model = models.JobEvent
|
||||||
relationship = 'hosts'
|
relationship = 'hosts'
|
||||||
view_name = _('Job Event Hosts List')
|
name = _('Job Event Hosts List')
|
||||||
|
|
||||||
|
|
||||||
class BaseJobEventsList(SubListAPIView):
|
class BaseJobEventsList(SubListAPIView):
|
||||||
@@ -3733,7 +3733,7 @@ class BaseJobEventsList(SubListAPIView):
|
|||||||
serializer_class = serializers.JobEventSerializer
|
serializer_class = serializers.JobEventSerializer
|
||||||
parent_model = None # Subclasses must define this attribute.
|
parent_model = None # Subclasses must define this attribute.
|
||||||
relationship = 'job_events'
|
relationship = 'job_events'
|
||||||
view_name = _('Job Events List')
|
name = _('Job Events List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
@@ -3942,7 +3942,7 @@ class BaseAdHocCommandEventsList(SubListAPIView):
|
|||||||
serializer_class = serializers.AdHocCommandEventSerializer
|
serializer_class = serializers.AdHocCommandEventSerializer
|
||||||
parent_model = None # Subclasses must define this attribute.
|
parent_model = None # Subclasses must define this attribute.
|
||||||
relationship = 'ad_hoc_command_events'
|
relationship = 'ad_hoc_command_events'
|
||||||
view_name = _('Ad Hoc Command Events List')
|
name = _('Ad Hoc Command Events List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
|
|
||||||
@@ -4100,7 +4100,7 @@ class UnifiedJobStdout(RetrieveAPIView):
|
|||||||
# Remove any ANSI escape sequences containing job event data.
|
# 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)
|
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 = {
|
context = {
|
||||||
'title': get_view_name(self.__class__),
|
'title': get_view_name(self.__class__),
|
||||||
@@ -4195,7 +4195,7 @@ class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
class NotificationTemplateTest(GenericAPIView):
|
class NotificationTemplateTest(GenericAPIView):
|
||||||
'''Test a Notification Template'''
|
'''Test a Notification Template'''
|
||||||
|
|
||||||
view_name = _('Notification Template Test')
|
name = _('Notification Template Test')
|
||||||
model = models.NotificationTemplate
|
model = models.NotificationTemplate
|
||||||
obj_permission_type = 'start'
|
obj_permission_type = 'start'
|
||||||
serializer_class = serializers.EmptySerializer
|
serializer_class = serializers.EmptySerializer
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class InventoryUpdateEventsList(SubListAPIView):
|
|||||||
serializer_class = InventoryUpdateEventSerializer
|
serializer_class = InventoryUpdateEventSerializer
|
||||||
parent_model = InventoryUpdate
|
parent_model = InventoryUpdate
|
||||||
relationship = 'inventory_update_events'
|
relationship = 'inventory_update_events'
|
||||||
view_name = _('Inventory Update Events List')
|
name = _('Inventory Update Events List')
|
||||||
search_fields = ('stdout',)
|
search_fields = ('stdout',)
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ logger = logging.getLogger('awx.main.analytics')
|
|||||||
|
|
||||||
class MetricsView(APIView):
|
class MetricsView(APIView):
|
||||||
|
|
||||||
view_name = _('Metrics')
|
name = _('Metrics')
|
||||||
swagger_topic = 'Metrics'
|
swagger_topic = 'Metrics'
|
||||||
|
|
||||||
renderer_classes = [renderers.PlainTextRenderer,
|
renderer_classes = [renderers.PlainTextRenderer,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ logger = logging.getLogger('awx.api.views.root')
|
|||||||
class ApiRootView(APIView):
|
class ApiRootView(APIView):
|
||||||
|
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
view_name = _('REST API')
|
name = _('REST API')
|
||||||
versioning_class = None
|
versioning_class = None
|
||||||
swagger_topic = 'Versioning'
|
swagger_topic = 'Versioning'
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class ApiRootView(APIView):
|
|||||||
class ApiOAuthAuthorizationRootView(APIView):
|
class ApiOAuthAuthorizationRootView(APIView):
|
||||||
|
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
view_name = _("API OAuth 2 Authorization Root")
|
name = _("API OAuth 2 Authorization Root")
|
||||||
versioning_class = None
|
versioning_class = None
|
||||||
swagger_topic = 'Authentication'
|
swagger_topic = 'Authentication'
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ class ApiVersionRootView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class ApiV2RootView(ApiVersionRootView):
|
class ApiV2RootView(ApiVersionRootView):
|
||||||
view_name = _('Version 2')
|
name = _('Version 2')
|
||||||
|
|
||||||
|
|
||||||
class ApiV2PingView(APIView):
|
class ApiV2PingView(APIView):
|
||||||
@@ -139,7 +139,7 @@ class ApiV2PingView(APIView):
|
|||||||
"""
|
"""
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
authentication_classes = ()
|
authentication_classes = ()
|
||||||
view_name = _('Ping')
|
name = _('Ping')
|
||||||
swagger_topic = 'System Configuration'
|
swagger_topic = 'System Configuration'
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
@@ -171,7 +171,7 @@ class ApiV2PingView(APIView):
|
|||||||
class ApiV2ConfigView(APIView):
|
class ApiV2ConfigView(APIView):
|
||||||
|
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
view_name = _('Configuration')
|
name = _('Configuration')
|
||||||
swagger_topic = 'System Configuration'
|
swagger_topic = 'System Configuration'
|
||||||
|
|
||||||
def check_permissions(self, request):
|
def check_permissions(self, request):
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class SettingCategoryList(ListAPIView):
|
|||||||
model = Setting # Not exactly, but needed for the view.
|
model = Setting # Not exactly, but needed for the view.
|
||||||
serializer_class = SettingCategorySerializer
|
serializer_class = SettingCategorySerializer
|
||||||
filter_backends = []
|
filter_backends = []
|
||||||
view_name = _('Setting Categories')
|
name = _('Setting Categories')
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
setting_categories = []
|
setting_categories = []
|
||||||
@@ -63,7 +63,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
model = Setting # Not exactly, but needed for the view.
|
model = Setting # Not exactly, but needed for the view.
|
||||||
serializer_class = SettingSingletonSerializer
|
serializer_class = SettingSingletonSerializer
|
||||||
filter_backends = []
|
filter_backends = []
|
||||||
view_name = _('Setting Detail')
|
name = _('Setting Detail')
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.category_slug = self.kwargs.get('category_slug', 'all')
|
self.category_slug = self.kwargs.get('category_slug', 'all')
|
||||||
@@ -154,7 +154,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
class SettingLoggingTest(GenericAPIView):
|
class SettingLoggingTest(GenericAPIView):
|
||||||
|
|
||||||
view_name = _('Logging Connectivity Test')
|
name = _('Logging Connectivity Test')
|
||||||
model = Setting
|
model = Setting
|
||||||
serializer_class = SettingSingletonSerializer
|
serializer_class = SettingSingletonSerializer
|
||||||
permission_classes = (IsSuperUser,)
|
permission_classes = (IsSuperUser,)
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from django.conf import settings
|
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():
|
def test_python_and_js_licenses():
|
||||||
|
|||||||
@@ -20,10 +20,8 @@ class ApiErrorView(views.APIView):
|
|||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
metadata_class = None
|
metadata_class = None
|
||||||
exception_class = exceptions.APIException
|
exception_class = exceptions.APIException
|
||||||
view_name = _('API Error')
|
|
||||||
|
|
||||||
def get_view_name(self):
|
name = _('API Error')
|
||||||
return self.view_name
|
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
response = super(ApiErrorView, self).finalize_response(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):
|
class APIException(exceptions.APIException):
|
||||||
status_code = status
|
status_code = status
|
||||||
default_detail = kwargs['content']
|
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)
|
response = api_error_view(request)
|
||||||
if hasattr(response, 'render'):
|
if hasattr(response, 'render'):
|
||||||
response.render()
|
response.render()
|
||||||
|
|||||||
@@ -307,7 +307,6 @@ REST_FRAMEWORK = {
|
|||||||
),
|
),
|
||||||
'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata',
|
'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata',
|
||||||
'EXCEPTION_HANDLER': 'awx.api.views.api_exception_handler',
|
'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',
|
'VIEW_DESCRIPTION_FUNCTION': 'awx.api.generics.get_view_description',
|
||||||
'NON_FIELD_ERRORS_KEY': '__all__',
|
'NON_FIELD_ERRORS_KEY': '__all__',
|
||||||
'DEFAULT_VERSION': 'v2',
|
'DEFAULT_VERSION': 'v2',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@@ -8,8 +9,8 @@ import ldap
|
|||||||
import awx
|
import awx
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
|
|
||||||
# Django Auth LDAP
|
# Django Auth LDAP
|
||||||
import django_auth_ldap.config
|
import django_auth_ldap.config
|
||||||
@@ -18,7 +19,8 @@ from django_auth_ldap.config import (
|
|||||||
LDAPSearchUnion,
|
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
|
# This must be imported so get_subclasses picks it up
|
||||||
from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
|
from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
|
||||||
@@ -74,6 +76,71 @@ class DependsOnMixin():
|
|||||||
return res
|
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):
|
class AuthenticationBackendsField(fields.StringListField):
|
||||||
|
|
||||||
# Mapping of settings that must be set in order to enable each
|
# Mapping of settings that must be set in order to enable each
|
||||||
@@ -459,70 +526,14 @@ class LDAPDNMapField(fields.StringListBooleanField):
|
|||||||
child = LDAPDNField()
|
child = LDAPDNField()
|
||||||
|
|
||||||
|
|
||||||
class BaseDictWithChildField(fields.DictField):
|
class LDAPSingleOrganizationMapField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
admins = LDAPDNMapField(allow_null=True, required=False)
|
||||||
'missing_keys': _('Missing key(s): {missing_keys}.'),
|
users = LDAPDNMapField(allow_null=True, required=False)
|
||||||
'invalid_keys': _('Invalid key(s): {invalid_keys}.'),
|
remove_admins = fields.BooleanField(required=False)
|
||||||
}
|
remove_users = fields.BooleanField(required=False)
|
||||||
child_fields = {
|
|
||||||
# 'key': fields.ChildField(),
|
|
||||||
}
|
|
||||||
allow_unknown_keys = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
child = _Forbidden()
|
||||||
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),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LDAPOrganizationMapField(fields.DictField):
|
class LDAPOrganizationMapField(fields.DictField):
|
||||||
@@ -530,17 +541,13 @@ class LDAPOrganizationMapField(fields.DictField):
|
|||||||
child = LDAPSingleOrganizationMapField()
|
child = LDAPSingleOrganizationMapField()
|
||||||
|
|
||||||
|
|
||||||
class LDAPSingleTeamMapField(BaseDictWithChildField):
|
class LDAPSingleTeamMapField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
organization = fields.CharField()
|
||||||
'missing_keys': _('Missing required key for team map: {invalid_keys}.'),
|
users = LDAPDNMapField(allow_null=True, required=False)
|
||||||
'invalid_keys': _('Invalid key(s) for team map: {invalid_keys}.'),
|
remove = fields.BooleanField(required=False)
|
||||||
}
|
|
||||||
child_fields = {
|
child = _Forbidden()
|
||||||
'organization': fields.CharField(),
|
|
||||||
'users': LDAPDNMapField(allow_null=True, required=False),
|
|
||||||
'remove': fields.BooleanField(required=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LDAPTeamMapField(fields.DictField):
|
class LDAPTeamMapField(fields.DictField):
|
||||||
@@ -614,17 +621,14 @@ class SocialMapField(fields.ListField):
|
|||||||
self.fail('type_error', input_type=type(data))
|
self.fail('type_error', input_type=type(data))
|
||||||
|
|
||||||
|
|
||||||
class SocialSingleOrganizationMapField(BaseDictWithChildField):
|
class SocialSingleOrganizationMapField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
admins = SocialMapField(allow_null=True, required=False)
|
||||||
'invalid_keys': _('Invalid key(s) for organization map: {invalid_keys}.'),
|
users = SocialMapField(allow_null=True, required=False)
|
||||||
}
|
remove_admins = fields.BooleanField(required=False)
|
||||||
child_fields = {
|
remove_users = fields.BooleanField(required=False)
|
||||||
'admins': SocialMapField(allow_null=True, required=False),
|
|
||||||
'users': SocialMapField(allow_null=True, required=False),
|
child = _Forbidden()
|
||||||
'remove_admins': fields.BooleanField(required=False),
|
|
||||||
'remove_users': fields.BooleanField(required=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SocialOrganizationMapField(fields.DictField):
|
class SocialOrganizationMapField(fields.DictField):
|
||||||
@@ -632,17 +636,13 @@ class SocialOrganizationMapField(fields.DictField):
|
|||||||
child = SocialSingleOrganizationMapField()
|
child = SocialSingleOrganizationMapField()
|
||||||
|
|
||||||
|
|
||||||
class SocialSingleTeamMapField(BaseDictWithChildField):
|
class SocialSingleTeamMapField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
organization = fields.CharField()
|
||||||
'missing_keys': _('Missing required key for team map: {missing_keys}.'),
|
users = SocialMapField(allow_null=True, required=False)
|
||||||
'invalid_keys': _('Invalid key(s) for team map: {invalid_keys}.'),
|
remove = fields.BooleanField(required=False)
|
||||||
}
|
|
||||||
child_fields = {
|
child = _Forbidden()
|
||||||
'organization': fields.CharField(),
|
|
||||||
'users': SocialMapField(allow_null=True, required=False),
|
|
||||||
'remove': fields.BooleanField(required=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SocialTeamMapField(fields.DictField):
|
class SocialTeamMapField(fields.DictField):
|
||||||
@@ -650,17 +650,11 @@ class SocialTeamMapField(fields.DictField):
|
|||||||
child = SocialSingleTeamMapField()
|
child = SocialSingleTeamMapField()
|
||||||
|
|
||||||
|
|
||||||
class SAMLOrgInfoValueField(BaseDictWithChildField):
|
class SAMLOrgInfoValueField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
name = fields.CharField()
|
||||||
'missing_keys': _('Missing required key(s) for org info record: {missing_keys}.'),
|
displayname = fields.CharField()
|
||||||
}
|
url = fields.URLField()
|
||||||
child_fields = {
|
|
||||||
'name': fields.CharField(),
|
|
||||||
'displayname': fields.CharField(),
|
|
||||||
'url': fields.URLField(),
|
|
||||||
}
|
|
||||||
allow_unknown_keys = True
|
|
||||||
|
|
||||||
|
|
||||||
class SAMLOrgInfoField(fields.DictField):
|
class SAMLOrgInfoField(fields.DictField):
|
||||||
@@ -683,34 +677,22 @@ class SAMLOrgInfoField(fields.DictField):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class SAMLContactField(BaseDictWithChildField):
|
class SAMLContactField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
givenName = fields.CharField()
|
||||||
'missing_keys': _('Missing required key(s) for contact: {missing_keys}.'),
|
emailAddress = fields.EmailField()
|
||||||
}
|
|
||||||
child_fields = {
|
|
||||||
'givenName': fields.CharField(),
|
|
||||||
'emailAddress': fields.EmailField(),
|
|
||||||
}
|
|
||||||
allow_unknown_keys = True
|
|
||||||
|
|
||||||
|
|
||||||
class SAMLIdPField(BaseDictWithChildField):
|
class SAMLIdPField(HybridDictField):
|
||||||
|
|
||||||
default_error_messages = {
|
entity_id = fields.CharField()
|
||||||
'missing_keys': _('Missing required key(s) for IdP: {missing_keys}.'),
|
url = fields.URLField()
|
||||||
}
|
x509cert = fields.CharField(validators=[validate_certificate])
|
||||||
child_fields = {
|
attr_user_permanent_id = fields.CharField(required=False)
|
||||||
'entity_id': fields.CharField(),
|
attr_first_name = fields.CharField(required=False)
|
||||||
'url': fields.URLField(),
|
attr_last_name = fields.CharField(required=False)
|
||||||
'x509cert': fields.CharField(validators=[validate_certificate]),
|
attr_username = fields.CharField(required=False)
|
||||||
'attr_user_permanent_id': fields.CharField(required=False),
|
attr_email = 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
|
|
||||||
|
|
||||||
|
|
||||||
class SAMLEnabledIdPsField(fields.DictField):
|
class SAMLEnabledIdPsField(fields.DictField):
|
||||||
@@ -718,52 +700,49 @@ class SAMLEnabledIdPsField(fields.DictField):
|
|||||||
child = SAMLIdPField()
|
child = SAMLIdPField()
|
||||||
|
|
||||||
|
|
||||||
class SAMLSecurityField(BaseDictWithChildField):
|
class SAMLSecurityField(HybridDictField):
|
||||||
|
|
||||||
child_fields = {
|
nameIdEncrypted = fields.BooleanField(required=False)
|
||||||
'nameIdEncrypted': fields.BooleanField(required=False),
|
authnRequestsSigned = fields.BooleanField(required=False)
|
||||||
'authnRequestsSigned': fields.BooleanField(required=False),
|
logoutRequestSigned = fields.BooleanField(required=False)
|
||||||
'logoutRequestSigned': fields.BooleanField(required=False),
|
logoutResponseSigned = fields.BooleanField(required=False)
|
||||||
'logoutResponseSigned': fields.BooleanField(required=False),
|
signMetadata = fields.BooleanField(required=False)
|
||||||
'signMetadata': fields.BooleanField(required=False),
|
wantMessagesSigned = fields.BooleanField(required=False)
|
||||||
'wantMessagesSigned': fields.BooleanField(required=False),
|
wantAssertionsSigned = fields.BooleanField(required=False)
|
||||||
'wantAssertionsSigned': fields.BooleanField(required=False),
|
wantAssertionsEncrypted = fields.BooleanField(required=False)
|
||||||
'wantAssertionsEncrypted': fields.BooleanField(required=False),
|
wantNameId = fields.BooleanField(required=False)
|
||||||
'wantNameId': fields.BooleanField(required=False),
|
wantNameIdEncrypted = fields.BooleanField(required=False)
|
||||||
'wantNameIdEncrypted': fields.BooleanField(required=False),
|
wantAttributeStatement = fields.BooleanField(required=False)
|
||||||
'wantAttributeStatement': fields.BooleanField(required=False),
|
requestedAuthnContext = fields.StringListBooleanField(required=False)
|
||||||
'requestedAuthnContext': fields.StringListBooleanField(required=False),
|
requestedAuthnContextComparison = fields.CharField(required=False)
|
||||||
'requestedAuthnContextComparison': fields.CharField(required=False),
|
metadataValidUntil = fields.CharField(allow_null=True, required=False)
|
||||||
'metadataValidUntil': fields.CharField(allow_null=True, required=False),
|
metadataCacheDuration = fields.CharField(allow_null=True, required=False)
|
||||||
'metadataCacheDuration': fields.CharField(allow_null=True, required=False),
|
signatureAlgorithm = fields.CharField(allow_null=True, required=False)
|
||||||
'signatureAlgorithm': fields.CharField(allow_null=True, required=False),
|
digestAlgorithm = fields.CharField(allow_null=True, required=False)
|
||||||
'digestAlgorithm': fields.CharField(allow_null=True, required=False),
|
|
||||||
}
|
|
||||||
allow_unknown_keys = True
|
|
||||||
|
|
||||||
|
|
||||||
class SAMLOrgAttrField(BaseDictWithChildField):
|
class SAMLOrgAttrField(HybridDictField):
|
||||||
|
|
||||||
child_fields = {
|
remove = fields.BooleanField(required=False)
|
||||||
'remove': fields.BooleanField(required=False),
|
saml_attr = fields.CharField(required=False, allow_null=True)
|
||||||
'saml_attr': fields.CharField(required=False, allow_null=True),
|
remove_admins = fields.BooleanField(required=False)
|
||||||
'remove_admins': fields.BooleanField(required=False),
|
saml_admin_attr = fields.CharField(required=False, allow_null=True)
|
||||||
'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)
|
||||||
'team': fields.CharField(required=True, allow_null=False),
|
organization = 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)
|
||||||
'team_org_map': fields.ListField(required=False, child=SAMLTeamAttrTeamOrgMapField(), allow_null=True),
|
remove = fields.BooleanField(required=False)
|
||||||
'remove': fields.BooleanField(required=False),
|
saml_attr = fields.CharField(required=False, allow_null=True)
|
||||||
'saml_attr': fields.CharField(required=False, allow_null=True),
|
|
||||||
}
|
child = _Forbidden()
|
||||||
|
|||||||
@@ -33,21 +33,23 @@ class TestSAMLOrgAttrField():
|
|||||||
|
|
||||||
@pytest.mark.parametrize("data, expected", [
|
@pytest.mark.parametrize("data, expected", [
|
||||||
({'remove': 'blah', 'saml_attr': 'foobar'},
|
({'remove': 'blah', 'saml_attr': 'foobar'},
|
||||||
ValidationError('"blah" is not a valid boolean.')),
|
{'remove': ['Must be a valid boolean.']}),
|
||||||
({'remove': True, 'saml_attr': False},
|
({'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'},
|
({'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},
|
({'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'},
|
({'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):
|
def test_internal_value_invalid(self, data, expected):
|
||||||
field = SAMLOrgAttrField()
|
field = SAMLOrgAttrField()
|
||||||
with pytest.raises(type(expected)) as e:
|
with pytest.raises(ValidationError) as e:
|
||||||
field.to_internal_value(data)
|
field.to_internal_value(data)
|
||||||
assert str(e.value) == str(expected)
|
assert e.value.detail == expected
|
||||||
|
|
||||||
|
|
||||||
class TestSAMLTeamAttrField():
|
class TestSAMLTeamAttrField():
|
||||||
@@ -77,36 +79,38 @@ class TestSAMLTeamAttrField():
|
|||||||
@pytest.mark.parametrize("data, expected", [
|
@pytest.mark.parametrize("data, expected", [
|
||||||
({'remove': True, 'saml_attr': 'foobar', 'team_org_map': [
|
({'remove': True, 'saml_attr': 'foobar', 'team_org_map': [
|
||||||
{'team': 'foobar', 'not_a_valid_key': 'blah', 'organization': 'Ansible'},
|
{'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': [
|
({'remove': False, 'saml_attr': 'foobar', 'team_org_map': [
|
||||||
{'organization': 'Ansible'},
|
{'organization': 'Ansible'},
|
||||||
]}, ValidationError('Missing key(s): "team".')),
|
]}, {'team_org_map': {0: {'team': ['This field is required.']}}}),
|
||||||
({'remove': False, 'saml_attr': 'foobar', 'team_org_map': [
|
({'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):
|
def test_internal_value_invalid(self, data, expected):
|
||||||
field = SAMLTeamAttrField()
|
field = SAMLTeamAttrField()
|
||||||
with pytest.raises(type(expected)) as e:
|
with pytest.raises(ValidationError) as e:
|
||||||
field.to_internal_value(data)
|
field.to_internal_value(data)
|
||||||
assert str(e.value) == str(expected)
|
assert e.value.detail == expected
|
||||||
|
|
||||||
|
|
||||||
class TestLDAPGroupTypeParamsField():
|
class TestLDAPGroupTypeParamsField():
|
||||||
|
|
||||||
@pytest.mark.parametrize("group_type, data, expected", [
|
@pytest.mark.parametrize("group_type, data, expected", [
|
||||||
('LDAPGroupType', {'name_attr': 'user', 'bob': ['a', 'b'], 'scooter': 'hello'},
|
('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'},
|
('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',
|
('PosixUIDGroupType', {'name_attr': 'user', 'member_attr': 'west', 'ldap_group_user_attr': 'legacyThing',
|
||||||
'bob': ['a', 'b'], 'scooter': 'hello'},
|
'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):
|
def test_internal_value_invalid(self, group_type, data, expected):
|
||||||
field = LDAPGroupTypeParamsField()
|
field = LDAPGroupTypeParamsField()
|
||||||
field.get_depends_on = mock.MagicMock(return_value=group_type)
|
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)
|
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' %}
|
{% extends 'rest_framework/base.html' %}
|
||||||
{% load i18n staticfiles %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% block title %}{{ name }} · {% trans 'AWX REST API' %}{% endblock %}
|
{% block title %}{{ name }} · {% trans 'AWX REST API' %}{% endblock %}
|
||||||
|
|
||||||
@@ -48,6 +48,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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 %}
|
{% block script %}
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div class="container">
|
<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-solo==1.1.3
|
||||||
django-split-settings==0.3.0
|
django-split-settings==0.3.0
|
||||||
django-taggit==0.22.2
|
django-taggit==0.22.2
|
||||||
djangorestframework==3.7.7
|
djangorestframework==3.9.4
|
||||||
djangorestframework-yaml==1.0.3
|
djangorestframework-yaml==1.0.3
|
||||||
irc==16.2
|
irc==16.2
|
||||||
jinja2==2.10.1
|
jinja2==2.10.1
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ asgi-amqp==1.1.3
|
|||||||
asgiref==1.1.2 # via asgi-amqp, channels, daphne
|
asgiref==1.1.2 # via asgi-amqp, channels, daphne
|
||||||
asn1crypto==0.24.0 # via cryptography
|
asn1crypto==0.24.0 # via cryptography
|
||||||
attrs==19.1.0 # via automat, service-identity, twisted
|
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
|
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-keyvault==1.1.0
|
||||||
azure-nspkg==3.0.2 # via azure-keyvault
|
azure-nspkg==3.0.2 # via azure-keyvault
|
||||||
billiard==3.6.0.0 # via celery
|
billiard==3.6.0.0 # via celery
|
||||||
@@ -43,7 +43,7 @@ django-split-settings==0.3.0
|
|||||||
django-taggit==0.22.2
|
django-taggit==0.22.2
|
||||||
django==1.11.20
|
django==1.11.20
|
||||||
djangorestframework-yaml==1.0.3
|
djangorestframework-yaml==1.0.3
|
||||||
djangorestframework==3.7.7
|
djangorestframework==3.9.4
|
||||||
future==0.16.0 # via django-radius
|
future==0.16.0 # via django-radius
|
||||||
hyperlink==19.0.0 # via twisted
|
hyperlink==19.0.0 # via twisted
|
||||||
idna==2.8 # via hyperlink, requests, 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.stream==2.0 # via irc
|
||||||
jaraco.text==3.0 # via irc, jaraco.collections
|
jaraco.text==3.0 # via irc, jaraco.collections
|
||||||
jinja2==2.10.1
|
jinja2==2.10.1
|
||||||
jsonpickle==1.1 # via asgi-amqp
|
jsonpickle==1.2 # via asgi-amqp
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
kombu==4.5.0 # via asgi-amqp, celery
|
kombu==4.5.0 # via asgi-amqp, celery
|
||||||
lockfile==0.12.2 # via python-daemon
|
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
|
pyopenssl==19.0.0 # via twisted
|
||||||
pyparsing==2.2.0
|
pyparsing==2.2.0
|
||||||
pyrad==2.1 # via django-radius
|
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-daemon==2.2.3 # via ansible-runner
|
||||||
python-dateutil==2.7.2
|
python-dateutil==2.7.2
|
||||||
python-ldap==3.2.0 # via django-auth-ldap
|
python-ldap==3.2.0 # via django-auth-ldap
|
||||||
|
|||||||
Reference in New Issue
Block a user