We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Advanced search for type returns 400
<!-- Issues are for **concrete, actionable bugs and feature requests** only - if you're just asking for debugging help or technical support, please use:

- http://webchat.freenode.net/?channels=ansible-awx
- https://groups.google.com/forum/#!forum/awx-project

We have to limit this because of limited volunteer time to respond to issues! -->

##### ISSUE TYPE
 - Bug Report

##### SUMMARY

Trying to do an advanced search by `type` the results are always `ExecutionEnvironment has no field named 'type'`:

![image](https://user-images.githubusercontent.com/12464858/109544586-83d70a00-7aa6-11eb-994c-dedcbce8801a.png)




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awx/api/metadata.py`
Content:
```
1 # Copyright (c) 2016 Ansible, Inc.
2 # All Rights Reserved.
3 
4 from collections import OrderedDict
5 from uuid import UUID
6 
7 # Django
8 from django.core.exceptions import PermissionDenied
9 from django.db.models.fields import PositiveIntegerField, BooleanField
10 from django.db.models.fields.related import ForeignKey
11 from django.http import Http404
12 from django.utils.encoding import force_text, smart_text
13 from django.utils.translation import ugettext_lazy as _
14 
15 # Django REST Framework
16 from rest_framework import exceptions
17 from rest_framework import metadata
18 from rest_framework import serializers
19 from rest_framework.relations import RelatedField, ManyRelatedField
20 from rest_framework.fields import JSONField as DRFJSONField
21 from rest_framework.request import clone_request
22 
23 # AWX
24 from awx.api.fields import ChoiceNullField
25 from awx.main.fields import JSONField, ImplicitRoleField
26 from awx.main.models import NotificationTemplate
27 from awx.main.tasks import AWXReceptorJob
28 
29 
30 class Metadata(metadata.SimpleMetadata):
31     def get_field_info(self, field):
32         field_info = OrderedDict()
33         field_info['type'] = self.label_lookup[field]
34         field_info['required'] = getattr(field, 'required', False)
35 
36         text_attrs = [
37             'read_only',
38             'label',
39             'help_text',
40             'min_length',
41             'max_length',
42             'min_value',
43             'max_value',
44             'category',
45             'category_slug',
46             'defined_in_file',
47             'unit',
48         ]
49 
50         for attr in text_attrs:
51             value = getattr(field, attr, None)
52             if value is not None and value != '':
53                 field_info[attr] = force_text(value, strings_only=True)
54 
55         placeholder = getattr(field, 'placeholder', serializers.empty)
56         if placeholder is not serializers.empty:
57             field_info['placeholder'] = placeholder
58 
59         serializer = getattr(field, 'parent', None)
60         if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):
61             # Update help text for common fields.
62             field_help_text = {
63                 'id': _('Database ID for this {}.'),
64                 'name': _('Name of this {}.'),
65                 'description': _('Optional description of this {}.'),
66                 'type': _('Data type for this {}.'),
67                 'url': _('URL for this {}.'),
68                 'related': _('Data structure with URLs of related resources.'),
69                 'summary_fields': _(
70                     'Data structure with name/description for related resources.  ' 'The output for some objects may be limited for performance reasons.'
71                 ),
72                 'created': _('Timestamp when this {} was created.'),
73                 'modified': _('Timestamp when this {} was last modified.'),
74             }
75             if field.field_name in field_help_text:
76                 opts = serializer.Meta.model._meta.concrete_model._meta
77                 verbose_name = smart_text(opts.verbose_name)
78                 field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
79 
80             if field.field_name == 'type':
81                 field_info['filterable'] = True
82             else:
83                 for model_field in serializer.Meta.model._meta.fields:
84                     if field.field_name == model_field.name:
85                         if getattr(model_field, '__accepts_json__', None):
86                             field_info['type'] = 'json'
87                         field_info['filterable'] = True
88                         break
89                 else:
90                     field_info['filterable'] = False
91 
92         # Indicate if a field has a default value.
93         # FIXME: Still isn't showing all default values?
94         try:
95             default = field.get_default()
96             if type(default) is UUID:
97                 default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
98             if field.field_name == 'TOWER_URL_BASE' and default == 'https://towerhost':
99                 default = '{}://{}'.format(self.request.scheme, self.request.get_host())
100             field_info['default'] = default
101         except serializers.SkipField:
102             pass
103 
104         if getattr(field, 'child', None):
105             field_info['child'] = self.get_field_info(field.child)
106         elif getattr(field, 'fields', None):
107             field_info['children'] = self.get_serializer_info(field)
108 
109         if not isinstance(field, (RelatedField, ManyRelatedField)) and hasattr(field, 'choices'):
110             choices = [(choice_value, choice_name) for choice_value, choice_name in field.choices.items()]
111             if not any(choice in ('', None) for choice, _ in choices):
112                 if field.allow_blank:
113                     choices = [("", "---------")] + choices
114                 if field.allow_null and not isinstance(field, ChoiceNullField):
115                     choices = [(None, "---------")] + choices
116             field_info['choices'] = choices
117 
118         # Indicate if a field is write-only.
119         if getattr(field, 'write_only', False):
120             field_info['write_only'] = True
121 
122         # Special handling of notification configuration where the required properties
123         # are conditional on the type selected.
124         if field.field_name == 'notification_configuration':
125             for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:
126                 field_info[notification_type_name] = notification_type_class.init_parameters
127 
128         # Special handling of notification messages where the required properties
129         # are conditional on the type selected.
130         try:
131             view_model = field.context['view'].model
132         except (AttributeError, KeyError):
133             view_model = None
134         if view_model == NotificationTemplate and field.field_name == 'messages':
135             for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:
136                 field_info[notification_type_name] = notification_type_class.default_messages
137 
138         # Update type of fields returned...
139         model_field = None
140         if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):
141             try:
142                 model_field = serializer.Meta.model._meta.get_field(field.field_name)
143             except Exception:
144                 pass
145         if field.field_name == 'type':
146             field_info['type'] = 'choice'
147         elif field.field_name in ('url', 'custom_virtualenv', 'token'):
148             field_info['type'] = 'string'
149         elif field.field_name in ('related', 'summary_fields'):
150             field_info['type'] = 'object'
151         elif isinstance(field, PositiveIntegerField):
152             field_info['type'] = 'integer'
153         elif field.field_name in ('created', 'modified'):
154             field_info['type'] = 'datetime'
155         elif RelatedField in field.__class__.__bases__ or isinstance(model_field, ForeignKey):
156             field_info['type'] = 'id'
157         elif (
158             isinstance(field, JSONField)
159             or isinstance(model_field, JSONField)
160             or isinstance(field, DRFJSONField)
161             or isinstance(getattr(field, 'model_field', None), JSONField)
162             or field.field_name == 'credential_passwords'
163         ):
164             field_info['type'] = 'json'
165         elif (
166             isinstance(field, ManyRelatedField)
167             and field.field_name == 'credentials'
168             # launch-time credentials
169         ):
170             field_info['type'] = 'list_of_ids'
171         elif isinstance(model_field, BooleanField):
172             field_info['type'] = 'boolean'
173 
174         return field_info
175 
176     def get_serializer_info(self, serializer, method=None):
177         filterer = getattr(serializer, 'filter_field_metadata', lambda fields, method: fields)
178         return filterer(super(Metadata, self).get_serializer_info(serializer), method)
179 
180     def determine_actions(self, request, view):
181         # Add field information for GET requests (so field names/labels are
182         # available even when we can't POST/PUT).
183         actions = {}
184         for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):
185             view.request = clone_request(request, method)
186             obj = None
187             try:
188                 # Test global permissions
189                 if hasattr(view, 'check_permissions'):
190                     view.check_permissions(view.request)
191                 # Test object permissions
192                 if method == 'PUT' and hasattr(view, 'get_object'):
193                     obj = view.get_object()
194             except (exceptions.APIException, PermissionDenied, Http404):
195                 continue
196             else:
197                 # If user has appropriate permissions for the view, include
198                 # appropriate metadata about the fields that should be supplied.
199                 serializer = view.get_serializer(instance=obj)
200                 actions[method] = self.get_serializer_info(serializer, method=method)
201             finally:
202                 view.request = request
203 
204             for field, meta in list(actions[method].items()):
205                 if not isinstance(meta, dict):
206                     continue
207 
208                 if field == "pod_spec_override":
209                     meta['default'] = AWXReceptorJob().pod_definition
210 
211                 # Add type choices if available from the serializer.
212                 if field == 'type' and hasattr(serializer, 'get_type_choices'):
213                     meta['choices'] = serializer.get_type_choices()
214 
215                 # For GET method, remove meta attributes that aren't relevant
216                 # when reading a field and remove write-only fields.
217                 if method == 'GET':
218                     attrs_to_remove = ('required', 'read_only', 'default', 'min_length', 'max_length', 'placeholder')
219                     for attr in attrs_to_remove:
220                         meta.pop(attr, None)
221                         meta.get('child', {}).pop(attr, None)
222                     if meta.pop('write_only', False):
223                         actions['GET'].pop(field)
224 
225                 # For PUT/POST methods, remove read-only fields.
226                 if method in ('PUT', 'POST'):
227                     # This value should always be False for PUT/POST, so don't
228                     # show it (file-based read-only settings can't be updated)
229                     meta.pop('defined_in_file', False)
230 
231                     if meta.pop('read_only', False):
232                         if field == 'id' and hasattr(view, 'attach'):
233                             continue
234                         actions[method].pop(field)
235 
236         return actions
237 
238     def determine_metadata(self, request, view):
239         # store request on self so we can use it to generate field defaults
240         # (such as TOWER_URL_BASE)
241         self.request = request
242 
243         try:
244             setattr(view, '_request', request)
245             metadata = super(Metadata, self).determine_metadata(request, view)
246         finally:
247             delattr(view, '_request')
248 
249         # Add type(s) handled by this view/serializer.
250         if hasattr(view, 'get_serializer'):
251             serializer = view.get_serializer()
252             if hasattr(serializer, 'get_types'):
253                 metadata['types'] = serializer.get_types()
254 
255         # Add search fields if available from the view.
256         if getattr(view, 'search_fields', None):
257             metadata['search_fields'] = view.search_fields
258 
259         # Add related search fields if available from the view.
260         if getattr(view, 'related_search_fields', None):
261             metadata['related_search_fields'] = view.related_search_fields
262 
263         # include role names in metadata
264         roles = []
265         model = getattr(view, 'model', None)
266         if model:
267             for field in model._meta.get_fields():
268                 if type(field) is ImplicitRoleField:
269                     roles.append(field.name)
270         if len(roles) > 0:
271             metadata['object_roles'] = roles
272 
273         from rest_framework import generics
274 
275         if isinstance(view, generics.ListAPIView) and hasattr(view, 'paginator'):
276             metadata['max_page_size'] = view.paginator.max_page_size
277 
278         return metadata
279 
280 
281 class RoleMetadata(Metadata):
282     def determine_metadata(self, request, view):
283         metadata = super(RoleMetadata, self).determine_metadata(request, view)
284         if 'actions' in metadata:
285             metadata['actions'].pop('POST')
286             metadata['actions']['POST'] = {
287                 "id": {"type": "integer", "label": "ID", "help_text": "Database ID for this role."},
288                 "disassociate": {"type": "integer", "label": "Disassociate", "help_text": "Provide to remove this role."},
289             }
290         return metadata
291 
292 
293 class SublistAttachDetatchMetadata(Metadata):
294     def determine_actions(self, request, view):
295         actions = super(SublistAttachDetatchMetadata, self).determine_actions(request, view)
296         method = 'POST'
297         if method in actions:
298             for field in list(actions[method].keys()):
299                 if field == 'id':
300                     continue
301                 actions[method].pop(field)
302         return actions
303
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awx/api/metadata.py b/awx/api/metadata.py
--- a/awx/api/metadata.py
+++ b/awx/api/metadata.py
@@ -26,6 +26,9 @@
 from awx.main.models import NotificationTemplate
 from awx.main.tasks import AWXReceptorJob
 
+# Polymorphic
+from polymorphic.models import PolymorphicModel
+
 
 class Metadata(metadata.SimpleMetadata):
     def get_field_info(self, field):
@@ -78,7 +81,9 @@
                 field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
 
             if field.field_name == 'type':
-                field_info['filterable'] = True
+                # Only include model classes with `type` field.
+                if issubclass(serializer.Meta.model, PolymorphicModel):
+                    field_info['filterable'] = True
             else:
                 for model_field in serializer.Meta.model._meta.fields:
                     if field.field_name == model_field.name:

{"golden_diff": "diff --git a/awx/api/metadata.py b/awx/api/metadata.py\n--- a/awx/api/metadata.py\n+++ b/awx/api/metadata.py\n@@ -26,6 +26,9 @@\n from awx.main.models import NotificationTemplate\n from awx.main.tasks import AWXReceptorJob\n \n+# Polymorphic\n+from polymorphic.models import PolymorphicModel\n+\n \n class Metadata(metadata.SimpleMetadata):\n     def get_field_info(self, field):\n@@ -78,7 +81,9 @@\n                 field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)\n \n             if field.field_name == 'type':\n-                field_info['filterable'] = True\n+                # Only include model classes with `type` field.\n+                if issubclass(serializer.Meta.model, PolymorphicModel):\n+                    field_info['filterable'] = True\n             else:\n                 for model_field in serializer.Meta.model._meta.fields:\n                     if field.field_name == model_field.name:\n", "issue": "Advanced search for type returns 400\n<!-- Issues are for **concrete, actionable bugs and feature requests** only - if you're just asking for debugging help or technical support, please use:\r\n\r\n- http://webchat.freenode.net/?channels=ansible-awx\r\n- https://groups.google.com/forum/#!forum/awx-project\r\n\r\nWe have to limit this because of limited volunteer time to respond to issues! -->\r\n\r\n##### ISSUE TYPE\r\n - Bug Report\r\n\r\n##### SUMMARY\r\n\r\nTrying to do an advanced search by `type` the results are always `ExecutionEnvironment has no field named 'type'`:\r\n\r\n![image](https://user-images.githubusercontent.com/12464858/109544586-83d70a00-7aa6-11eb-994c-dedcbce8801a.png)\r\n\r\n\r\n\n", "before_files": [{"content": "# Copyright (c) 2016 Ansible, Inc.\n# All Rights Reserved.\n\nfrom collections import OrderedDict\nfrom uuid import UUID\n\n# Django\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models.fields import PositiveIntegerField, BooleanField\nfrom django.db.models.fields.related import ForeignKey\nfrom django.http import Http404\nfrom django.utils.encoding import force_text, smart_text\nfrom django.utils.translation import ugettext_lazy as _\n\n# Django REST Framework\nfrom rest_framework import exceptions\nfrom rest_framework import metadata\nfrom rest_framework import serializers\nfrom rest_framework.relations import RelatedField, ManyRelatedField\nfrom rest_framework.fields import JSONField as DRFJSONField\nfrom rest_framework.request import clone_request\n\n# AWX\nfrom awx.api.fields import ChoiceNullField\nfrom awx.main.fields import JSONField, ImplicitRoleField\nfrom awx.main.models import NotificationTemplate\nfrom awx.main.tasks import AWXReceptorJob\n\n\nclass Metadata(metadata.SimpleMetadata):\n    def get_field_info(self, field):\n        field_info = OrderedDict()\n        field_info['type'] = self.label_lookup[field]\n        field_info['required'] = getattr(field, 'required', False)\n\n        text_attrs = [\n            'read_only',\n            'label',\n            'help_text',\n            'min_length',\n            'max_length',\n            'min_value',\n            'max_value',\n            'category',\n            'category_slug',\n            'defined_in_file',\n            'unit',\n        ]\n\n        for attr in text_attrs:\n            value = getattr(field, attr, None)\n            if value is not None and value != '':\n                field_info[attr] = force_text(value, strings_only=True)\n\n        placeholder = getattr(field, 'placeholder', serializers.empty)\n        if placeholder is not serializers.empty:\n            field_info['placeholder'] = placeholder\n\n        serializer = getattr(field, 'parent', None)\n        if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):\n            # Update help text for common fields.\n            field_help_text = {\n                'id': _('Database ID for this {}.'),\n                'name': _('Name of this {}.'),\n                'description': _('Optional description of this {}.'),\n                'type': _('Data type for this {}.'),\n                'url': _('URL for this {}.'),\n                'related': _('Data structure with URLs of related resources.'),\n                'summary_fields': _(\n                    'Data structure with name/description for related resources.  ' 'The output for some objects may be limited for performance reasons.'\n                ),\n                'created': _('Timestamp when this {} was created.'),\n                'modified': _('Timestamp when this {} was last modified.'),\n            }\n            if field.field_name in field_help_text:\n                opts = serializer.Meta.model._meta.concrete_model._meta\n                verbose_name = smart_text(opts.verbose_name)\n                field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)\n\n            if field.field_name == 'type':\n                field_info['filterable'] = True\n            else:\n                for model_field in serializer.Meta.model._meta.fields:\n                    if field.field_name == model_field.name:\n                        if getattr(model_field, '__accepts_json__', None):\n                            field_info['type'] = 'json'\n                        field_info['filterable'] = True\n                        break\n                else:\n                    field_info['filterable'] = False\n\n        # Indicate if a field has a default value.\n        # FIXME: Still isn't showing all default values?\n        try:\n            default = field.get_default()\n            if type(default) is UUID:\n                default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n            if field.field_name == 'TOWER_URL_BASE' and default == 'https://towerhost':\n                default = '{}://{}'.format(self.request.scheme, self.request.get_host())\n            field_info['default'] = default\n        except serializers.SkipField:\n            pass\n\n        if getattr(field, 'child', None):\n            field_info['child'] = self.get_field_info(field.child)\n        elif getattr(field, 'fields', None):\n            field_info['children'] = self.get_serializer_info(field)\n\n        if not isinstance(field, (RelatedField, ManyRelatedField)) and hasattr(field, 'choices'):\n            choices = [(choice_value, choice_name) for choice_value, choice_name in field.choices.items()]\n            if not any(choice in ('', None) for choice, _ in choices):\n                if field.allow_blank:\n                    choices = [(\"\", \"---------\")] + choices\n                if field.allow_null and not isinstance(field, ChoiceNullField):\n                    choices = [(None, \"---------\")] + choices\n            field_info['choices'] = choices\n\n        # Indicate if a field is write-only.\n        if getattr(field, 'write_only', False):\n            field_info['write_only'] = True\n\n        # Special handling of notification configuration where the required properties\n        # are conditional on the type selected.\n        if field.field_name == 'notification_configuration':\n            for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:\n                field_info[notification_type_name] = notification_type_class.init_parameters\n\n        # Special handling of notification messages where the required properties\n        # are conditional on the type selected.\n        try:\n            view_model = field.context['view'].model\n        except (AttributeError, KeyError):\n            view_model = None\n        if view_model == NotificationTemplate and field.field_name == 'messages':\n            for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:\n                field_info[notification_type_name] = notification_type_class.default_messages\n\n        # Update type of fields returned...\n        model_field = None\n        if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):\n            try:\n                model_field = serializer.Meta.model._meta.get_field(field.field_name)\n            except Exception:\n                pass\n        if field.field_name == 'type':\n            field_info['type'] = 'choice'\n        elif field.field_name in ('url', 'custom_virtualenv', 'token'):\n            field_info['type'] = 'string'\n        elif field.field_name in ('related', 'summary_fields'):\n            field_info['type'] = 'object'\n        elif isinstance(field, PositiveIntegerField):\n            field_info['type'] = 'integer'\n        elif field.field_name in ('created', 'modified'):\n            field_info['type'] = 'datetime'\n        elif RelatedField in field.__class__.__bases__ or isinstance(model_field, ForeignKey):\n            field_info['type'] = 'id'\n        elif (\n            isinstance(field, JSONField)\n            or isinstance(model_field, JSONField)\n            or isinstance(field, DRFJSONField)\n            or isinstance(getattr(field, 'model_field', None), JSONField)\n            or field.field_name == 'credential_passwords'\n        ):\n            field_info['type'] = 'json'\n        elif (\n            isinstance(field, ManyRelatedField)\n            and field.field_name == 'credentials'\n            # launch-time credentials\n        ):\n            field_info['type'] = 'list_of_ids'\n        elif isinstance(model_field, BooleanField):\n            field_info['type'] = 'boolean'\n\n        return field_info\n\n    def get_serializer_info(self, serializer, method=None):\n        filterer = getattr(serializer, 'filter_field_metadata', lambda fields, method: fields)\n        return filterer(super(Metadata, self).get_serializer_info(serializer), method)\n\n    def determine_actions(self, request, view):\n        # Add field information for GET requests (so field names/labels are\n        # available even when we can't POST/PUT).\n        actions = {}\n        for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):\n            view.request = clone_request(request, method)\n            obj = None\n            try:\n                # Test global permissions\n                if hasattr(view, 'check_permissions'):\n                    view.check_permissions(view.request)\n                # Test object permissions\n                if method == 'PUT' and hasattr(view, 'get_object'):\n                    obj = view.get_object()\n            except (exceptions.APIException, PermissionDenied, Http404):\n                continue\n            else:\n                # If user has appropriate permissions for the view, include\n                # appropriate metadata about the fields that should be supplied.\n                serializer = view.get_serializer(instance=obj)\n                actions[method] = self.get_serializer_info(serializer, method=method)\n            finally:\n                view.request = request\n\n            for field, meta in list(actions[method].items()):\n                if not isinstance(meta, dict):\n                    continue\n\n                if field == \"pod_spec_override\":\n                    meta['default'] = AWXReceptorJob().pod_definition\n\n                # Add type choices if available from the serializer.\n                if field == 'type' and hasattr(serializer, 'get_type_choices'):\n                    meta['choices'] = serializer.get_type_choices()\n\n                # For GET method, remove meta attributes that aren't relevant\n                # when reading a field and remove write-only fields.\n                if method == 'GET':\n                    attrs_to_remove = ('required', 'read_only', 'default', 'min_length', 'max_length', 'placeholder')\n                    for attr in attrs_to_remove:\n                        meta.pop(attr, None)\n                        meta.get('child', {}).pop(attr, None)\n                    if meta.pop('write_only', False):\n                        actions['GET'].pop(field)\n\n                # For PUT/POST methods, remove read-only fields.\n                if method in ('PUT', 'POST'):\n                    # This value should always be False for PUT/POST, so don't\n                    # show it (file-based read-only settings can't be updated)\n                    meta.pop('defined_in_file', False)\n\n                    if meta.pop('read_only', False):\n                        if field == 'id' and hasattr(view, 'attach'):\n                            continue\n                        actions[method].pop(field)\n\n        return actions\n\n    def determine_metadata(self, request, view):\n        # store request on self so we can use it to generate field defaults\n        # (such as TOWER_URL_BASE)\n        self.request = request\n\n        try:\n            setattr(view, '_request', request)\n            metadata = super(Metadata, self).determine_metadata(request, view)\n        finally:\n            delattr(view, '_request')\n\n        # Add type(s) handled by this view/serializer.\n        if hasattr(view, 'get_serializer'):\n            serializer = view.get_serializer()\n            if hasattr(serializer, 'get_types'):\n                metadata['types'] = serializer.get_types()\n\n        # Add search fields if available from the view.\n        if getattr(view, 'search_fields', None):\n            metadata['search_fields'] = view.search_fields\n\n        # Add related search fields if available from the view.\n        if getattr(view, 'related_search_fields', None):\n            metadata['related_search_fields'] = view.related_search_fields\n\n        # include role names in metadata\n        roles = []\n        model = getattr(view, 'model', None)\n        if model:\n            for field in model._meta.get_fields():\n                if type(field) is ImplicitRoleField:\n                    roles.append(field.name)\n        if len(roles) > 0:\n            metadata['object_roles'] = roles\n\n        from rest_framework import generics\n\n        if isinstance(view, generics.ListAPIView) and hasattr(view, 'paginator'):\n            metadata['max_page_size'] = view.paginator.max_page_size\n\n        return metadata\n\n\nclass RoleMetadata(Metadata):\n    def determine_metadata(self, request, view):\n        metadata = super(RoleMetadata, self).determine_metadata(request, view)\n        if 'actions' in metadata:\n            metadata['actions'].pop('POST')\n            metadata['actions']['POST'] = {\n                \"id\": {\"type\": \"integer\", \"label\": \"ID\", \"help_text\": \"Database ID for this role.\"},\n                \"disassociate\": {\"type\": \"integer\", \"label\": \"Disassociate\", \"help_text\": \"Provide to remove this role.\"},\n            }\n        return metadata\n\n\nclass SublistAttachDetatchMetadata(Metadata):\n    def determine_actions(self, request, view):\n        actions = super(SublistAttachDetatchMetadata, self).determine_actions(request, view)\n        method = 'POST'\n        if method in actions:\n            for field in list(actions[method].keys()):\n                if field == 'id':\n                    continue\n                actions[method].pop(field)\n        return actions\n", "path": "awx/api/metadata.py"}], "after_files": [{"content": "# Copyright (c) 2016 Ansible, Inc.\n# All Rights Reserved.\n\nfrom collections import OrderedDict\nfrom uuid import UUID\n\n# Django\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models.fields import PositiveIntegerField, BooleanField\nfrom django.db.models.fields.related import ForeignKey\nfrom django.http import Http404\nfrom django.utils.encoding import force_text, smart_text\nfrom django.utils.translation import ugettext_lazy as _\n\n# Django REST Framework\nfrom rest_framework import exceptions\nfrom rest_framework import metadata\nfrom rest_framework import serializers\nfrom rest_framework.relations import RelatedField, ManyRelatedField\nfrom rest_framework.fields import JSONField as DRFJSONField\nfrom rest_framework.request import clone_request\n\n# AWX\nfrom awx.api.fields import ChoiceNullField\nfrom awx.main.fields import JSONField, ImplicitRoleField\nfrom awx.main.models import NotificationTemplate\nfrom awx.main.tasks import AWXReceptorJob\n\n# Polymorphic\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Metadata(metadata.SimpleMetadata):\n    def get_field_info(self, field):\n        field_info = OrderedDict()\n        field_info['type'] = self.label_lookup[field]\n        field_info['required'] = getattr(field, 'required', False)\n\n        text_attrs = [\n            'read_only',\n            'label',\n            'help_text',\n            'min_length',\n            'max_length',\n            'min_value',\n            'max_value',\n            'category',\n            'category_slug',\n            'defined_in_file',\n            'unit',\n        ]\n\n        for attr in text_attrs:\n            value = getattr(field, attr, None)\n            if value is not None and value != '':\n                field_info[attr] = force_text(value, strings_only=True)\n\n        placeholder = getattr(field, 'placeholder', serializers.empty)\n        if placeholder is not serializers.empty:\n            field_info['placeholder'] = placeholder\n\n        serializer = getattr(field, 'parent', None)\n        if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):\n            # Update help text for common fields.\n            field_help_text = {\n                'id': _('Database ID for this {}.'),\n                'name': _('Name of this {}.'),\n                'description': _('Optional description of this {}.'),\n                'type': _('Data type for this {}.'),\n                'url': _('URL for this {}.'),\n                'related': _('Data structure with URLs of related resources.'),\n                'summary_fields': _(\n                    'Data structure with name/description for related resources.  ' 'The output for some objects may be limited for performance reasons.'\n                ),\n                'created': _('Timestamp when this {} was created.'),\n                'modified': _('Timestamp when this {} was last modified.'),\n            }\n            if field.field_name in field_help_text:\n                opts = serializer.Meta.model._meta.concrete_model._meta\n                verbose_name = smart_text(opts.verbose_name)\n                field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)\n\n            if field.field_name == 'type':\n                # Only include model classes with `type` field.\n                if issubclass(serializer.Meta.model, PolymorphicModel):\n                    field_info['filterable'] = True\n            else:\n                for model_field in serializer.Meta.model._meta.fields:\n                    if field.field_name == model_field.name:\n                        if getattr(model_field, '__accepts_json__', None):\n                            field_info['type'] = 'json'\n                        field_info['filterable'] = True\n                        break\n                else:\n                    field_info['filterable'] = False\n\n        # Indicate if a field has a default value.\n        # FIXME: Still isn't showing all default values?\n        try:\n            default = field.get_default()\n            if type(default) is UUID:\n                default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n            if field.field_name == 'TOWER_URL_BASE' and default == 'https://towerhost':\n                default = '{}://{}'.format(self.request.scheme, self.request.get_host())\n            field_info['default'] = default\n        except serializers.SkipField:\n            pass\n\n        if getattr(field, 'child', None):\n            field_info['child'] = self.get_field_info(field.child)\n        elif getattr(field, 'fields', None):\n            field_info['children'] = self.get_serializer_info(field)\n\n        if not isinstance(field, (RelatedField, ManyRelatedField)) and hasattr(field, 'choices'):\n            choices = [(choice_value, choice_name) for choice_value, choice_name in field.choices.items()]\n            if not any(choice in ('', None) for choice, _ in choices):\n                if field.allow_blank:\n                    choices = [(\"\", \"---------\")] + choices\n                if field.allow_null and not isinstance(field, ChoiceNullField):\n                    choices = [(None, \"---------\")] + choices\n            field_info['choices'] = choices\n\n        # Indicate if a field is write-only.\n        if getattr(field, 'write_only', False):\n            field_info['write_only'] = True\n\n        # Special handling of notification configuration where the required properties\n        # are conditional on the type selected.\n        if field.field_name == 'notification_configuration':\n            for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:\n                field_info[notification_type_name] = notification_type_class.init_parameters\n\n        # Special handling of notification messages where the required properties\n        # are conditional on the type selected.\n        try:\n            view_model = field.context['view'].model\n        except (AttributeError, KeyError):\n            view_model = None\n        if view_model == NotificationTemplate and field.field_name == 'messages':\n            for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:\n                field_info[notification_type_name] = notification_type_class.default_messages\n\n        # Update type of fields returned...\n        model_field = None\n        if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):\n            try:\n                model_field = serializer.Meta.model._meta.get_field(field.field_name)\n            except Exception:\n                pass\n        if field.field_name == 'type':\n            field_info['type'] = 'choice'\n        elif field.field_name in ('url', 'custom_virtualenv', 'token'):\n            field_info['type'] = 'string'\n        elif field.field_name in ('related', 'summary_fields'):\n            field_info['type'] = 'object'\n        elif isinstance(field, PositiveIntegerField):\n            field_info['type'] = 'integer'\n        elif field.field_name in ('created', 'modified'):\n            field_info['type'] = 'datetime'\n        elif RelatedField in field.__class__.__bases__ or isinstance(model_field, ForeignKey):\n            field_info['type'] = 'id'\n        elif (\n            isinstance(field, JSONField)\n            or isinstance(model_field, JSONField)\n            or isinstance(field, DRFJSONField)\n            or isinstance(getattr(field, 'model_field', None), JSONField)\n            or field.field_name == 'credential_passwords'\n        ):\n            field_info['type'] = 'json'\n        elif (\n            isinstance(field, ManyRelatedField)\n            and field.field_name == 'credentials'\n            # launch-time credentials\n        ):\n            field_info['type'] = 'list_of_ids'\n        elif isinstance(model_field, BooleanField):\n            field_info['type'] = 'boolean'\n\n        return field_info\n\n    def get_serializer_info(self, serializer, method=None):\n        filterer = getattr(serializer, 'filter_field_metadata', lambda fields, method: fields)\n        return filterer(super(Metadata, self).get_serializer_info(serializer), method)\n\n    def determine_actions(self, request, view):\n        # Add field information for GET requests (so field names/labels are\n        # available even when we can't POST/PUT).\n        actions = {}\n        for method in {'GET', 'PUT', 'POST'} & set(view.allowed_methods):\n            view.request = clone_request(request, method)\n            obj = None\n            try:\n                # Test global permissions\n                if hasattr(view, 'check_permissions'):\n                    view.check_permissions(view.request)\n                # Test object permissions\n                if method == 'PUT' and hasattr(view, 'get_object'):\n                    obj = view.get_object()\n            except (exceptions.APIException, PermissionDenied, Http404):\n                continue\n            else:\n                # If user has appropriate permissions for the view, include\n                # appropriate metadata about the fields that should be supplied.\n                serializer = view.get_serializer(instance=obj)\n                actions[method] = self.get_serializer_info(serializer, method=method)\n            finally:\n                view.request = request\n\n            for field, meta in list(actions[method].items()):\n                if not isinstance(meta, dict):\n                    continue\n\n                if field == \"pod_spec_override\":\n                    meta['default'] = AWXReceptorJob().pod_definition\n\n                # Add type choices if available from the serializer.\n                if field == 'type' and hasattr(serializer, 'get_type_choices'):\n                    meta['choices'] = serializer.get_type_choices()\n\n                # For GET method, remove meta attributes that aren't relevant\n                # when reading a field and remove write-only fields.\n                if method == 'GET':\n                    attrs_to_remove = ('required', 'read_only', 'default', 'min_length', 'max_length', 'placeholder')\n                    for attr in attrs_to_remove:\n                        meta.pop(attr, None)\n                        meta.get('child', {}).pop(attr, None)\n                    if meta.pop('write_only', False):\n                        actions['GET'].pop(field)\n\n                # For PUT/POST methods, remove read-only fields.\n                if method in ('PUT', 'POST'):\n                    # This value should always be False for PUT/POST, so don't\n                    # show it (file-based read-only settings can't be updated)\n                    meta.pop('defined_in_file', False)\n\n                    if meta.pop('read_only', False):\n                        if field == 'id' and hasattr(view, 'attach'):\n                            continue\n                        actions[method].pop(field)\n\n        return actions\n\n    def determine_metadata(self, request, view):\n        # store request on self so we can use it to generate field defaults\n        # (such as TOWER_URL_BASE)\n        self.request = request\n\n        try:\n            setattr(view, '_request', request)\n            metadata = super(Metadata, self).determine_metadata(request, view)\n        finally:\n            delattr(view, '_request')\n\n        # Add type(s) handled by this view/serializer.\n        if hasattr(view, 'get_serializer'):\n            serializer = view.get_serializer()\n            if hasattr(serializer, 'get_types'):\n                metadata['types'] = serializer.get_types()\n\n        # Add search fields if available from the view.\n        if getattr(view, 'search_fields', None):\n            metadata['search_fields'] = view.search_fields\n\n        # Add related search fields if available from the view.\n        if getattr(view, 'related_search_fields', None):\n            metadata['related_search_fields'] = view.related_search_fields\n\n        # include role names in metadata\n        roles = []\n        model = getattr(view, 'model', None)\n        if model:\n            for field in model._meta.get_fields():\n                if type(field) is ImplicitRoleField:\n                    roles.append(field.name)\n        if len(roles) > 0:\n            metadata['object_roles'] = roles\n\n        from rest_framework import generics\n\n        if isinstance(view, generics.ListAPIView) and hasattr(view, 'paginator'):\n            metadata['max_page_size'] = view.paginator.max_page_size\n\n        return metadata\n\n\nclass RoleMetadata(Metadata):\n    def determine_metadata(self, request, view):\n        metadata = super(RoleMetadata, self).determine_metadata(request, view)\n        if 'actions' in metadata:\n            metadata['actions'].pop('POST')\n            metadata['actions']['POST'] = {\n                \"id\": {\"type\": \"integer\", \"label\": \"ID\", \"help_text\": \"Database ID for this role.\"},\n                \"disassociate\": {\"type\": \"integer\", \"label\": \"Disassociate\", \"help_text\": \"Provide to remove this role.\"},\n            }\n        return metadata\n\n\nclass SublistAttachDetatchMetadata(Metadata):\n    def determine_actions(self, request, view):\n        actions = super(SublistAttachDetatchMetadata, self).determine_actions(request, view)\n        method = 'POST'\n        if method in actions:\n            for field in list(actions[method].keys()):\n                if field == 'id':\n                    continue\n                actions[method].pop(field)\n        return actions\n", "path": "awx/api/metadata.py"}]}