Fix migration issues, tests, and templates

This commit is contained in:
Wayne Witzel III
2017-11-09 17:24:54 -05:00
parent 6d6bbbb627
commit 96904968d8
21 changed files with 346 additions and 419 deletions

View File

@@ -22,6 +22,7 @@ from rest_framework.filters import BaseFilterBackend
# AWX # AWX
from awx.main.utils import get_type_for_model, to_python_boolean 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.credential import CredentialType
from awx.main.models.rbac import RoleAncestorEntry from awx.main.models.rbac import RoleAncestorEntry
@@ -70,7 +71,7 @@ class TypeFilterBackend(BaseFilterBackend):
types_map[ct_type] = ct.pk types_map[ct_type] = ct.pk
model = queryset.model model = queryset.model
model_type = get_type_for_model(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]) types_pks = set([v for k,v in types_map.items() if k in types])
queryset = queryset.filter(polymorphic_ctype_id__in=types_pks) queryset = queryset.filter(polymorphic_ctype_id__in=types_pks)
elif model_type in types: elif model_type in types:
@@ -119,7 +120,7 @@ class FieldLookupBackend(BaseFilterBackend):
'last_updated': 'last_job_run', 'last_updated': 'last_job_run',
}.get(name, name) }.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' name = 'polymorphic_ctype'
new_parts.append('polymorphic_ctype__model') new_parts.append('polymorphic_ctype__model')
else: else:
@@ -136,7 +137,7 @@ class FieldLookupBackend(BaseFilterBackend):
new_parts.pop() new_parts.pop()
new_parts.append(name_alt) new_parts.append(name_alt)
else: 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): if isinstance(field, ForeignObjectRel) and getattr(field.field, '__prevent_search__', False):
raise PermissionDenied(_('Filtering on %s is not allowed.' % name)) raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
elif getattr(field, '__prevent_search__', False): elif getattr(field, '__prevent_search__', False):
@@ -375,7 +376,7 @@ class OrderByBackend(BaseFilterBackend):
# given the limited number of views with multiple types, # given the limited number of views with multiple types,
# sorting on polymorphic_ctype.model is effectively the same. # sorting on polymorphic_ctype.model is effectively the same.
new_order_by = [] 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: for field in order_by:
if field == 'type': if field == 'type':
new_order_by.append('polymorphic_ctype__model') new_order_by.append('polymorphic_ctype__model')

View File

@@ -31,6 +31,7 @@ from rest_framework import views
from awx.api.filters import FieldLookupBackend from awx.api.filters import FieldLookupBackend
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils 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.serializers import ResourceAccessListElementSerializer
from awx.api.versioning import URLPathVersioning, get_request_version from awx.api.versioning import URLPathVersioning, get_request_version
from awx.api.metadata import SublistAttachDetatchMetadata from awx.api.metadata import SublistAttachDetatchMetadata
@@ -321,8 +322,7 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
return page return page
def get_description_context(self): def get_description_context(self):
opts = self.model._meta if 'username' in get_all_field_names(self.model):
if 'username' in opts.get_all_field_names():
order_field = 'username' order_field = 'username'
else: else:
order_field = 'name' order_field = 'name'

View File

@@ -477,7 +477,7 @@ class BaseSerializer(serializers.ModelSerializer):
return super(BaseSerializer, self).run_validation(data) return super(BaseSerializer, self).run_validation(data)
except ValidationError as exc: except ValidationError as exc:
# Avoid bug? in DRF if exc.detail happens to be a list instead of a dict. # 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): def get_validation_exclusions(self, obj=None):
# Borrowed from DRF 2.x - return model fields that should be excluded # Borrowed from DRF 2.x - return model fields that should be excluded

View File

@@ -27,7 +27,6 @@ from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -53,7 +52,9 @@ import qsstats
import ansiconv import ansiconv
# Python Social Auth # Python Social Auth
from social.backends.utils import load_backends from social_core.backends.utils import load_backends
from wsgiref.util import FileWrapper
# AWX # AWX
from awx.main.tasks import send_notifications from awx.main.tasks import send_notifications

View File

