Repository: wagtail/wagtail

Issue:
Adding `q=...` to admin filtered image admin listing causes crash.
### Issue Summary

If you try to combine `?q=` search type URL with filtering by collection in the Image view, you get a crash. (`AttributeError at /admin/images/`)

```
  File "/Users/daniel/tmp/search-crash/.v/lib/python3.10/site-packages/wagtail/images/views/images.py", line 96, in get_context_data
    context = super().get_context_data(**kwargs)
  File "/Users/daniel/tmp/search-crash/.v/lib/python3.10/site-packages/wagtail/images/views/images.py", line 67, in get_context_data
    images = images.filter(collection=self.current_collection)

Exception Type: AttributeError at /admin/images/
Exception Value: 'SQLiteSearchResults' object has no attribute 'filter'
```

(We have a client who wants to be able to search within an image collection, so were exploring options)

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. `cd myproject`
2. `./manage.py migrate`
3. `./manage.py createsuperuser`
4. `./manage.py runserver`
5. http://localhost:8000/admin/images/?collection_id=1&q=1

Crash.

* I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: (yes)

### Technical details

* Python version: 3.8, 3.9, 3.10
* Django version: 3.2, 4.0
* Wagtail version: 2.15, 2.16.1
* Browser version: N/A
* Search backends: SQLLite, Postgres (database backend) and ElasticSearch backends all have this.

e.g:
```
'SQLiteSearchResults' object has no attribute 'filter'
```
```
'Elasticsearch6SearchResults' object has no attribute 'filter'
```
```
'PostgresSearchResults' object has no attribute 'filter'
```


Discussion:
My guess is it's this line. 

https://github.com/wagtail/wagtail/blob/main/wagtail/images/views/images.py#L56

We could add some checks to ensure the q GET Param has a value here before trying to build the search query results. 

```python
query_string = None
        if "q" in self.request.GET:
            self.form = SearchForm(self.request.GET, placeholder=_("Search images"))
            if self.form.is_valid():
                query_string = self.form.cleaned_data["q"]

                images = images.search(query_string)
```

Might also be worth digging into the SearchForm and fixing there but it seems better to never call it if we know we don't have an actual query. 
can i work on this issue as a starter task? @lb-  😅 
Go for it. Nothing stopping you or anyone picking up a bug to fix. @ParitoshKabra 

I would recommend for something like this you start with the unit test, sometimes finding where to add a test can be tricky and PRs must come with a test to ensure won't break this again in the future. 
I had a doubt @lb- , the image search which is there in the attached small video, is it intended to always search in all collections? That is, when we pick a collection and then perform a search, even then the search page gets refreshed and it searches against all collections. As a part of this issue, shall that also be changed?





https://user-images.githubusercontent.com/76113183/157580102-594d0159-6905-4987-bcb1-4fc3d3b12067.mov

 
Also one more thing, when we search for an image, we need to actually add its complete name, shouldn't it be some `startsWith` type of a thing?
Thanks for digging into this @ParitoshKabra 

1. The search losing the collection is a known issue - https://github.com/wagtail/wagtail/issues/4073 (feel free to investigate)
2. Search indexing by default is basic, it depends on the search backend used.  https://docs.wagtail.org/en/stable/topics/search/backends.html - I'm not very skilled in the search area though so maybe that could also be improved

There is also this issue that may be related. https://github.com/wagtail/wagtail/issues/3746

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
@@ -87,17 +87,6 @@ def get_context_data(self, **kwargs):
             .prefetch_renditions("max-165x165")
         )
 
-        # Search
-        query_string = None
-        if "q" in self.request.GET:
-            self.form = SearchForm(self.request.GET, placeholder=_("Search images"))
-            if self.form.is_valid():
-                query_string = self.form.cleaned_data["q"]
-
-                images = images.search(query_string)
-        else:
-            self.form = SearchForm(placeholder=_("Search images"))
-
         # Filter by collection
         self.current_collection = None
         collection_id = self.request.GET.get("collection_id")
@@ -116,6 +105,17 @@ def get_context_data(self, **kwargs):
             except (AttributeError):
                 self.current_tag = None
 
+        # Search
+        query_string = None
+        if "q" in self.request.GET:
+            self.form = SearchForm(self.request.GET, placeholder=_("Search images"))
+            if self.form.is_valid():
+                query_string = self.form.cleaned_data["q"]
+
+                images = images.search(query_string)
+        else:
+            self.form = SearchForm(placeholder=_("Search images"))
+
         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
@@ -44,6 +44,34 @@ def test_search(self):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.context["query_string"], "Hello")
 
+    def test_collection_query_search(self):
+        root_collection = Collection.get_first_root_node()
+        child_collection = [
+            root_collection.add_child(name="Baker Collection"),
+            root_collection.add_child(name="Other Collection"),
+        ]
+        title_list = ["Baker", "Other"]
+        answer_list = []
+        for i in range(10):
+            self.image = Image.objects.create(
+                title=f"{title_list[i%2]} {i}",
+                file=get_test_image_file(size=(1, 1)),
+                collection=child_collection[i % 2],
+            )
+            if i % 2 == 0:
+                answer_list.append(self.image)
+        response = self.get({"q": "Baker", "collection_id": child_collection[0].id})
+        status_code = response.status_code
+        query_string = response.context["query_string"]
+        response_list = response.context["images"].object_list
+        response_body = response.content.decode("utf-8")
+
+        self.assertEqual(status_code, 200)
+        self.assertEqual(query_string, "Baker")
+        self.assertCountEqual(answer_list, response_list)
+        for i in range(0, 10, 2):
+            self.assertIn("Baker %i" % i, response_body)
+
     def test_pagination(self):
         pages = ["0", "1", "-1", "9999", "Not a page"]
         for page in pages: