# Buggy code
def copy_model_obj(
    old_parent, new_parent, model, obj, creater, copy_name="", create_kwargs=None
):
    fields_to_preserve = set(getattr(model, "FIELDS_TO_PRESERVE_AT_COPY", []))
    fields_to_discard = set(getattr(model, "FIELDS_TO_DISCARD_AT_COPY", []))
    m2m_to_preserve = {}
    o2m_to_preserve = {}
    create_kwargs = create_kwargs or {}
    for field_name in fields_to_discard:
        create_kwargs.pop(field_name, None)
    for field in model._meta.get_fields():
        try:
            field_val = getattr(obj, field.name)
        except AttributeError:
            continue
        # Adjust copy blacklist fields here.
        if (
            field.name in fields_to_discard
            or field.name
            in [
                "id",
                "pk",
                "polymorphic_ctype",
                "unifiedjobtemplate_ptr",
                "created_by",
                "modified_by",
            ]
            or field.name.endswith("_role")
        ):
            create_kwargs.pop(field.name, None)
            continue
        if field.one_to_many:
            if field.name in fields_to_preserve:
                o2m_to_preserve[field.name] = field_val
        elif field.many_to_many:
            if field.name in fields_to_preserve and not old_parent:
                m2m_to_preserve[field.name] = field_val
        elif field.many_to_one and not field_val:
            create_kwargs.pop(field.name, None)
        elif field.many_to_one and field_val == old_parent:
            create_kwargs[field.name] = new_parent
        elif field.name == "name" and not old_parent:
            create_kwargs[field.name] = copy_name or field_val + " copy"
        elif field.name in fields_to_preserve:
            create_kwargs[field.name] = CopyAPIView._decrypt_model_field_if_needed(
                obj, field.name, field_val
            )
    new_obj = model.objects.create(**create_kwargs)
    # Need to save separatedly because Djang-crum get_current_user would
    # not work properly in non-request-response-cycle context.
    new_obj.created_by = creater
    new_obj.save()
    for m2m in m2m_to_preserve:
        for related_obj in m2m_to_preserve[m2m].all():
            getattr(new_obj, m2m).add(related_obj)
    if not old_parent:
        sub_objects = []
        for o2m in o2m_to_preserve:
            for sub_obj in o2m_to_preserve[o2m].all():
                sub_model = type(sub_obj)
                sub_objects.append(
                    (sub_model.__module__, sub_model.__name__, sub_obj.pk)
                )
        return new_obj, sub_objects
    ret = {obj: new_obj}
    for o2m in o2m_to_preserve:
        for sub_obj in o2m_to_preserve[o2m].all():
            ret.update(
                CopyAPIView.copy_model_obj(
                    obj, new_obj, type(sub_obj), sub_obj, creater
                )
            )
    return ret