@@ -54,12 +54,12 @@ def get_object_from_data(field, Model, data, obj=None):
# Calling method needs to deal with non-existence of key # Calling method needs to deal with non-existence of key
raise ParseError(_("Required related field %s for permission check." % field)) raise ParseError(_("Required related field %s for permission check." % field))
if isinstance(raw_value, Model): try:
return raw_value if isinstance(raw_value, Model):
elif raw_value is None: return raw_value
return None elif raw_value is None:
else: return None
try: else:
new_pk = int(raw_value) new_pk = int(raw_value)
# Avoid database query by comparing pk to model for similarity # Avoid database query by comparing pk to model for similarity
if obj and new_pk == getattr(obj, '%s_id' % field, None): 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: else:
# Get the new resource from the database # Get the new resource from the database
return get_object_or_400(Model, pk=new_pk) return get_object_or_400(Model, pk=new_pk)
except (TypeError, ValueError): except (TypeError, ValueError):
raise ParseError(_("Bad data found in related field %s." % field)) raise ParseError(_("Bad data found in related field %s." % field))
class StateConflict(ValidationError): class StateConflict(ValidationError):

View File

@@ -18,12 +18,12 @@ from django.db.models.signals import (
) )
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.db import models from django.db import models
from django.db.models.fields.related import ( from django.db.models.fields.related import add_lazy_relation
add_lazy_relation, from django.db.models.fields.related_descriptors import (
SingleRelatedObjectDescriptor, ReverseOneToOneDescriptor,
ReverseSingleRelatedObjectDescriptor, ForwardManyToOneDescriptor,
ManyRelatedObjectsDescriptor, ManyToManyDescriptor,
ReverseManyRelatedObjectsDescriptor, ReverseManyToOneDescriptor,
) )
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _ 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 # 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.""" """Descriptor for access to the object from its related class."""
def __get__(self, instance, instance_type=None): 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)))) raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj))))
ret.append(obj.id) ret.append(obj.id)
else: else:
if type(obj) is ManyRelatedObjectsDescriptor: if type(obj) is ManyToManyDescriptor:
for o in obj.all(): for o in obj.all():
ret += resolve_role_field(o, field_components[1]) ret += resolve_role_field(o, field_components[1])
else: else:
@@ -179,7 +179,7 @@ def is_implicit_parent(parent_role, child_role):
return False return False
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor): class ImplicitRoleDescriptor(ForwardManyToOneDescriptor):
pass pass
@@ -230,18 +230,18 @@ class ImplicitRoleField(models.ForeignKey):
field_name, sep, field_attr = field_name.partition('.') field_name, sep, field_attr = field_name.partition('.')
field = getattr(cls, field_name) field = getattr(cls, field_name)
if type(field) is ReverseManyRelatedObjectsDescriptor or \ if type(field) is ReverseManyToOneDescriptor or \
type(field) is ManyRelatedObjectsDescriptor: type(field) is ManyToManyDescriptor:
if '.' in field_attr: if '.' in field_attr:
raise Exception('Referencing deep roles through ManyToMany fields is unsupported.') raise Exception('Referencing deep roles through ManyToMany fields is unsupported.')
if type(field) is ReverseManyRelatedObjectsDescriptor: if type(field) is ReverseManyToOneDescriptor:
sender = field.through sender = field.through
else: else:
sender = field.related.through 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) m2m_changed.connect(self.m2m_update(field_attr, reverse), sender, weak=False)
def m2m_update(self, field_attr, _reverse): def m2m_update(self, field_attr, _reverse):

View File

@@ -132,7 +132,7 @@ class BaseTestMixin(MockCommonlySlowTestMixin):
# Set flag so that task chain works with unit tests. # Set flag so that task chain works with unit tests.
settings.CELERY_UNIT_TEST = True settings.CELERY_UNIT_TEST = True
settings.SYSTEM_UUID='00000000-0000-0000-0000-000000000000' 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' settings.CALLBACK_QUEUE = 'callback_tasks_unit'
# Disable socket notifications for unit tests. # Disable socket notifications for unit tests.

View File

