Repository: wagtail/wagtail

Issue:
Image search: FilterFieldError when using tag filter combined with search query
### Issue Summary

I looks like the search implementation has some issues with the `RelatedFields` search for tags, which contains a "name" field. It seems like this leads to the following error, if you search for both a term and a tag:

```
Internal Server Error: /admin/images/
Traceback (most recent call last):
  File "/.../venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/.../venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/decorators/cache.py", line 62, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/urls/__init__.py", line 165, in wrapper
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/auth.py", line 205, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 142, in dispatch
    return handler(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/auth.py", line 62, in wrapped_view_func
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 49, in get
    return super().get(request)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 216, in get
    context = self.get_context_data(**kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 147, in get_context_data
    context = super().get_context_data(**kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 114, in get_context_data
    images = images.search(query_string)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/queryset.py", line 18, in search
    return search_backend.search(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 432, in search
    return self._search(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 419, in _search
    search_query_compiler.check()
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 217, in check
    self._get_filters_from_where_node(self.queryset.query.where, check_only=True)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 145, in _get_filters_from_where_node
    child_filters = [
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 146, in <listcomp>
    self._get_filters_from_where_node(child)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 133, in _get_filters_from_where_node
    return self._process_filter(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 81, in _process_filter
    raise FilterFieldError(
wagtail.search.backends.base.FilterFieldError: Cannot filter search results with field "name". Please add index.FilterField('name') to Image.search_fields.
```

I've tested the same with a wagtail 2.15 installation and it doesn't fail there, but the search only uses the search term and does not combine it with the tag, so this probably bypasses the issue.

This issue matches with #6616, but it also affects the core image model as demonstrated here.

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. `./manage.py makemigrations && ./manage.py migrate && ./manage.py createsuperuser && ./manage.py runserver`
3. Login to the CMS and go to /admin/images/
4. Trigger the error:

**Option A - Quick:**
Go to http://127.0.0.1:8000/admin/images/?q=test&tag=test

**Option B - by UI:**
1. Upload images and set at least one tag
2. On the listing page, first enter a search term (this updates the URL)
3. Reload the page
4. Click on one of the assigned tags in the "popular tags" list

There are multiple paths to get there with the UI, another one:
Use the global search to search for a term, then select "Images" in "Other searches" on the result page, then click one of the assigned tags.

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

### Technical details

-   Python version: 3.10
-   Django version: 3.2
-   Wagtail version: 4.0.1
-   Browser version: Firefox 103

Image search: FilterFieldError when using tag filter combined with search query
### Issue Summary

I looks like the search implementation has some issues with the `RelatedFields` search for tags, which contains a "name" field. It seems like this leads to the following error, if you search for both a term and a tag:

```
Internal Server Error: /admin/images/
Traceback (most recent call last):
  File "/.../venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/.../venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/decorators/cache.py", line 62, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/urls/__init__.py", line 165, in wrapper
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/auth.py", line 205, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 142, in dispatch
    return handler(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/admin/auth.py", line 62, in wrapped_view_func
    return view_func(request, *args, **kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 49, in get
    return super().get(request)
  File "/.../venv/lib/python3.10/site-packages/django/views/generic/base.py", line 216, in get
    context = self.get_context_data(**kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 147, in get_context_data
    context = super().get_context_data(**kwargs)
  File "/.../venv/lib/python3.10/site-packages/wagtail/images/views/images.py", line 114, in get_context_data
    images = images.search(query_string)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/queryset.py", line 18, in search
    return search_backend.search(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 432, in search
    return self._search(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 419, in _search
    search_query_compiler.check()
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 217, in check
    self._get_filters_from_where_node(self.queryset.query.where, check_only=True)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 145, in _get_filters_from_where_node
    child_filters = [
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 146, in <listcomp>
    self._get_filters_from_where_node(child)
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 133, in _get_filters_from_where_node
    return self._process_filter(
  File "/.../venv/lib/python3.10/site-packages/wagtail/search/backends/base.py", line 81, in _process_filter
    raise FilterFieldError(
wagtail.search.backends.base.FilterFieldError: Cannot filter search results with field "name". Please add index.FilterField('name') to Image.search_fields.
```

I've tested the same with a wagtail 2.15 installation and it doesn't fail there, but the search only uses the search term and does not combine it with the tag, so this probably bypasses the issue.

