mirror of
https://github.com/ansible/awx.git
synced 2026-01-22 06:58:06 -03:30
Fix migration issues, tests, and templates
This commit is contained in:
parent
6d6bbbb627
commit
96904968d8
@ -22,6 +22,7 @@ from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
# AWX
|
||||
from awx.main.utils import get_type_for_model, to_python_boolean
|
||||
from awx.main.utils.db import get_all_field_names
|
||||
from awx.main.models.credential import CredentialType
|
||||
from awx.main.models.rbac import RoleAncestorEntry
|
||||
|
||||
@ -70,7 +71,7 @@ class TypeFilterBackend(BaseFilterBackend):
|
||||
types_map[ct_type] = ct.pk
|
||||
model = queryset.model
|
||||
model_type = get_type_for_model(model)
|
||||
if 'polymorphic_ctype' in model._meta.get_all_field_names():
|
||||
if 'polymorphic_ctype' in get_all_field_names(model):
|
||||
types_pks = set([v for k,v in types_map.items() if k in types])
|
||||
queryset = queryset.filter(polymorphic_ctype_id__in=types_pks)
|
||||
elif model_type in types:
|
||||
@ -119,7 +120,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
'last_updated': 'last_job_run',
|
||||
}.get(name, name)
|
||||
|
||||
if name == 'type' and 'polymorphic_ctype' in model._meta.get_all_field_names():
|
||||
if name == 'type' and 'polymorphic_ctype' in get_all_field_names(model):
|
||||
name = 'polymorphic_ctype'
|
||||
new_parts.append('polymorphic_ctype__model')
|
||||
else:
|
||||
@ -136,7 +137,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
new_parts.pop()
|
||||
new_parts.append(name_alt)
|
||||
else:
|
||||
field = model._meta.get_field_by_name(name)[0]
|
||||
field = model._meta.get_field(name)
|
||||
if isinstance(field, ForeignObjectRel) and getattr(field.field, '__prevent_search__', False):
|
||||
raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
|
||||
elif getattr(field, '__prevent_search__', False):
|
||||
@ -375,7 +376,7 @@ class OrderByBackend(BaseFilterBackend):
|
||||
# given the limited number of views with multiple types,
|
||||
# sorting on polymorphic_ctype.model is effectively the same.
|
||||
new_order_by = []
|
||||
if 'polymorphic_ctype' in queryset.model._meta.get_all_field_names():
|
||||
if 'polymorphic_ctype' in get_all_field_names(queryset.model):
|
||||
for field in order_by:
|
||||
if field == 'type':
|
||||
new_order_by.append('polymorphic_ctype__model')
|
||||
|
||||
@ -31,6 +31,7 @@ from rest_framework import views
|
||||
from awx.api.filters import FieldLookupBackend
|
||||
from awx.main.models import * # noqa
|
||||
from awx.main.utils import * # noqa
|
||||
from awx.main.utils.db import get_all_field_names
|
||||
from awx.api.serializers import ResourceAccessListElementSerializer
|
||||
from awx.api.versioning import URLPathVersioning, get_request_version
|
||||
from awx.api.metadata import SublistAttachDetatchMetadata
|
||||
@ -321,8 +322,7 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
|
||||
return page
|
||||
|
||||
def get_description_context(self):
|
||||
opts = self.model._meta
|
||||
if 'username' in opts.get_all_field_names():
|
||||
if 'username' in get_all_field_names(self.model):
|
||||
order_field = 'username'
|
||||
else:
|
||||
order_field = 'name'
|
||||
|
||||
@ -477,7 +477,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
||||
return super(BaseSerializer, self).run_validation(data)
|
||||
except ValidationError as exc:
|
||||
# Avoid bug? in DRF if exc.detail happens to be a list instead of a dict.
|
||||
raise ValidationError(detail=serializers.get_validation_error_detail(exc))
|
||||
raise ValidationError(detail=serializers.as_serializer_error(exc))
|
||||
|
||||
def get_validation_exclusions(self, obj=None):
|
||||
# Borrowed from DRF 2.x - return model fields that should be excluded
|
||||
|
||||
@ -27,7 +27,6 @@ from django.utils.timezone import now
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.servers.basehttp import FileWrapper
|
||||
from django.http import HttpResponse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -53,7 +52,9 @@ import qsstats
|
||||
import ansiconv
|
||||
|
||||
# Python Social Auth
|
||||
from social.backends.utils import load_backends
|
||||
from social_core.backends.utils import load_backends
|
||||
|
||||
from wsgiref.util import FileWrapper
|
||||
|
||||
# AWX
|
||||
from awx.main.tasks import send_notifications
|
||||
|
||||
@ -54,12 +54,12 @@ def get_object_from_data(field, Model, data, obj=None):
|
||||
# Calling method needs to deal with non-existence of key
|
||||
raise ParseError(_("Required related field %s for permission check." % field))
|
||||
|
||||
if isinstance(raw_value, Model):
|
||||
return raw_value
|
||||
elif raw_value is None:
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
if isinstance(raw_value, Model):
|
||||
return raw_value
|
||||
elif raw_value is None:
|
||||
return None
|
||||
else:
|
||||
new_pk = int(raw_value)
|
||||
# Avoid database query by comparing pk to model for similarity
|
||||
if obj and new_pk == getattr(obj, '%s_id' % field, None):
|
||||
@ -67,8 +67,8 @@ def get_object_from_data(field, Model, data, obj=None):
|
||||
else:
|
||||
# Get the new resource from the database
|
||||
return get_object_or_400(Model, pk=new_pk)
|
||||
except (TypeError, ValueError):
|
||||
raise ParseError(_("Bad data found in related field %s." % field))
|
||||
except (TypeError, ValueError):
|
||||
raise ParseError(_("Bad data found in related field %s." % field))
|
||||
|
||||
|
||||
class StateConflict(ValidationError):
|
||||
|
||||
@ -18,12 +18,12 @@ from django.db.models.signals import (
|
||||
)
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db import models
|
||||
from django.db.models.fields.related import (
|
||||
add_lazy_relation,
|
||||
SingleRelatedObjectDescriptor,
|
||||
ReverseSingleRelatedObjectDescriptor,
|
||||
ManyRelatedObjectsDescriptor,
|
||||
ReverseManyRelatedObjectsDescriptor,
|
||||
from django.db.models.fields.related import add_lazy_relation
|
||||
from django.db.models.fields.related_descriptors import (
|
||||
ReverseOneToOneDescriptor,
|
||||
ForwardManyToOneDescriptor,
|
||||
ManyToManyDescriptor,
|
||||
ReverseManyToOneDescriptor,
|
||||
)
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -96,7 +96,7 @@ class JSONBField(upstream_JSONBField):
|
||||
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
||||
|
||||
|
||||
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
||||
class AutoSingleRelatedObjectDescriptor(ReverseOneToOneDescriptor):
|
||||
"""Descriptor for access to the object from its related class."""
|
||||
|
||||
def __get__(self, instance, instance_type=None):
|
||||
@ -139,7 +139,7 @@ def resolve_role_field(obj, field):
|
||||
raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj))))
|
||||
ret.append(obj.id)
|
||||
else:
|
||||
if type(obj) is ManyRelatedObjectsDescriptor:
|
||||
if type(obj) is ManyToManyDescriptor:
|
||||
for o in obj.all():
|
||||
ret += resolve_role_field(o, field_components[1])
|
||||
else:
|
||||
@ -179,7 +179,7 @@ def is_implicit_parent(parent_role, child_role):
|
||||
return False
|
||||
|
||||
|
||||
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
||||
class ImplicitRoleDescriptor(ForwardManyToOneDescriptor):
|
||||
pass
|
||||
|
||||
|
||||
@ -230,18 +230,18 @@ class ImplicitRoleField(models.ForeignKey):
|
||||
field_name, sep, field_attr = field_name.partition('.')
|
||||
field = getattr(cls, field_name)
|
||||
|
||||
if type(field) is ReverseManyRelatedObjectsDescriptor or \
|
||||
type(field) is ManyRelatedObjectsDescriptor:
|
||||
if type(field) is ReverseManyToOneDescriptor or \
|
||||
type(field) is ManyToManyDescriptor:
|
||||
|
||||
if '.' in field_attr:
|
||||
raise Exception('Referencing deep roles through ManyToMany fields is unsupported.')
|
||||
|
||||
if type(field) is ReverseManyRelatedObjectsDescriptor:
|
||||
if type(field) is ReverseManyToOneDescriptor:
|
||||
sender = field.through
|
||||
else:
|
||||
sender = field.related.through
|
||||
|
||||
reverse = type(field) is ManyRelatedObjectsDescriptor
|
||||
reverse = type(field) is ManyToManyDescriptor
|
||||
m2m_changed.connect(self.m2m_update(field_attr, reverse), sender, weak=False)
|
||||
|
||||
def m2m_update(self, field_attr, _reverse):
|
||||
|
||||
@ -132,7 +132,7 @@ class BaseTestMixin(MockCommonlySlowTestMixin):
|
||||
# Set flag so that task chain works with unit tests.
|
||||
settings.CELERY_UNIT_TEST = True
|
||||
settings.SYSTEM_UUID='00000000-0000-0000-0000-000000000000'
|
||||
settings.BROKER_URL='redis://localhost:55672/'
|
||||
settings.CELERY_BROKER_URL='redis://localhost:55672/'
|
||||
settings.CALLBACK_QUEUE = 'callback_tasks_unit'
|
||||
|
||||
# Disable socket notifications for unit tests.
|
||||
|
||||
@ -92,24 +92,21 @@ class TestJobTemplateCopyEdit:
|
||||
credential=None, ask_credential_on_launch=True,
|
||||
name='deploy-job-template'
|
||||
)
|
||||
serializer = JobTemplateSerializer(jt_res)
|
||||
serializer.context = self.fake_context(admin_user)
|
||||
serializer = JobTemplateSerializer(jt_res, context=self.fake_context(admin_user))
|
||||
response = serializer.to_representation(jt_res)
|
||||
assert not response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
|
||||
def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user):
|
||||
"Absent a validation error, system admins can do everything"
|
||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
||||
serializer.context = self.fake_context(admin_user)
|
||||
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(admin_user))
|
||||
response = serializer.to_representation(jt_copy_edit)
|
||||
assert response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
|
||||
def test_org_admin_copy_edit(self, jt_copy_edit, org_admin):
|
||||
"Organization admins SHOULD be able to copy a JT firmly in their org"
|
||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
||||
serializer.context = self.fake_context(org_admin)
|
||||
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
|
||||
response = serializer.to_representation(jt_copy_edit)
|
||||
assert response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
@ -125,8 +122,7 @@ class TestJobTemplateCopyEdit:
|
||||
jt_copy_edit.credential = machine_credential
|
||||
jt_copy_edit.save()
|
||||
|
||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
||||
serializer.context = self.fake_context(org_admin)
|
||||
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
|
||||
response = serializer.to_representation(jt_copy_edit)
|
||||
assert not response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
@ -140,8 +136,7 @@ class TestJobTemplateCopyEdit:
|
||||
jt_copy_edit.admin_role.members.add(rando)
|
||||
jt_copy_edit.save()
|
||||
|
||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
||||
serializer.context = self.fake_context(rando)
|
||||
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
|
||||
response = serializer.to_representation(jt_copy_edit)
|
||||
assert not response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
@ -155,8 +150,7 @@ class TestJobTemplateCopyEdit:
|
||||
jt_copy_edit.project.admin_role.members.add(rando)
|
||||
jt_copy_edit.project.save()
|
||||
|
||||
serializer = JobTemplateSerializer(jt_copy_edit)
|
||||
serializer.context = self.fake_context(rando)
|
||||
serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
|
||||
response = serializer.to_representation(jt_copy_edit)
|
||||
assert response['summary_fields']['user_capabilities']['copy']
|
||||
assert response['summary_fields']['user_capabilities']['edit']
|
||||
|
||||
@ -39,8 +39,8 @@ def run_command(name, *args, **options):
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"username,password,expected,changed", [
|
||||
('admin', 'dingleberry', 'Password updated\n', True),
|
||||
('admin', 'admin', 'Password not updated\n', False),
|
||||
('admin', 'dingleberry', 'Password updated', True),
|
||||
('admin', 'admin', 'Password not updated', False),
|
||||
(None, 'foo', 'username required', False),
|
||||
('admin', None, 'password required', False),
|
||||
]
|
||||
|
||||
@ -92,7 +92,7 @@ class TestInvalidOptionsFunctional:
|
||||
cmd = inventory_import.Command()
|
||||
with mock.patch('django.db.transaction.rollback'):
|
||||
with pytest.raises(IOError) as err:
|
||||
cmd.handle_noargs(
|
||||
cmd.handle(
|
||||
inventory_id=inventory.id,
|
||||
source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid')
|
||||
assert 'Source does not exist' in err.value.message
|
||||
@ -100,14 +100,14 @@ class TestInvalidOptionsFunctional:
|
||||
def test_invalid_inventory_id(self):
|
||||
cmd = inventory_import.Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(inventory_id=42, source='/notapath/shouldnotmatter')
|
||||
cmd.handle(inventory_id=42, source='/notapath/shouldnotmatter')
|
||||
assert 'id = 42' in err.value.message
|
||||
assert 'cannot be found' in err.value.message
|
||||
|
||||
def test_invalid_inventory_name(self):
|
||||
cmd = inventory_import.Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(inventory_name='fooservers', source='/notapath/shouldnotmatter')
|
||||
cmd.handle(inventory_name='fooservers', source='/notapath/shouldnotmatter')
|
||||
assert 'name = fooservers' in err.value.message
|
||||
assert 'cannot be found' in err.value.message
|
||||
|
||||
@ -122,7 +122,7 @@ class TestINIImports:
|
||||
@mock.patch.object(inventory_import.AnsibleInventoryLoader, 'load', mock.MagicMock(return_value=TEST_MEM_OBJECTS))
|
||||
def test_inventory_single_ini_import(self, inventory, capsys):
|
||||
cmd = inventory_import.Command()
|
||||
r = cmd.handle_noargs(
|
||||
r = cmd.handle(
|
||||
inventory_id=inventory.pk, source=__file__,
|
||||
method='backport')
|
||||
out, err = capsys.readouterr()
|
||||
@ -192,7 +192,7 @@ class TestINIImports:
|
||||
)
|
||||
def test_hostvars_are_saved(self, inventory):
|
||||
cmd = inventory_import.Command()
|
||||
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')
|
||||
cmd.handle(inventory_id=inventory.pk, source='doesnt matter')
|
||||
assert inventory.hosts.count() == 1
|
||||
h = inventory.hosts.all()[0]
|
||||
assert h.name == 'foo'
|
||||
@ -219,4 +219,4 @@ class TestINIImports:
|
||||
)
|
||||
def test_recursive_group_error(self, inventory):
|
||||
cmd = inventory_import.Command()
|
||||
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter')
|
||||
cmd.handle(inventory_id=inventory.pk, source='doesnt matter')
|
||||
|
||||
@ -64,7 +64,7 @@ def celery_memory_broker():
|
||||
|
||||
Allows django signal code to execute without the need for redis
|
||||
'''
|
||||
settings.BROKER_URL='memory://localhost/'
|
||||
settings.CELERY_BROKER_URL='memory://localhost/'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -38,7 +38,7 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
|
||||
'Users can see all roles they have access to, but not all roles'
|
||||
this_user = user('user-test_get_roles_list_user')
|
||||
organization.member_role.members.add(this_user)
|
||||
custom_role = Role.objects.create(name='custom_role-test_get_roles_list_user')
|
||||
custom_role = Role.objects.create(role_field='custom_role-test_get_roles_list_user')
|
||||
organization.member_role.children.add(custom_role)
|
||||
|
||||
url = reverse('api:role_list')
|
||||
@ -128,7 +128,7 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
|
||||
organization.member_role.members.add(alice)
|
||||
organization.admin_role.members.add(bob)
|
||||
organization.member_role.members.add(bob)
|
||||
custom_role = Role.objects.create(name='custom_role-test_user_view_admin_roles_list')
|
||||
custom_role = Role.objects.create(role_field='custom_role-test_user_view_admin_roles_list')
|
||||
organization.member_role.children.add(custom_role)
|
||||
team.member_role.members.add(bob)
|
||||
|
||||
|
||||
@ -125,11 +125,15 @@ class TestWorkflowJobTemplateNodeSerializerCharPrompts():
|
||||
serializer = WorkflowJobTemplateNodeSerializer()
|
||||
node = WorkflowJobTemplateNode(pk=1)
|
||||
node.char_prompts = {'limit': 'webservers'}
|
||||
serializer.instance = node
|
||||
|
||||
view = FakeView(node)
|
||||
view.request = FakeRequest()
|
||||
view.request.method = "PATCH"
|
||||
serializer.context = {'view': view}
|
||||
|
||||
serializer = WorkflowJobTemplateNodeSerializer()
|
||||
serializer = WorkflowJobTemplateNodeSerializer(context={'view':view})
|
||||
serializer.instance = node
|
||||
|
||||
return serializer
|
||||
|
||||
def test_change_single_field(self, WFJT_serializer):
|
||||
|
||||
@ -9,7 +9,6 @@ from awx.api.views import (
|
||||
JobTemplateLabelList,
|
||||
JobTemplateSurveySpec,
|
||||
InventoryInventorySourcesUpdate,
|
||||
InventoryHostsList,
|
||||
HostInsights,
|
||||
)
|
||||
|
||||
@ -17,8 +16,6 @@ from awx.main.models import (
|
||||
Host,
|
||||
)
|
||||
|
||||
from awx.main.managers import HostManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_response_new(mocker):
|
||||
@ -223,17 +220,3 @@ class TestHostInsights():
|
||||
|
||||
assert resp.data['error'] == 'The Insights Credential for "inventory_name_here" was not found.'
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
class TestInventoryHostsList(object):
|
||||
|
||||
def test_host_list_smart_inventory(self, mocker):
|
||||
Inventory = namedtuple('Inventory', ['kind', 'host_filter', 'hosts', 'organization_id'])
|
||||
obj = Inventory(kind='smart', host_filter='localhost', hosts=HostManager(), organization_id=None)
|
||||
obj.hosts.instance = obj
|
||||
|
||||
with mock.patch.object(InventoryHostsList, 'get_parent_object', return_value=obj):
|
||||
with mock.patch('awx.main.utils.filters.SmartFilter.query_from_string') as mock_query:
|
||||
view = InventoryHostsList()
|
||||
view.get_queryset()
|
||||
mock_query.assert_called_once_with('localhost')
|
||||
|
||||
@ -19,7 +19,7 @@ class TestInvalidOptions:
|
||||
def test_invalid_options_no_options_specified(self):
|
||||
cmd = Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs()
|
||||
cmd.handle()
|
||||
assert 'inventory-id' in err.value.message
|
||||
assert 'required' in err.value.message
|
||||
|
||||
@ -27,7 +27,7 @@ class TestInvalidOptions:
|
||||
# You can not specify both name and if of the inventory
|
||||
cmd = Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(
|
||||
cmd.handle(
|
||||
inventory_id=42, inventory_name='my-inventory'
|
||||
)
|
||||
assert 'inventory-id' in err.value.message
|
||||
@ -37,7 +37,7 @@ class TestInvalidOptions:
|
||||
# You can't overwrite and keep_vars at the same time, that wouldn't make sense
|
||||
cmd = Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(
|
||||
cmd.handle(
|
||||
inventory_id=42, overwrite=True, keep_vars=True
|
||||
)
|
||||
assert 'overwrite-vars' in err.value.message
|
||||
@ -47,13 +47,13 @@ class TestInvalidOptions:
|
||||
# Need a source to import
|
||||
cmd = Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(
|
||||
cmd.handle(
|
||||
inventory_id=42, overwrite=True, keep_vars=True
|
||||
)
|
||||
assert 'overwrite-vars' in err.value.message
|
||||
assert 'exclusive' in err.value.message
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(
|
||||
cmd.handle(
|
||||
inventory_id=42, overwrite_vars=True, keep_vars=True
|
||||
)
|
||||
assert 'overwrite-vars' in err.value.message
|
||||
@ -62,7 +62,7 @@ class TestInvalidOptions:
|
||||
def test_invalid_options_missing_source(self):
|
||||
cmd = Command()
|
||||
with pytest.raises(CommandError) as err:
|
||||
cmd.handle_noargs(inventory_id=42)
|
||||
cmd.handle(inventory_id=42)
|
||||
assert '--source' in err.value.message
|
||||
assert 'required' in err.value.message
|
||||
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
import pytest
|
||||
import mock
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from awx.main.models.rbac import (
|
||||
Role,
|
||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
ROLE_SINGLETON_SYSTEM_AUDITOR
|
||||
)
|
||||
from awx.main.models import Organization, JobTemplate, Project
|
||||
|
||||
from awx.main.fields import (
|
||||
ImplicitRoleField,
|
||||
is_implicit_parent
|
||||
)
|
||||
|
||||
|
||||
def apply_fake_roles(obj):
|
||||
'''
|
||||
Creates an un-saved role for all the implicit role fields on an object
|
||||
'''
|
||||
for fd in obj._meta.fields:
|
||||
if not isinstance(fd, ImplicitRoleField):
|
||||
continue
|
||||
r = Role(role_field=fd.name)
|
||||
setattr(obj, fd.name, r)
|
||||
with mock.patch('django.contrib.contenttypes.fields.GenericForeignKey.get_content_type') as mck_ct:
|
||||
mck_ct.return_value = ContentType(model=obj._meta.model_name)
|
||||
r.content_object = obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def system_administrator():
|
||||
return Role(
|
||||
role_field=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
singleton_name=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def system_auditor():
|
||||
return Role(
|
||||
role_field=ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||
singleton_name=ROLE_SINGLETON_SYSTEM_AUDITOR
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def organization():
|
||||
o = Organization(name='unit-test-org')
|
||||
apply_fake_roles(o)
|
||||
return o
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project(organization):
|
||||
p = Project(name='unit-test-proj', organization=organization)
|
||||
apply_fake_roles(p)
|
||||
return p
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def job_template(project):
|
||||
jt = JobTemplate(name='unit-test-jt', project=project)
|
||||
apply_fake_roles(jt)
|
||||
return jt
|
||||
|
||||
|
||||
class TestIsImplicitParent:
|
||||
'''
|
||||
Tests to confirm that `is_implicit_parent` gives the right answers
|
||||
'''
|
||||
def test_sys_admin_implicit_parent(self, organization, system_administrator):
|
||||
assert is_implicit_parent(
|
||||
parent_role=system_administrator,
|
||||
child_role=organization.admin_role
|
||||
)
|
||||
|
||||
|
||||
def test_admin_is_parent_of_member_role(self, organization):
|
||||
assert is_implicit_parent(
|
||||
parent_role=organization.admin_role,
|
||||
child_role=organization.member_role
|
||||
)
|
||||
|
||||
def test_member_is_not_parent_of_admin_role(self, organization):
|
||||
assert not is_implicit_parent(
|
||||
parent_role=organization.member_role,
|
||||
child_role=organization.admin_role
|
||||
)
|
||||
|
||||
def test_second_level_implicit_parent_role(self, job_template, organization):
|
||||
assert is_implicit_parent(
|
||||
parent_role=organization.admin_role,
|
||||
child_role=job_template.admin_role
|
||||
)
|
||||
|
||||
def test_second_level_is_not_an_implicit_parent_role(self, job_template, organization):
|
||||
assert not is_implicit_parent(
|
||||
parent_role=organization.member_role,
|
||||
child_role=job_template.admin_role
|
||||
)
|
||||
@ -8,11 +8,11 @@ from datetime import timedelta
|
||||
('admin_checks', 'awx.main.tasks.run_administrative_checks'),
|
||||
('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'),
|
||||
])
|
||||
def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path):
|
||||
assert job_name in settings.CELERYBEAT_SCHEDULE
|
||||
assert 'schedule' in settings.CELERYBEAT_SCHEDULE[job_name]
|
||||
assert type(settings.CELERYBEAT_SCHEDULE[job_name]['schedule']) is timedelta
|
||||
assert settings.CELERYBEAT_SCHEDULE[job_name]['task'] == function_path
|
||||
def test_CELERY_BEAT_SCHEDULE(mocker, job_name, function_path):
|
||||
assert job_name in settings.CELERY_BEAT_SCHEDULE
|
||||
assert 'schedule' in settings.CELERY_BEAT_SCHEDULE[job_name]
|
||||
assert type(settings.CELERY_BEAT_SCHEDULE[job_name]['schedule']) is timedelta
|
||||
assert settings.CELERY_BEAT_SCHEDULE[job_name]['task'] == function_path
|
||||
|
||||
# Ensures that the function exists
|
||||
mocker.patch(function_path)
|
||||
|
||||
@ -16,6 +16,7 @@ from awx.main.access import (
|
||||
from awx.conf.license import LicenseForbids
|
||||
from awx.main.models import (
|
||||
Credential,
|
||||
CredentialType,
|
||||
Inventory,
|
||||
Project,
|
||||
Role,
|
||||
@ -57,7 +58,7 @@ class TestRelatedFieldAccess:
|
||||
def test_new_with_bad_data(self, access, mocker):
|
||||
data = {'related': 3.1415}
|
||||
with pytest.raises(ParseError):
|
||||
access.check_related('related', mocker.MagicMock, data)
|
||||
access.check_related('related', mocker.MagicMock(), data)
|
||||
|
||||
def test_new_mandatory_fail(self, access, mocker):
|
||||
access.user.is_superuser = False
|
||||
@ -118,10 +119,18 @@ class TestRelatedFieldAccess:
|
||||
@pytest.fixture
|
||||
def job_template_with_ids(job_template_factory):
|
||||
# Create non-persisted objects with IDs to send to job_template_factory
|
||||
credential = Credential(id=1, pk=1, name='testcred', kind='ssh')
|
||||
net_cred = Credential(id=2, pk=2, name='testnetcred', kind='net')
|
||||
cloud_cred = Credential(id=3, pk=3, name='testcloudcred', kind='aws')
|
||||
vault_cred = Credential(id=4, pk=4, name='testnetcred', kind='vault')
|
||||
ssh_type = CredentialType(kind='ssh')
|
||||
credential = Credential(id=1, pk=1, name='testcred', credential_type=ssh_type)
|
||||
|
||||
net_type = CredentialType(kind='net')
|
||||
net_cred = Credential(id=2, pk=2, name='testnetcred', credential_type=net_type)
|
||||
|
||||
cloud_type = CredentialType(kind='aws')
|
||||
cloud_cred = Credential(id=3, pk=3, name='testcloudcred', credential_type=cloud_type)
|
||||
|
||||
vault_type = CredentialType(kind='vault')
|
||||
vault_cred = Credential(id=4, pk=4, name='testnetcred', credential_type=vault_type)
|
||||
|
||||
inv = Inventory(id=11, pk=11, name='testinv')
|
||||
proj = Project(id=14, pk=14, name='testproj')
|
||||
|
||||
|
||||
@ -450,7 +450,7 @@ def copy_model_by_class(obj1, Class2, fields, kwargs):
|
||||
elif not isinstance(Class2._meta.get_field(field_name), (ForeignObjectRel, ManyToManyField)):
|
||||
create_kwargs[field_name] = kwargs[field_name]
|
||||
elif hasattr(obj1, field_name):
|
||||
field_obj = obj1._meta.get_field_by_name(field_name)[0]
|
||||
field_obj = obj1._meta.get_field(field_name)
|
||||
if not isinstance(field_obj, ManyToManyField):
|
||||
create_kwargs[field_name] = getattr(obj1, field_name)
|
||||
|
||||
@ -471,7 +471,7 @@ def copy_m2m_relationships(obj1, obj2, fields, kwargs=None):
|
||||
'''
|
||||
for field_name in fields:
|
||||
if hasattr(obj1, field_name):
|
||||
field_obj = obj1._meta.get_field_by_name(field_name)[0]
|
||||
field_obj = obj1._meta.get_field(field_name)
|
||||
if isinstance(field_obj, ManyToManyField):
|
||||
# Many to Many can be specified as field_name
|
||||
src_field_value = getattr(obj1, field_name)
|
||||
|
||||
@ -6,6 +6,7 @@ from django.db.migrations.loader import MigrationLoader
|
||||
from django.db import connection
|
||||
|
||||
# Python
|
||||
from itertools import chain
|
||||
import re
|
||||
|
||||
|
||||
@ -20,3 +21,15 @@ def get_tower_migration_version():
|
||||
if migration_version > v:
|
||||
v = migration_version
|
||||
return v
|
||||
|
||||
|
||||
def get_all_field_names(model):
|
||||
# Implements compatibility with _meta.get_all_field_names
|
||||
# See: https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api
|
||||
return list(set(chain.from_iterable(
|
||||
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
|
||||
for field in model._meta.get_fields()
|
||||
# For complete backwards compatibility, you may want to exclude
|
||||
# GenericForeignKey from the results.
|
||||
if not (field.many_to_one and field.related_model is None)
|
||||
)))
|
||||
|
||||
@ -1,270 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
{# Copy of base.html from rest_framework with minor AWX change. #}
|
||||
{% load staticfiles %}
|
||||
{% load rest_framework %}
|
||||
{% load i18n %}
|
||||
{% load rest_framework %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
<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 %}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" %}"/>
|
||||
{% block meta %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
{% 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 %}
|
||||
<title>{% block title %}{% if name %}{{ name }} – {% endif %}Django REST framework{% endblock %}</title>
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
{% 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 %}
|
||||
|
||||
{% 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 %}">
|
||||
<div class="container">
|
||||
<span>
|
||||
{% block branding %}
|
||||
<a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
|
||||
Django REST framework <span class="version">{{ version }}</span>
|
||||
</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 %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<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 %}
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content">
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
{% 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="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">GET</a>
|
||||
{% block body %}
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
|
||||
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="{% trans '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="{% blocktrans %}Make a GET request on the {{ name }} resource with the format set to `{{ format }}`{% endblocktrans %}">{{ format }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">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="{% blocktrans %}Make an OPTIONS request on the {{ name }} resource{% endblocktrans %}">OPTIONS</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if delete_form %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
||||
<button class="btn btn-danger js-tooltip" title="{% blocktrans %}Make a DELETE request on the {{ name }} resource{% endblocktrans %}">DELETE</button>
|
||||
</form>
|
||||
{% 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 class="content-main">
|
||||
<div class="page-header">
|
||||
<h1>{{ name }}</h1>
|
||||
</div>
|
||||
<div style="float:left">
|
||||
{% block description %}
|
||||
{{ description }}
|
||||
<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 %}
|
||||
</div>
|
||||
</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 %}
|
||||
|
||||
{% if paginator %}
|
||||
<nav style="float: right">
|
||||
{% get_pagination_html paginator %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
|
||||
<div class="request-info" style="clear: both" >
|
||||
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div id="content" role="main" aria-label="{% trans "content" %}">
|
||||
{% block content %}
|
||||
|
||||
<div class="response-info">
|
||||
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
||||
{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
|
||||
{% endfor %}
|
||||
{# Original line below had the side effect of also escaping content: #}
|
||||
{# </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} #}
|
||||
{# For Ansible Tower, disable automatic URL creation and move content outside of autoescape off block. #}
|
||||
{% endautoescape %}</span>{{ content }}</pre>
|
||||
<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>
|
||||
|
||||
{% if display_edit_forms %}
|
||||
<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 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 %}
|
||||
{% if paginator %}
|
||||
<nav style="float: right">
|
||||
{% get_pagination_html paginator %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<div class="well tab-content">
|
||||
<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 %}{% for key, val in response_headers|items %}
|
||||
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
|
||||
{# 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 %}
|
||||
<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>
|
||||
<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>
|
||||
{% csrf_token %}
|
||||
{{ post_form }}
|
||||
{% include "rest_framework/raw_data_form.html" %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
|
||||
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div {% if raw_data_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="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% 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 or raw_data_put_form or raw_data_patch_form %}
|
||||
<div {% if put_form %}class="tabbable"{% endif %}>
|
||||
{% 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="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<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 {% 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="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button>
|
||||
{% endif %}
|
||||
{% if raw_data_patch_form %}
|
||||
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PATCH request on the {{ name }} resource{% endblocktrans %}">PATCH</button>
|
||||
<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 %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endwith %}
|
||||
{% 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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div><!-- /.content -->
|
||||
</div><!-- /.container -->
|
||||
{# div#push added for Ansible Tower. #}
|
||||
<div id="push"></div>
|
||||
</div><!-- ./wrapper -->
|
||||
{% endblock content %}
|
||||
</div><!-- /.content -->
|
||||
</div><!-- /.container -->
|
||||
</div><!-- ./wrapper -->
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static "rest_framework/js/jquery-1.11.3.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>
|
||||
{% 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();
|
||||
$('form').ajaxForm();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
{% if filter_form %}
|
||||
{{ filter_form }}
|
||||
{% endif %}
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user