@@ -92,24 +92,21 @@ class TestJobTemplateCopyEdit:
credential=None, ask_credential_on_launch=True, credential=None, ask_credential_on_launch=True,
name='deploy-job-template' name='deploy-job-template'
) )
serializer = JobTemplateSerializer(jt_res) serializer = JobTemplateSerializer(jt_res, context=self.fake_context(admin_user))
serializer.context = self.fake_context(admin_user)
response = serializer.to_representation(jt_res) response = serializer.to_representation(jt_res)
assert not response['summary_fields']['user_capabilities']['copy'] assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] assert response['summary_fields']['user_capabilities']['edit']
def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user): def test_sys_admin_copy_edit(self, jt_copy_edit, admin_user):
"Absent a validation error, system admins can do everything" "Absent a validation error, system admins can do everything"
serializer = JobTemplateSerializer(jt_copy_edit) serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(admin_user))
serializer.context = self.fake_context(admin_user)
response = serializer.to_representation(jt_copy_edit) response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy'] assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] assert response['summary_fields']['user_capabilities']['edit']
def test_org_admin_copy_edit(self, jt_copy_edit, org_admin): 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" "Organization admins SHOULD be able to copy a JT firmly in their org"
serializer = JobTemplateSerializer(jt_copy_edit) serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
serializer.context = self.fake_context(org_admin)
response = serializer.to_representation(jt_copy_edit) response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy'] assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] assert response['summary_fields']['user_capabilities']['edit']
@@ -125,8 +122,7 @@ class TestJobTemplateCopyEdit:
jt_copy_edit.credential = machine_credential jt_copy_edit.credential = machine_credential
jt_copy_edit.save() jt_copy_edit.save()
serializer = JobTemplateSerializer(jt_copy_edit) serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(org_admin))
serializer.context = self.fake_context(org_admin)
response = serializer.to_representation(jt_copy_edit) response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['user_capabilities']['copy'] assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] assert response['summary_fields']['user_capabilities']['edit']
@@ -140,8 +136,7 @@ class TestJobTemplateCopyEdit:
jt_copy_edit.admin_role.members.add(rando) jt_copy_edit.admin_role.members.add(rando)
jt_copy_edit.save() jt_copy_edit.save()
serializer = JobTemplateSerializer(jt_copy_edit) serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
serializer.context = self.fake_context(rando)
response = serializer.to_representation(jt_copy_edit) response = serializer.to_representation(jt_copy_edit)
assert not response['summary_fields']['user_capabilities']['copy'] assert not response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] 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.admin_role.members.add(rando)
jt_copy_edit.project.save() jt_copy_edit.project.save()
serializer = JobTemplateSerializer(jt_copy_edit) serializer = JobTemplateSerializer(jt_copy_edit, context=self.fake_context(rando))
serializer.context = self.fake_context(rando)
response = serializer.to_representation(jt_copy_edit) response = serializer.to_representation(jt_copy_edit)
assert response['summary_fields']['user_capabilities']['copy'] assert response['summary_fields']['user_capabilities']['copy']
assert response['summary_fields']['user_capabilities']['edit'] assert response['summary_fields']['user_capabilities']['edit']

View File

@@ -39,8 +39,8 @@ def run_command(name, *args, **options):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"username,password,expected,changed", [ "username,password,expected,changed", [
('admin', 'dingleberry', 'Password updated\n', True), ('admin', 'dingleberry', 'Password updated', True),
('admin', 'admin', 'Password not updated\n', False), ('admin', 'admin', 'Password not updated', False),
(None, 'foo', 'username required', False), (None, 'foo', 'username required', False),
('admin', None, 'password required', False), ('admin', None, 'password required', False),
] ]

View File

@@ -92,7 +92,7 @@ class TestInvalidOptionsFunctional:
cmd = inventory_import.Command() cmd = inventory_import.Command()
with mock.patch('django.db.transaction.rollback'): with mock.patch('django.db.transaction.rollback'):
with pytest.raises(IOError) as err: with pytest.raises(IOError) as err:
cmd.handle_noargs( cmd.handle(
inventory_id=inventory.id, inventory_id=inventory.id,
source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid') source='/tmp/pytest-of-root/pytest-7/inv_files0-invalid')
assert 'Source does not exist' in err.value.message assert 'Source does not exist' in err.value.message
@@ -100,14 +100,14 @@ class TestInvalidOptionsFunctional:
def test_invalid_inventory_id(self): def test_invalid_inventory_id(self):
cmd = inventory_import.Command() cmd = inventory_import.Command()
with pytest.raises(CommandError) as err: 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 'id = 42' in err.value.message
assert 'cannot be found' in err.value.message assert 'cannot be found' in err.value.message
def test_invalid_inventory_name(self): def test_invalid_inventory_name(self):
cmd = inventory_import.Command() cmd = inventory_import.Command()
with pytest.raises(CommandError) as err: 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 'name = fooservers' in err.value.message
assert 'cannot be found' 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)) @mock.patch.object(inventory_import.AnsibleInventoryLoader, 'load', mock.MagicMock(return_value=TEST_MEM_OBJECTS))
def test_inventory_single_ini_import(self, inventory, capsys): def test_inventory_single_ini_import(self, inventory, capsys):
cmd = inventory_import.Command() cmd = inventory_import.Command()
r = cmd.handle_noargs( r = cmd.handle(
inventory_id=inventory.pk, source=__file__, inventory_id=inventory.pk, source=__file__,
method='backport') method='backport')
out, err = capsys.readouterr() out, err = capsys.readouterr()
@@ -192,7 +192,7 @@ class TestINIImports:
) )
def test_hostvars_are_saved(self, inventory): def test_hostvars_are_saved(self, inventory):
cmd = inventory_import.Command() 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 assert inventory.hosts.count() == 1
h = inventory.hosts.all()[0] h = inventory.hosts.all()[0]
assert h.name == 'foo' assert h.name == 'foo'
@@ -219,4 +219,4 @@ class TestINIImports:
) )
def test_recursive_group_error(self, inventory): def test_recursive_group_error(self, inventory):
cmd = inventory_import.Command() cmd = inventory_import.Command()
cmd.handle_noargs(inventory_id=inventory.pk, source='doesnt matter') cmd.handle(inventory_id=inventory.pk, source='doesnt matter')

View File

@@ -64,7 +64,7 @@ def celery_memory_broker():
Allows django signal code to execute without the need for redis Allows django signal code to execute without the need for redis
''' '''
settings.BROKER_URL='memory://localhost/' settings.CELERY_BROKER_URL='memory://localhost/'
@pytest.fixture @pytest.fixture

View File

@@ -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' 'Users can see all roles they have access to, but not all roles'
this_user = user('user-test_get_roles_list_user') this_user = user('user-test_get_roles_list_user')
organization.member_role.members.add(this_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) organization.member_role.children.add(custom_role)
url = reverse('api:role_list') 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.member_role.members.add(alice)
organization.admin_role.members.add(bob) organization.admin_role.members.add(bob)
organization.member_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) organization.member_role.children.add(custom_role)
team.member_role.members.add(bob) team.member_role.members.add(bob)

View File

@@ -125,11 +125,15 @@ class TestWorkflowJobTemplateNodeSerializerCharPrompts():
serializer = WorkflowJobTemplateNodeSerializer() serializer = WorkflowJobTemplateNodeSerializer()
node = WorkflowJobTemplateNode(pk=1) node = WorkflowJobTemplateNode(pk=1)
node.char_prompts = {'limit': 'webservers'} node.char_prompts = {'limit': 'webservers'}
serializer.instance = node
view = FakeView(node) view = FakeView(node)
view.request = FakeRequest() view.request = FakeRequest()
view.request.method = "PATCH" view.request.method = "PATCH"
serializer.context = {'view': view}
serializer = WorkflowJobTemplateNodeSerializer()
serializer = WorkflowJobTemplateNodeSerializer(context={'view':view})
serializer.instance = node
return serializer return serializer
def test_change_single_field(self, WFJT_serializer): def test_change_single_field(self, WFJT_serializer):

View File

