Repository: WordPress/openverse-api

Issue:
Mature filter is broken
## Description
<!-- Concisely describe the bug. Compare your experience with what you expected to happen. -->
<!-- For example: "I clicked the 'submit' button and instead of seeing a thank you message, I saw a blank page." -->
The `mature` flag on our API is currently failing.

Here's the stacktrace we're seeing

```python
AttributeError: 'NoneType' object has no attribute 'to_dict'
  File "django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "catalog/api/views/media_views.py", line 68, in list
    results, num_pages, num_results = perform_search(
  File "catalog/api/controllers/elasticsearch/search.py", line 166, in perform_search
    start, end = get_query_slice(
  File "catalog/api/controllers/elasticsearch/utils.py", line 88, in get_query_slice
    start_slice, end_slice = paginate_with_dead_link_mask(s, page_size, page)
  File "catalog/api/controllers/elasticsearch/utils.py", line 58, in paginate_with_dead_link_mask
    query_hash = get_query_hash(s)
  File "catalog/api/utils/dead_link_mask.py", line 21, in get_query_hash
    serialized_search_obj = s.to_dict()
  File "elasticsearch_dsl/search.py", line 658, in to_dict
    d["query"] = self.query.to_dict()
  File "elasticsearch_dsl/utils.py", line 382, in to_dict
    value = list(map(lambda x: x.to_dict(), value))
  File "elasticsearch_dsl/utils.py", line 382, in <lambda>
    value = list(map(lambda x: x.to_dict(), value))
```

## Reproduction
<!-- Provide detailed steps to reproduce the bug. -->
1. Visit http://localhost:8000/v1/images/?mature=true
2. Observe 500 error

## Additional context
<!-- Add any other context about the problem here; or delete the section entirely. -->
Sentry issue: https://sentry.io/share/issue/84aad5fd22354d20844f9fd5873958be/

## Resolution
<!-- Replace the [ ] with [x] to check the box. -->
- [ ] 🙋 I would be interested in resolving this bug.



Discussion:

Resolving patch:
diff --git a/api/catalog/api/controllers/elasticsearch/search.py b/api/catalog/api/controllers/elasticsearch/search.py
--- a/api/catalog/api/controllers/elasticsearch/search.py
+++ b/api/catalog/api/controllers/elasticsearch/search.py
@@ -9,6 +9,7 @@
 
 from elasticsearch.exceptions import RequestError
 from elasticsearch_dsl import Q, Search
+from elasticsearch_dsl.query import EMPTY_QUERY
 from elasticsearch_dsl.response import Hit
 
 from catalog.api.controllers.elasticsearch.utils import (
@@ -151,7 +152,7 @@ def perform_search(
         rank_queries = []
         for field, boost in feature_boost.items():
             rank_queries.append(Q("rank_feature", field=field, boost=boost))
-        s.query = Q("bool", must=s.query, should=rank_queries)
+        s.query = Q("bool", must=s.query or EMPTY_QUERY, should=rank_queries)
 
     # Use highlighting to determine which fields contribute to the selection of
     # top results.


Test patch: