diff --git a/awx/api/filters.py b/awx/api/filters.py
index a231c8af8d..03f848e6a8 100644
--- a/awx/api/filters.py
+++ b/awx/api/filters.py
@@ -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')
diff --git a/awx/api/generics.py b/awx/api/generics.py
index d2d7f0ff40..f69a4efd09 100644
--- a/awx/api/generics.py
+++ b/awx/api/generics.py
@@ -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'
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index c8d6028844..19fae02fd5 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -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
diff --git a/awx/api/views.py b/awx/api/views.py
index 2cc692e7e7..1328a6571c 100644
--- a/awx/api/views.py
+++ b/awx/api/views.py
@@ -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
diff --git a/awx/main/access.py b/awx/main/access.py
index aa34551576..5d790cf560 100644
--- a/awx/main/access.py
+++ b/awx/main/access.py
@@ -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):
diff --git a/awx/main/fields.py b/awx/main/fields.py
index 6a746518d9..19a1f1b78e 100644
--- a/awx/main/fields.py
+++ b/awx/main/fields.py
@@ -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):
diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py
index 8f8d56936e..14863bda6a 100644
--- a/awx/main/tests/base.py
+++ b/awx/main/tests/base.py
@@ -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.
diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py
index 688d4cf174..de16354d47 100644
--- a/awx/main/tests/functional/api/test_rbac_displays.py
+++ b/awx/main/tests/functional/api/test_rbac_displays.py
@@ -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']
diff --git a/awx/main/tests/functional/commands/test_commands.py b/awx/main/tests/functional/commands/test_commands.py
index 95cd291cee..0b3b582279 100644
--- a/awx/main/tests/functional/commands/test_commands.py
+++ b/awx/main/tests/functional/commands/test_commands.py
@@ -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),
]
diff --git a/awx/main/tests/functional/commands/test_inventory_import.py b/awx/main/tests/functional/commands/test_inventory_import.py
index 7c385ad1dd..f8f601a3c8 100644
--- a/awx/main/tests/functional/commands/test_inventory_import.py
+++ b/awx/main/tests/functional/commands/test_inventory_import.py
@@ -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')
diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py
index d712d66d26..81c9ab7767 100644
--- a/awx/main/tests/functional/conftest.py
+++ b/awx/main/tests/functional/conftest.py
@@ -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
diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py
index a789abd99f..a390b4c54f 100644
--- a/awx/main/tests/functional/test_rbac_api.py
+++ b/awx/main/tests/functional/test_rbac_api.py
@@ -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)
diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
index 55f2015b86..7ac067c93a 100644
--- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
@@ -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):
diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py
index edebdf5525..8014e9c4a6 100644
--- a/awx/main/tests/unit/api/test_views.py
+++ b/awx/main/tests/unit/api/test_views.py
@@ -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')
diff --git a/awx/main/tests/unit/commands/test_inventory_import.py b/awx/main/tests/unit/commands/test_inventory_import.py
index 21b7fe391d..8bbe219011 100644
--- a/awx/main/tests/unit/commands/test_inventory_import.py
+++ b/awx/main/tests/unit/commands/test_inventory_import.py
@@ -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
diff --git a/awx/main/tests/unit/models/test_rbac_unit.py b/awx/main/tests/unit/models/test_rbac_unit.py
deleted file mode 100644
index 24d9a657ba..0000000000
--- a/awx/main/tests/unit/models/test_rbac_unit.py
+++ /dev/null
@@ -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
- )
diff --git a/awx/main/tests/unit/settings/test_defaults.py b/awx/main/tests/unit/settings/test_defaults.py
index 52eeb56425..3cc0ed46f6 100644
--- a/awx/main/tests/unit/settings/test_defaults.py
+++ b/awx/main/tests/unit/settings/test_defaults.py
@@ -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)
diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py
index 61660afeb0..961a1d4039 100644
--- a/awx/main/tests/unit/test_access.py
+++ b/awx/main/tests/unit/test_access.py
@@ -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')
diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py
index 449f32bcf8..8512541cd3 100644
--- a/awx/main/utils/common.py
+++ b/awx/main/utils/common.py
@@ -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)
diff --git a/awx/main/utils/db.py b/awx/main/utils/db.py
index f9c625a7a1..bd2f5db69b 100644
--- a/awx/main/utils/db.py
+++ b/awx/main/utils/db.py
@@ -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)
+ )))
diff --git a/awx/templates/rest_framework/base.html b/awx/templates/rest_framework/base.html
index 7ae5cb8c1a..cbd761cf33 100644
--- a/awx/templates/rest_framework/base.html
+++ b/awx/templates/rest_framework/base.html
@@ -1,270 +1,295 @@
-
{# Copy of base.html from rest_framework with minor AWX change. #}
{% load staticfiles %}
-{% load rest_framework %}
{% load i18n %}
+{% load rest_framework %}
+
+
-
- {% block head %}
+
+ {% block head %}
- {% block meta %}
-
-
- {% endblock %}
-
- {% block title %}Django REST framework{% endblock %}
-
- {% block style %}
- {% block bootstrap_theme %}
-
-
+ {% block meta %}
+
+
{% endblock %}
-
-
- {% endblock %}
+ {% block title %}{% if name %}{{ name }} – {% endif %}Django REST framework{% endblock %}
- {% endblock %}
-
+ {% block style %}
+ {% block bootstrap_theme %}
+
+
+ {% endblock %}
-{% block body %}
-
-
-
- {% block navbar %}
-
-
-
- {% block branding %}
-
- Django REST framework {{ version }}
-
- {% endblock %}
-
-
- {% block userlinks %}
- {% if user.is_authenticated %}
- {% optional_logout request user %}
- {% else %}
- {% optional_login request %}
- {% endif %}
- {% endblock %}
-
-
-
- {% endblock %}
-
-
- {% block breadcrumbs %}
-
+
+
{% endblock %}
-
-
+ {% endblock %}
+
- {% if 'GET' in allowed_methods %}
-
- {% endif %}
-
- {% if options_form %}
-
- {% endif %}
-
- {% if delete_form %}
-
- {% endif %}
-
- {% if filter_form %}
-
-
- {% trans "Filters" %}
-
- {% endif %}
-
-
-
-
- {% block description %}
- {{ description }}
+
+ {% block navbar %}
+
+
+
+
+ {% block userlinks %}
+ {% if user.is_authenticated %}
+ {% optional_logout request user %}
+ {% else %}
+ {% optional_login request %}
+ {% endif %}
+ {% endblock %}
+
+
+
+ {% endblock %}
- {% if paginator %}
-
- {% get_pagination_html paginator %}
-
- {% endif %}
+
+ {% block breadcrumbs %}
+
+ {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
+ {% if forloop.last %}
+ {{ breadcrumb_name }}
+ {% else %}
+ {{ breadcrumb_name }}
+ {% endif %}
+ {% empty %}
+ {% block breadcrumbs_empty %} {% endblock breadcrumbs_empty %}
+ {% endfor %}
+
+ {% endblock %}
-
-
{{ request.method }} {{ request.get_full_path }}
-
+
+
+ {% block content %}
-
-
HTTP {{ response.status_code }} {{ response.status_text }} {% autoescape off %}
-{% for key, val in response_headers.items %}{{ key }}: {{ val|break_long_headers|urlize_quoted_links }}
-{% endfor %}
-{# Original line below had the side effect of also escaping content: #}
-{# {{ content|urlize_quoted_links }}{% endautoescape %} #}
-{# For Ansible Tower, disable automatic URL creation and move content outside of autoescape off block. #}
-{% endautoescape %}{{ content }}
+
+ {% if 'GET' in allowed_methods %}
+
+ {% endif %}
+
+ {% if options_form %}
+
+ {% endif %}
+
+ {% if delete_form %}
+
DELETE
+
+
+
+
+
+
+
Are you sure you want to delete this {{ name }}?
+
+
+
+
+ {% endif %}
+
+ {% if filter_form %}
+
+
+ {% trans "Filters" %}
+
+ {% endif %}
- {% if display_edit_forms %}
+
+
+
+ {% block description %}
+ {{ description }}
+ {% endblock %}
+
- {% if post_form or raw_data_post_form %}
-
- {% if post_form %}
-
- {% endif %}
+ {% if paginator %}
+
+ {% get_pagination_html paginator %}
+
+ {% endif %}
-
+
+
{{ request.method }} {{ request.get_full_path }}
+
+
+
+
HTTP {{ response.status_code }} {{ response.status_text }} {% autoescape off %}{% for key, val in response_headers|items %}
+{{ key }}: {{ val|break_long_headers|urlize_quoted_links }} {% endfor %}
+{# Original line below had the side effect of also escaping content: #}
+{# {{ content|urlize_quoted_links }}{% endautoescape %} #}
+{# For AWX, disable automatic URL creation and move content outside of autoescape off block. #}
+{% endautoescape %}{{ content }}
+
+
+
+ {% if display_edit_forms %}
+ {% if post_form or raw_data_post_form %}
+
{% if post_form %}
-
- {% with form=post_form %}
-
- {% endif %}
+ {% endif %}
- {% if put_form or raw_data_put_form or raw_data_patch_form %}
-
- {% if put_form %}
-
- {% endif %}
-
-
+ {% if put_form or raw_data_put_form or raw_data_patch_form %}
+
{% if put_form %}
-
+
{% endif %}
-
- {% with form=raw_data_put_or_patch_form %}
-
-
- {% include "rest_framework/raw_data_form.html" %}
-
-
+ {% endif %}
{% endif %}
- {% endif %}
-
-
- {# div#push added for Ansible Tower. #}
-
-
+ {% endblock content %}
+
+
+
- {% block script %}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {% endblock %}
+
+
{% endblock %}
-
- {% if filter_form %}
- {{ filter_form }}
- {% endif %}
-
-
-{% endblock %}