@@ -9,7 +9,6 @@ from awx.api.views import (
JobTemplateLabelList, JobTemplateLabelList,
JobTemplateSurveySpec, JobTemplateSurveySpec,
InventoryInventorySourcesUpdate, InventoryInventorySourcesUpdate,
InventoryHostsList,
HostInsights, HostInsights,
) )
@@ -17,8 +16,6 @@ from awx.main.models import (
Host, Host,
) )
from awx.main.managers import HostManager
@pytest.fixture @pytest.fixture
def mock_response_new(mocker): 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.data['error'] == 'The Insights Credential for "inventory_name_here" was not found.'
assert resp.status_code == 404 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')

View File

@@ -19,7 +19,7 @@ class TestInvalidOptions:
def test_invalid_options_no_options_specified(self): def test_invalid_options_no_options_specified(self):
cmd = Command() cmd = Command()
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs() cmd.handle()
assert 'inventory-id' in err.value.message assert 'inventory-id' in err.value.message
assert 'required' 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 # You can not specify both name and if of the inventory
cmd = Command() cmd = Command()
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs( cmd.handle(
inventory_id=42, inventory_name='my-inventory' inventory_id=42, inventory_name='my-inventory'
) )
assert 'inventory-id' in err.value.message 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 # You can't overwrite and keep_vars at the same time, that wouldn't make sense
cmd = Command() cmd = Command()
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs( cmd.handle(
inventory_id=42, overwrite=True, keep_vars=True inventory_id=42, overwrite=True, keep_vars=True
) )
assert 'overwrite-vars' in err.value.message assert 'overwrite-vars' in err.value.message
@@ -47,13 +47,13 @@ class TestInvalidOptions:
# Need a source to import # Need a source to import
cmd = Command() cmd = Command()
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs( cmd.handle(
inventory_id=42, overwrite=True, keep_vars=True inventory_id=42, overwrite=True, keep_vars=True
) )
assert 'overwrite-vars' in err.value.message assert 'overwrite-vars' in err.value.message
assert 'exclusive' in err.value.message assert 'exclusive' in err.value.message
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs( cmd.handle(
inventory_id=42, overwrite_vars=True, keep_vars=True inventory_id=42, overwrite_vars=True, keep_vars=True
) )
assert 'overwrite-vars' in err.value.message assert 'overwrite-vars' in err.value.message
@@ -62,7 +62,7 @@ class TestInvalidOptions:
def test_invalid_options_missing_source(self): def test_invalid_options_missing_source(self):
cmd = Command() cmd = Command()
with pytest.raises(CommandError) as err: with pytest.raises(CommandError) as err:
cmd.handle_noargs(inventory_id=42) cmd.handle(inventory_id=42)
assert '--source' in err.value.message assert '--source' in err.value.message
assert 'required' in err.value.message assert 'required' in err.value.message

View File

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

View File

@@ -8,11 +8,11 @@ from datetime import timedelta
('admin_checks', 'awx.main.tasks.run_administrative_checks'), ('admin_checks', 'awx.main.tasks.run_administrative_checks'),
('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'), ('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'),
]) ])
def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path): def test_CELERY_BEAT_SCHEDULE(mocker, job_name, function_path):
assert job_name in settings.CELERYBEAT_SCHEDULE assert job_name in settings.CELERY_BEAT_SCHEDULE
assert 'schedule' in settings.CELERYBEAT_SCHEDULE[job_name] assert 'schedule' in settings.CELERY_BEAT_SCHEDULE[job_name]
assert type(settings.CELERYBEAT_SCHEDULE[job_name]['schedule']) is timedelta assert type(settings.CELERY_BEAT_SCHEDULE[job_name]['schedule']) is timedelta
assert settings.CELERYBEAT_SCHEDULE[job_name]['task'] == function_path assert settings.CELERY_BEAT_SCHEDULE[job_name]['task'] == function_path
# Ensures that the function exists # Ensures that the function exists
mocker.patch(function_path) mocker.patch(function_path)

View File

