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:
softwarefactory-project-zuul[bot] 2019-06-12 18:41:05 +00:00 committed by GitHub
commit e70c7ab458
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 258 additions and 599 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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,

View File

@ -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):

View File

@ -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,)

View File

@ -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():

View File

@ -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()

View File

@ -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',

View File

@ -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()

View File

@ -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

View File

@ -1,5 +1,5 @@
{% extends 'rest_framework/base.html' %}
{% load i18n staticfiles %}
{% load i18n static %}
{% block title %}{{ name }} &middot; {% 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">

View File

@ -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 %}&nbsp;{% 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>

View File

@ -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

View File

@ -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