This issue matches with #6616, but it also affects the core image model as demonstrated here.

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. `./manage.py makemigrations && ./manage.py migrate && ./manage.py createsuperuser && ./manage.py runserver`
3. Login to the CMS and go to /admin/images/
4. Trigger the error:

**Option A - Quick:**
Go to http://127.0.0.1:8000/admin/images/?q=test&tag=test

**Option B - by UI:**
1. Upload images and set at least one tag
2. On the listing page, first enter a search term (this updates the URL)
3. Reload the page
4. Click on one of the assigned tags in the "popular tags" list

There are multiple paths to get there with the UI, another one:
Use the global search to search for a term, then select "Images" in "Other searches" on the result page, then click one of the assigned tags.

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

### Technical details

-   Python version: 3.10
-   Django version: 3.2
-   Wagtail version: 4.0.1
-   Browser version: Firefox 103



Discussion:
This has maybe been introduced with https://github.com/wagtail/wagtail/issues/8029, which moved code for the search-query after the filter for collection and tag.

A quick-fix for the server error could be to move the search-query between collection and tag, similar to how wagtailmedia has "fixed" it: https://github.com/torchbox/wagtailmedia/blob/main/src/wagtailmedia/views/media.py#L52
The same issue as #8029 also occurred there during testing: see https://github.com/torchbox/wagtailmedia/pull/169#issuecomment-1221955165

Of course, this won't make the combined search work, but at least the server error is gone.
This has maybe been introduced with https://github.com/wagtail/wagtail/issues/8029, which moved code for the search-query after the filter for collection and tag.

A quick-fix for the server error could be to move the search-query between collection and tag, similar to how wagtailmedia has "fixed" it: https://github.com/torchbox/wagtailmedia/blob/main/src/wagtailmedia/views/media.py#L52
The same issue as #8029 also occurred there during testing: see https://github.com/torchbox/wagtailmedia/pull/169#issuecomment-1221955165

Of course, this won't make the combined search work, but at least the server error is gone.

Resolving patch:
diff --git a/wagtail/images/views/images.py b/wagtail/images/views/images.py
--- a/wagtail/images/views/images.py
+++ b/wagtail/images/views/images.py
@@ -96,14 +96,6 @@ def get_context_data(self, **kwargs):
             except (ValueError, Collection.DoesNotExist):
                 pass
 
-        # Filter by tag
-        self.current_tag = self.request.GET.get("tag")
-        if self.current_tag:
-            try:
-                images = images.filter(tags__name=self.current_tag)
-            except (AttributeError):
-                self.current_tag = None
-
         # Search
         query_string = None
         if "q" in self.request.GET:
@@ -115,6 +107,14 @@ def get_context_data(self, **kwargs):
         else:
             self.form = SearchForm(placeholder=_("Search images"))
 
+        # Filter by tag
+        self.current_tag = self.request.GET.get("tag")
+        if self.current_tag:
+            try:
+                images = images.filter(tags__name=self.current_tag)
+            except (AttributeError):
+                self.current_tag = None
+
         entries_per_page = self.get_num_entries_per_page()
         paginator = Paginator(images, per_page=entries_per_page)
         images = paginator.get_page(self.request.GET.get("p"))


Test patch:
diff --git a/wagtail/images/tests/test_admin_views.py b/wagtail/images/tests/test_admin_views.py
--- a/wagtail/images/tests/test_admin_views.py
+++ b/wagtail/images/tests/test_admin_views.py
@@ -279,6 +279,29 @@ def test_tag_filtering_preserves_other_params(self):
             "?p=3&amp;tag=even" in response_body or "?tag=even&amp;p=3" in response_body
         )
 
+    def test_tag_filtering_with_search_term(self):
+        Image.objects.create(
+            title="Test image with no tags",
+            file=get_test_image_file(),
+        )
+
+        image_one_tag = Image.objects.create(
+            title="Test image with one tag",
+            file=get_test_image_file(),
+        )
+        image_one_tag.tags.add("one")
+
+        image_two_tags = Image.objects.create(
+            title="Test image with two tags",
+            file=get_test_image_file(),
+        )
+        image_two_tags.tags.add("one", "two")
+
+        # The tag gets ignored, if a valid search term is present, so this will find all
+        # images, as all of them contain "test" in their titles.
+        response = self.get({"tag": "one", "q": "test"})
+        self.assertEqual(response.context["images"].paginator.count, 3)
+
     def test_search_form_rendered(self):
         response = self.get()
         html = response.content.decode()