@@ -16,6 +16,7 @@ from awx.main.access import (
from awx.conf.license import LicenseForbids from awx.conf.license import LicenseForbids
from awx.main.models import ( from awx.main.models import (
Credential, Credential,
CredentialType,
Inventory, Inventory,
Project, Project,
Role, Role,
@@ -57,7 +58,7 @@ class TestRelatedFieldAccess:
def test_new_with_bad_data(self, access, mocker): def test_new_with_bad_data(self, access, mocker):
data = {'related': 3.1415} data = {'related': 3.1415}
with pytest.raises(ParseError): 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): def test_new_mandatory_fail(self, access, mocker):
access.user.is_superuser = False access.user.is_superuser = False
@@ -118,10 +119,18 @@ class TestRelatedFieldAccess:
@pytest.fixture @pytest.fixture
def job_template_with_ids(job_template_factory): def job_template_with_ids(job_template_factory):
# Create non-persisted objects with IDs to send to 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') ssh_type = CredentialType(kind='ssh')
net_cred = Credential(id=2, pk=2, name='testnetcred', kind='net') credential = Credential(id=1, pk=1, name='testcred', credential_type=ssh_type)
cloud_cred = Credential(id=3, pk=3, name='testcloudcred', kind='aws')
vault_cred = Credential(id=4, pk=4, name='testnetcred', kind='vault') 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') inv = Inventory(id=11, pk=11, name='testinv')
proj = Project(id=14, pk=14, name='testproj') proj = Project(id=14, pk=14, name='testproj')

View File

@@ -450,7 +450,7 @@ def copy_model_by_class(obj1, Class2, fields, kwargs):
elif not isinstance(Class2._meta.get_field(field_name), (ForeignObjectRel, ManyToManyField)): elif not isinstance(Class2._meta.get_field(field_name), (ForeignObjectRel, ManyToManyField)):
create_kwargs[field_name] = kwargs[field_name] create_kwargs[field_name] = kwargs[field_name]
elif hasattr(obj1, 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): if not isinstance(field_obj, ManyToManyField):
create_kwargs[field_name] = getattr(obj1, field_name) 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: for field_name in fields:
if hasattr(obj1, field_name): 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): if isinstance(field_obj, ManyToManyField):
# Many to Many can be specified as field_name # Many to Many can be specified as field_name
src_field_value = getattr(obj1, field_name) src_field_value = getattr(obj1, field_name)

View File

@@ -6,6 +6,7 @@ from django.db.migrations.loader import MigrationLoader
from django.db import connection from django.db import connection
# Python # Python
from itertools import chain
import re import re
@@ -20,3 +21,15 @@ def get_tower_migration_version():
if migration_version > v: if migration_version > v:
v = migration_version v = migration_version
return v 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)
)))

View File

