Repository: internetarchive/openlibrary

Issue:
List carousel does not work with Work IDs
<!-- What problem are we solving? What does the experience look like today? What are the symptoms? -->
Creating a collection using the list carousel method does not work if the list contains Works.
### Evidence / Screenshot (if possible)

### Relevant url?
https://openlibrary.org/collections/banned-challenged-in-schools?v=7

### Steps to Reproduce
<!-- What steps caused you to find the bug? -->
1. Create a list carousel using a list containing Works, for example, https://openlibrary.org/people/agent-lisa/lists/OL205302L
2. Do ...notice error

<!-- What actually happened after these steps? What did you expect to happen? -->
* Actual: 
```
ListCarousel failed with error:
the JSON object must be str, bytes or bytearray, not NoneType
```
* Expected: Same behaviour as one get using a list containing only editions.

### Details

- **Logged in (Y/N)?**
- **Browser type/version?**
- **Operating system?**
- **Environment (prod/dev/local)?** prod
<!-- If not sure, put prod -->

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->

### Related files
<!-- Files related to this issue; this is super useful for new contributors who might want to help! If you're not sure, leave this blank; a maintainer will add them. -->

### Stakeholders
<!-- @ tag stakeholders of this bug -->
@mekarpeles 


Discussion:
Right now we're using `editions`:
https://github.com/internetarchive/openlibrary/blob/a8e46e3af041ba824c32833165815c9af7cc35c4/openlibrary/plugins/openlibrary/lists.py#L330

instead of filtering seeds for type `edition` or `work`:
https://openlibrary.org/people/agent-lisa/lists/OL205302L/seeds.json
@b2pacific has offered to work on this.  I will update the assignees shortly.

Resolving patch:
diff --git a/openlibrary/core/lists/model.py b/openlibrary/core/lists/model.py
--- a/openlibrary/core/lists/model.py
+++ b/openlibrary/core/lists/model.py
@@ -97,6 +97,14 @@ def preview(self):
             "last_update": self.last_update and self.last_update.isoformat() or None,
         }
 
+    def get_book_keys(self, offset=0, limit=50):
+        offset = offset or 0
+        return list({
+            (seed.works[0].key if seed.works else seed.key)
+            for seed in self.seeds
+            if seed.key.startswith(('/books', '/works'))
+        })[offset : offset + limit]
+
     def get_editions(self, limit=50, offset=0, _raw=False):
         """Returns the editions objects belonged to this list ordered by last_modified.
 
diff --git a/openlibrary/plugins/worksearch/code.py b/openlibrary/plugins/worksearch/code.py
--- a/openlibrary/plugins/worksearch/code.py
+++ b/openlibrary/plugins/worksearch/code.py
@@ -17,10 +17,10 @@
 from infogami import config
 from infogami.utils import delegate, stats
 from infogami.utils.view import public, render, render_template, safeint
+from openlibrary.core import cache
 from openlibrary.core.lending import add_availability, get_availability_of_ocaids
 from openlibrary.core.models import Edition  # noqa: E402
 from openlibrary.plugins.inside.code import fulltext_search
-from openlibrary.plugins.openlibrary.lists import get_list_editions
 from openlibrary.plugins.openlibrary.processors import urlsafe
 from openlibrary.plugins.upstream.utils import urlencode
 from openlibrary.utils import escape_bracket
@@ -1192,20 +1192,32 @@ def random_author_search(limit=10):
     return search_results['response']
 
 
-def rewrite_list_editions_query(q, page, offset, limit):
+def rewrite_list_query(q, page, offset, limit):
     """Takes a solr query. If it doesn't contain a /lists/ key, then
     return the query, unchanged, exactly as it entered the
     function. If it does contain a lists key, then use the pagination
     information to fetch the right block of keys from the
-    lists_editions API and then feed these editions resulting work
+    lists_editions and lists_works API and then feed these editions resulting work
     keys into solr with the form key:(OL123W, OL234W). This way, we
     can use the solr API to fetch list works and render them in
     carousels in the right format.
     """
+    def cached_get_list_book_keys(key, offset, limit):
+        # make cacheable
+        if 'env' not in web.ctx:
+            delegate.fakeload()
+        lst = web.ctx.site.get(key)
+        return lst.get_book_keys(offset=offset, limit=limit)
+
     if '/lists/' in q:
-        editions = get_list_editions(q, offset=offset, limit=limit)
-        work_ids = [ed.get('works')[0]['key'] for ed in editions]
-        q = 'key:(' + ' OR '.join(work_ids) + ')'
+        # we're making an assumption that q is just a list key
+        book_keys = cache.memcache_memoize(
+            cached_get_list_book_keys,
+            "search.list_books_query",
+            timeout=5*60)(q, offset, limit)
+
+        q = f"key:({' OR '.join(book_keys)})"
+
         # We've applied the offset to fetching get_list_editions to
         # produce the right set of discrete work IDs. We don't want
         # it applied to paginate our resulting solr query.
@@ -1237,7 +1249,7 @@ def work_search(
         sort = process_sort(sort)
 
     # deal with special /lists/ key queries
-    query['q'], page, offset, limit = rewrite_list_editions_query(
+    query['q'], page, offset, limit = rewrite_list_query(
         query['q'], page, offset, limit
     )
     try:
@@ -1303,7 +1315,7 @@ def GET(self):
 
         # If the query is a /list/ key, create custom list_editions_query
         q = query.get('q', '')
-        query['q'], page, offset, limit = rewrite_list_editions_query(
+        query['q'], page, offset, limit = rewrite_list_query(
             q, page, offset, limit
         )
         response = work_search(


Test patch: