Repository: ansible/awx

Issue:
Passing a bad lookup type modifier in api request triggers 500 error
##### ISSUE TYPE
 - Bug Report

##### SUMMARY
Send a request like `/api/v2/jobs/?id__iexact=1`

<img width="1680" alt="Screen Shot 2021-02-03 at 12 28 26 PM" src="https://user-images.githubusercontent.com/9889020/106785392-56b84880-661b-11eb-9288-fa50845636f7.png">

`iexact` isn't supported on the `id` field.  What I'm hoping for is a nice 4xx error that says that modifier is not supported.

The impetuous for this issue came from https://github.com/ansible/awx/issues/7834.  The advanced search in the UI allows the user to select from a static list of modifiers in the `Advanced` section.  I left that issue open as a UI enhancement to attempt to trim that list down to only modifiers that will work with a particular field.


Discussion:
@sarabrajsingh are you still working on this right now? If now I'd like to keep it in the On Deck issues for anyone to pick up as able.
There is a theory that this may change with the in progress Django upgrade. 
@jbradberry I believe recommended this wait until that's complete to recreate/resolve. 
Marked as blocked
Confirmed that `/api/v2/jobs/?id__iexact=1` gives a 500 with current devel. I believe this is ready to be worked on.

Resolving patch:
diff --git a/awx/api/filters.py b/awx/api/filters.py
--- a/awx/api/filters.py
+++ b/awx/api/filters.py
@@ -232,6 +232,9 @@ def value_to_python(self, model, lookup, value):
                 re.compile(value)
             except re.error as e:
                 raise ValueError(e.args[0])
+        elif new_lookup.endswith('__iexact'):
+            if not isinstance(field, (CharField, TextField)):
+                raise ValueError(f'{field.name} is not a text field and cannot be filtered by case-insensitive search')
         elif new_lookup.endswith('__search'):
             related_model = getattr(field, 'related_model', None)
             if not related_model:
@@ -258,8 +261,8 @@ def filter_queryset(self, request, queryset, view):
             search_filters = {}
             needs_distinct = False
             # Can only have two values: 'AND', 'OR'
-            # If 'AND' is used, an iterm must satisfy all condition to show up in the results.
-            # If 'OR' is used, an item just need to satisfy one condition to appear in results.
+            # If 'AND' is used, an item must satisfy all conditions to show up in the results.
+            # If 'OR' is used, an item just needs to satisfy one condition to appear in results.
             search_filter_relation = 'OR'
             for key, values in request.query_params.lists():
                 if key in self.RESERVED_NAMES:


Test patch:
diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py
--- a/awx/main/tests/unit/api/test_filters.py
+++ b/awx/main/tests/unit/api/test_filters.py
@@ -79,6 +79,19 @@ def test_invalid_field():
     assert 'is not an allowed field name. Must be ascii encodable.' in str(excinfo.value)
 
 
+def test_valid_iexact():
+    field_lookup = FieldLookupBackend()
+    value, new_lookup, _ = field_lookup.value_to_python(JobTemplate, 'project__name__iexact', 'foo')
+    assert 'foo' in value
+
+
+def test_invalid_iexact():
+    field_lookup = FieldLookupBackend()
+    with pytest.raises(ValueError) as excinfo:
+        field_lookup.value_to_python(Job, 'id__iexact', '1')
+    assert 'is not a text field and cannot be filtered by case-insensitive search' in str(excinfo.value)
+
+
 @pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in'])
 @pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
 def test_filter_on_password_field(password_field, lookup_suffix):