@@ -1,270 +1,295 @@
<!DOCTYPE html>
{# Copy of base.html from rest_framework with minor AWX change. #} {# Copy of base.html from rest_framework with minor AWX change. #}
{% load staticfiles %} {% load staticfiles %}
{% load rest_framework %}
{% load i18n %} {% load i18n %}
{% load rest_framework %}
<!DOCTYPE html>
<html> <html>
<head> <head>
{% block head %} {% block head %}
{% block meta %} {% block meta %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="NONE,NOARCHIVE" /> <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" %}"/>
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/> <title>{% block title %}{% if name %}{{ name }} {% endif %}Django REST framework{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
{% endblock %}
{% endblock %} {% block style %}
</head> {% 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 %} <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>
<body class="{% block bodyclass %}{% endblock %}"> <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>
<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>
{% endblock %} {% endblock %}
<!-- Content --> {% endblock %}
<div id="content"> </head>
{% if 'GET' in allowed_methods %} {% block body %}
<form id="get-form" class="pull-right"> <body class="{% block bodyclass %}{% endblock %}">
<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>
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="{% trans 'Specify a format for the GET request' %}"> <div class="wrapper">
<span class="caret"></span> {% block navbar %}
</button> <div class="navbar navbar-static-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"
<ul class="dropdown-menu"> role="navigation" aria-label="{% trans "navbar" %}">
{% for format in available_formats %} <div class="container">
<li> <span>
<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> {% block branding %}
</li> <a class='navbar-brand' rel="nofollow" href='http://www.django-rest-framework.org'>
{% endfor %} Django REST framework
</ul> </a>
</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 }}
{% endblock %} {% 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 %} <div class="container">
<nav style="float: right"> {% block breadcrumbs %}
{% get_pagination_html paginator %} <ul class="breadcrumb">
</nav> {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
{% endif %} {% 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 %}
<div class="request-info" style="clear: both" > <!-- Content -->
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre> <div id="content" role="main" aria-label="{% trans "content" %}">
</div> {% block content %}
<div class="response-info"> <div class="region" aria-label="{% trans "request form" %}">
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %} {% if 'GET' in allowed_methods %}
{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span> <form id="get-form" class="pull-right">
{% endfor %} <fieldset>
{# Original line below had the side effect of also escaping content: #} {% if api_settings.URL_FORMAT_OVERRIDE %}
{# </span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} #} <div class="btn-group format-selection">
{# For Ansible Tower, disable automatic URL creation and move content outside of autoescape off block. #} <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>
{% endautoescape %}</span>{{ content }}</pre>
<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> </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>
{% 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 %} {% if paginator %}
<div {% if post_form %}class="tabbable"{% endif %}> <nav style="float: right">
{% if post_form %} {% get_pagination_html paginator %}
<ul class="nav nav-tabs form-switcher"> </nav>
<li> {% endif %}
<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"> <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 %} {% if post_form %}
<div class="tab-pane" id="post-object-form"> <ul class="nav nav-tabs form-switcher">
{% with form=post_form %} <li>
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate> <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> <fieldset>
{% csrf_token %} {% include "rest_framework/raw_data_form.html" %}
{{ post_form }}
<div class="form-actions"> <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> </div>
</fieldset> </fieldset>
</form> </form>
{% endwith %} {% endwith %}
</div> </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> </div>
</div> {% endif %}
{% endif %}
{% if put_form or raw_data_put_form or raw_data_patch_form %} {% if put_form or raw_data_put_form or raw_data_patch_form %}
<div {% if put_form %}class="tabbable"{% endif %}> <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 %} {% if put_form %}
<div class="tab-pane" id="put-object-form"> <ul class="nav nav-tabs form-switcher">
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate> <li>
<fieldset> <a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a>
{{ put_form }} </li>
<div class="form-actions"> <li>
<button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button> <a name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a>
</div> </li>
</fieldset> </ul>
</form>
</div>
{% endif %} {% endif %}
<div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form"> <div class="well tab-content">
{% with form=raw_data_put_or_patch_form %} {% if put_form %}
<form action="{{ request.get_full_path }}" data-method="PUT" class="form-horizontal"> <div class="tab-pane" id="put-object-form">
<fieldset> <form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
{% include "rest_framework/raw_data_form.html" %} <fieldset>
<div class="form-actions"> {{ put_form }}
{% if raw_data_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> <button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
{% endif %} </div>
{% if raw_data_patch_form %} </fieldset>
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PATCH request on the {{ name }} resource{% endblocktrans %}">PATCH</button> </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 %} {% endif %}
</div> {% if raw_data_patch_form %}
</fieldset> <button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
</form> {% endif %}
{% endwith %} </div>
</fieldset>
</form>
{% endwith %}
</div>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %} {% endif %}
{% endif %} {% endblock content %}
</div><!-- /.content --> </div><!-- /.content -->
</div><!-- /.container --> </div><!-- /.container -->
{# div#push added for Ansible Tower. #} </div><!-- ./wrapper -->
<div id="push"></div>
</div><!-- ./wrapper -->
{% block script %} {% if filter_form %}
<script src="{% static "rest_framework/js/jquery-1.11.3.min.js" %}"></script> {{ filter_form }}
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script> {% endif %}
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script> {% block script %}
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script> <script>
<script src="{% static "rest_framework/js/default.js" %}"></script> window.drf = {
<script> 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() { $(document).ready(function() {
$('form').ajaxForm(); $('form').ajaxForm();
}); });
</script> </script>
{% endblock %}
</body>
{% endblock %} {% endblock %}
{% if filter_form %}
{{ filter_form }}
{% endif %}
</body>
{% endblock %}
</html> </html>