# Traceback (KeyError)
2018-03-23 15:38:36,900 ERROR    django.request Internal Server Error: /api/v2/inventory_sources/25/
Traceback (most recent call last):
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
response = get_response(request)
File "/usr/lib/python2.7/site-packages/awx/wsgi.py", line 65, in _legacy_get_response
return super(AWXWSGIHandler, self)._legacy_get_response(request)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
response = self._get_response(request)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner
return func(*args, **kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/usr/lib/python2.7/site-packages/awx/api/generics.py", line 284, in dispatch
return super(APIView, self).dispatch(request, *args, **kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/views.py", line 494, in dispatch
response = self.handle_exception(exc)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/views.py", line 454, in handle_exception
self.raise_uncaught_exception(exc)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/views.py", line 491, in dispatch
response = handler(request, *args, **kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/generics.py", line 257, in put
return self.update(request, *args, **kwargs)
File "/usr/lib/python2.7/site-packages/awx/api/generics.py", line 741, in update
return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/mixins.py", line 67, in update
instance = self.get_object()
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/generics.py", line 101, in get_object
self.check_object_permissions(self.request, obj)
File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/views.py", line 344, in check_object_permissions
if not permission.has_object_permission(request, self, obj):
File "/usr/lib/python2.7/site-packages/awx/api/permissions.py", line 138, in has_object_permission
return self.has_permission(request, view, obj)
File "/usr/lib/python2.7/site-packages/awx/api/permissions.py", line 129, in has_permission
response = self.check_permissions(request, view, obj)
File "/usr/lib/python2.7/site-packages/awx/api/permissions.py", line 118, in check_permissions
result = check_method and check_method(request, view, obj)
File "/usr/lib/python2.7/site-packages/awx/api/permissions.py", line 82, in check_put_permissions
request.data)
File "/usr/lib/python2.7/site-packages/awx/main/access.py", line 117, in check_user_access
result = access_method(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/awx/main/access.py", line 157, in wrapper
return func(self, *args, **kwargs)
File "/usr/lib/python2.7/site-packages/awx/main/access.py", line 931, in can_change
self.check_related('credential', Credential, data, obj=obj, role_field='use_role') and
File "/usr/lib/python2.7/site-packages/awx/main/access.py", line 290, in check_related
if current and (changed or mandatory) and (not user_has_resource_access(current)):
File "/usr/lib/python2.7/site-packages/awx/main/access.py", line 283, in user_has_resource_access
access_method_type = {'admin_role': 'change', 'execute_role': 'start'}[role_field]
KeyError: 'use_role'

# Fixed code
def copy_model_obj(
    old_parent, new_parent, model, obj, creater, copy_name="", create_kwargs=None
):
    fields_to_preserve = set(getattr(model, "FIELDS_TO_PRESERVE_AT_COPY", []))
    fields_to_discard = set(getattr(model, "FIELDS_TO_DISCARD_AT_COPY", []))
    m2m_to_preserve = {}
    o2m_to_preserve = {}
    create_kwargs = create_kwargs or {}
    for field_name in fields_to_discard:
        create_kwargs.pop(field_name, None)
    for field in model._meta.get_fields():
        try:
            field_val = getattr(obj, field.name)
        except AttributeError:
            continue
        # Adjust copy blacklist fields here.
        if (
            field.name in fields_to_discard
            or field.name
            in [
                "id",
                "pk",
                "polymorphic_ctype",
                "unifiedjobtemplate_ptr",
                "created_by",
                "modified_by",
            ]
            or field.name.endswith("_role")
        ):
            create_kwargs.pop(field.name, None)
            continue
        if field.one_to_many:
            if field.name in fields_to_preserve:
                o2m_to_preserve[field.name] = field_val
        elif field.many_to_many:
            if field.name in fields_to_preserve and not old_parent:
                m2m_to_preserve[field.name] = field_val
        elif field.many_to_one and not field_val:
            create_kwargs.pop(field.name, None)
        elif field.many_to_one and field_val == old_parent:
            create_kwargs[field.name] = new_parent
        elif field.name == "name" and not old_parent:
            create_kwargs[field.name] = copy_name or field_val + " copy"
        elif field.name in fields_to_preserve:
            create_kwargs[field.name] = CopyAPIView._decrypt_model_field_if_needed(
                obj, field.name, field_val
            )
    new_obj = model.objects.create(**create_kwargs)
    logger.debug(
        six.text_type("Deep copy: Created new object {}({})").format(new_obj, model)
    )
    # Need to save separatedly because Djang-crum get_current_user would
    # not work properly in non-request-response-cycle context.
    new_obj.created_by = creater
    new_obj.save()
    for m2m in m2m_to_preserve:
        for related_obj in m2m_to_preserve[m2m].all():
            getattr(new_obj, m2m).add(related_obj)
    if not old_parent:
        sub_objects = []
        for o2m in o2m_to_preserve:
            for sub_obj in o2m_to_preserve[o2m].all():
                sub_model = type(sub_obj)
                sub_objects.append(
                    (sub_model.__module__, sub_model.__name__, sub_obj.pk)
                )
        return new_obj, sub_objects
    ret = {obj: new_obj}
    for o2m in o2m_to_preserve:
        for sub_obj in o2m_to_preserve[o2m].all():
            ret.update(
                CopyAPIView.copy_model_obj(
                    obj, new_obj, type(sub_obj), sub_obj, creater
                )
            )
    return ret