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
No known key found for this signature in database
GPG Key ID: B4F07BDC564D6301
21 changed files with 346 additions and 419 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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'),
('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)

View File

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

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

View File

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

View File

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