Repository: internetarchive/openlibrary

Issue:
Make work id and author id fields on book edit page smarter about what they accept
On the book editing page, the fields for setting the work for an edition and for setting the author of a work have autosuggest functionality — you can start typing a value to get a list of matching records. You can also type a work or author ID to get a specific record, but if you enter anything besides the bare ID, it doesn't work.

When doing librarian work, I often find myself wanting to enter a specific ID in these fields, especially if I've been making a lot of changes and autosuggest doesn't have the right values because SOLR hasn't caught up yet. This involves finding the item I want, clicking on it, carefully extracting the appropriate ID from the browser location bar, and pasting that into the editing field.

What I'd really love to be able to do is just right-click an author or work link somewhere, copy the URL, paste it into the editing field, and have the autosuggest be smart enough to figure out what I'm trying to do and suggest the right ID.


### Proposal & Constraints
I propose upgrading the work and author fields so that they can extract the appropriate ID from a complex string (like a URL), saving librarians a lot of fussy clicking and selecting IDs.

I don't see any downsides here. OLIDs are well designed so that they're easily distinguishable from other text. The likelihood of a false match is extremely small, and if it were to happen, the user still gets feedback about the value they're about to save, so they can make any necessary corrections.

### Stakeholders
@mheiman 





Discussion:

Resolving patch:
diff --git a/openlibrary/plugins/upstream/addbook.py b/openlibrary/plugins/upstream/addbook.py
--- a/openlibrary/plugins/upstream/addbook.py
+++ b/openlibrary/plugins/upstream/addbook.py
@@ -14,7 +14,7 @@
 
 from openlibrary.plugins.openlibrary.processors import urlsafe
 from openlibrary.plugins.worksearch.search import get_solr
-from openlibrary.utils import is_author_olid, is_work_olid
+from openlibrary.utils import find_author_olid_in_string, find_work_olid_in_string
 from openlibrary.i18n import gettext as _
 from openlibrary import accounts
 import logging
@@ -1033,11 +1033,11 @@ def GET(self):
 
         solr = get_solr()
 
+        # look for ID in query string here
         q = solr.escape(i.q).strip()
-        query_is_key = is_work_olid(q.upper())
-        if query_is_key:
-            # ensure uppercase; key is case sensitive in solr
-            solr_q = 'key:"/works/%s"' % q.upper()
+        embedded_olid = find_work_olid_in_string(q)
+        if embedded_olid:
+            solr_q = 'key:"/works/%s"' % embedded_olid
         else:
             solr_q = f'title:"{q}"^2 OR title:({q}*)'
 
@@ -1054,9 +1054,9 @@ def GET(self):
         # exclude fake works that actually have an edition key
         docs = [d for d in data['docs'] if d['key'][-1] == 'W']
 
-        if query_is_key and not docs:
+        if embedded_olid and not docs:
             # Grumble! Work not in solr yet. Create a dummy.
-            key = '/works/%s' % q.upper()
+            key = '/works/%s' % embedded_olid
             work = web.ctx.site.get(key)
             if work:
                 docs = [work.as_fake_solr_record()]
@@ -1081,10 +1081,9 @@ def GET(self):
         solr = get_solr()
 
         q = solr.escape(i.q).strip()
-        query_is_key = is_author_olid(q.upper())
-        if query_is_key:
-            # ensure uppercase; key is case sensitive in solr
-            solr_q = 'key:"/authors/%s"' % q.upper()
+        embedded_olid = find_author_olid_in_string(q)
+        if embedded_olid:
+            solr_q = 'key:"/authors/%s"' % embedded_olid
         else:
             prefix_q = q + "*"
             solr_q = f'name:({prefix_q}) OR alternate_names:({prefix_q})'
@@ -1099,9 +1098,9 @@ def GET(self):
         data = solr.select(solr_q, **params)
         docs = data['docs']
 
-        if query_is_key and not docs:
+        if embedded_olid and not docs:
             # Grumble! Must be a new author. Fetch from db, and build a "fake" solr resp
-            key = '/authors/%s' % q.upper()
+            key = '/authors/%s' % embedded_olid
             author = web.ctx.site.get(key)
             if author:
                 docs = [author.as_fake_solr_record()]
diff --git a/openlibrary/utils/__init__.py b/openlibrary/utils/__init__.py
--- a/openlibrary/utils/__init__.py
+++ b/openlibrary/utils/__init__.py
@@ -127,20 +127,32 @@ def dicthash(d):
         return d
 
 
-author_olid_re = re.compile(r'^OL\d+A$')
-
-
-def is_author_olid(s):
-    """Case sensitive check for strings like 'OL123A'."""
-    return bool(author_olid_re.match(s))
+author_olid_embedded_re = re.compile(r'OL\d+A', re.IGNORECASE)
 
+def find_author_olid_in_string(s):
+    """
+    >>> find_author_olid_in_string("ol123a")
+    'OL123A'
+    >>> find_author_olid_in_string("/authors/OL123A/edit")
+    'OL123A'
+    >>> find_author_olid_in_string("some random string")
+    """
+    found = re.search(author_olid_embedded_re, s)
+    return found and found.group(0).upper()
 
-work_olid_re = re.compile(r'^OL\d+W$')
 
+work_olid_embedded_re = re.compile(r'OL\d+W', re.IGNORECASE)
 
-def is_work_olid(s):
-    """Case sensitive check for strings like 'OL123W'."""
-    return bool(work_olid_re.match(s))
+def find_work_olid_in_string(s):
+    """
+    >>> find_work_olid_in_string("ol123w")
+    'OL123W'
+    >>> find_work_olid_in_string("/works/OL123W/Title_of_book")
+    'OL123W'
+    >>> find_work_olid_in_string("some random string")
+    """
+    found = re.search(work_olid_embedded_re, s)
+    return found and found.group(0).upper()
 
 
 def extract_numeric_id_from_olid(olid):


Test patch: