Plone images, names before and after storage

I have code that interfaces with the restapi and tries to pull down images based on the name of the image in a database, which corresponds to the filename at the time of upload.

The name of that image within plone's schema is the_judges_lodgings_-_the_courtroom_-geograph_6932854.jpg

The first bracket is replaced by a -, the second is just removed. But all I have is the name before the upload.

Is there a way, with the rest api, to say "get me the image whose canonical, unchanged name, is "? The name exists, I can see it there. But the title of the image isn't right.

I've been trying to use the examples in Serialization — plone.restapi v8.24.2.dev0 and ended up with

def get_image_by_title(title):
    file = f"{urlbase}images/{title}"
    r = requests.get(
        file,
        headers={'Accept': jsontype, 'Content-Type': jsontype},
        auth=(user['username'], user['password']),
        verify=False)
    return r

This, obviously, isn't working for strangely named files.

The filename is under image/filename:

response = requests.get(
    "https://6-classic.demo.plone.org/en/the_judges_lodgings_-_the_courtroom_geograph_6932854.jpg",
    headers=headers, auth=auth)

pprint.pprint(response.json()["image"]["filename"])

# -> "The_Judge's_Lodgings_-_the_Courtroom_(geograph_6932854).jpg"

You could also use the parameter ?metadata_fields=image_scales and request your images folder. Your function could look something like:

def get_image_by_title(filename):
    response = requests.get(
                  f"{urlbase}images?metadata_fields=image_scales",
                  headers=headers, auth=auth)
    response_json = response.json()
    item_at_id = None
    for item in response_json["items"]:
        if item["@type"] == "Image" and "image_scales" in item:
            if "image" in item["image_scales"]:
                for i in item["image_scales"]["image"]:
                    if i["filename"] == filename:
                        # item_at_id = item["@id"]
                        # return item_at_id
                        id = item["id"]
                        return id


id = get_image_by_title("The_Judge's_Lodgings_-_the_Courtroom_geograph_6932854).jpg")
print(id)
# -> the_judges_lodgings_-_the_courtroom_geograph_6932854.jpg

# or with item_at_id
# -> https://your.domain.org/images/the_judges_lodgings_-_the_courtroom_geograph_6932854.jpg

Otherwise (if you need this often) you could add an index in portal_catalog and use a query. See Querystring Search and Searching and indexing.

The Querystring search seems to be the right approach, as I have the title but not what Plone calls it.
I run into a problem when there's brackets in the title.

def search_for_image_by_title(title):
    r = requests.post(
        f'{urlbase}@querystring-search',
        headers={'Accept': jsontype, 'Content-Type': jsontype},
        json={
            'query':[
                {
                    "i": "portal_type",
                    "o": "plone.app.querystring.operation.selection.any",
                    "v": ["Image"]
                },
                {
                    "i": "Title",
                    'o': 'plone.app.querystring.operation.string.is',
                    "v": urllib.parse.quote(title)
                }
            ]
        },
        auth=(user['username'], user['password'])
    )
    return r

If I run it with urllib.parse.quote, I get an empty search result. Which is about what I'd expect when there's no file matching the title. But the title is copied and pasted from within plone itself.

If I run it without the urllib.parse.quote, I get an exception in the response.

 'traceback': ['File '
               '"/opt/plone/buildout-cache/eggs/cp38/Zope-4.8.2-py3.8.egg/ZPublisher/WSGIPublisher.py", '
               'line 162, in transaction_pubevents',
               '    yield',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Zope-4.8.2-py3.8.egg/ZPublisher/WSGIPublisher.py", '
               'line 371, in publish_module',
               '    response = _publish(request, new_mod_info)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Zope-4.8.2-py3.8.egg/ZPublisher/WSGIPublisher.py", '
               'line 266, in publish',
               '    result = mapply(obj,',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Zope-4.8.2-py3.8.egg/ZPublisher/mapply.py", '
               'line 85, in mapply',
               '    return debug(object, args, context)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Zope-4.8.2-py3.8.egg/ZPublisher/WSGIPublisher.py", '
               'line 63, in call_object',
               '    return obj(*args)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/plone.rest-1.6.2-py3.8.egg/plone/rest/service.py", '
               'line 22, in __call__',
               '    return self.render()',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/plone.restapi-7.8.0-py3.8.egg/plone/restapi/services/__init__.py", '
               'line 20, in render',
               '    content = self.reply()',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/plone.restapi-7.8.0-py3.8.egg/plone/restapi/services/querystringsearch/get.py", '
               'line 57, in reply',
               '    results = querybuilder(**querybuilder_parameters)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/plone.app.querystring-1.6.1-py3.8.egg/plone/app/querystring/querybuilder.py", '
               'line 111, in __call__',
               '    self._results = self._makequery(',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/plone.app.querystring-1.6.1-py3.8.egg/plone/app/querystring/querybuilder.py", '
               'line 213, in _makequery',
               '    results = catalog(**parsedquery)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.CMFPlone-5.2.9-py3.8.egg/Products/CMFPlone/CatalogTool.py", '
               'line 468, in searchResults',
               '    return ZCatalog.searchResults(self, query, **kw)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCatalog/ZCatalog.py", '
               'line 627, in searchResults',
               '    return self._catalog.searchResults(query, **kw)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCatalog/Catalog.py", '
               'line 1094, in searchResults',
               '    return self.search(query, sort_indexes, reverse, '
               'sort_limit, _merge)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCatalog/Catalog.py", '
               'line 637, in search',
               '    rs = self._search_index(cr, index_id, query, rs)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCatalog/Catalog.py", '
               'line 567, in _search_index',
               '    index_rs = index.query_index(index_query, rs)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCTextIndex/ZCTextIndex.py", '
               'line 209, in query_index',
               '    tree = '
               'QueryParser(self.getLexicon()).parseQuery(query_str)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCTextIndex/QueryParser.py", '
               'line 155, in parseQuery',
               '    self._require(_EOF)',
               '',
               '  File '
               '"/opt/plone/buildout-cache/eggs/cp38/Products.ZCatalog-5.4-py3.8.egg/Products/ZCTextIndex/QueryParser.py", '
               'line 175, in _require',
               '    raise ParseTree.ParseError(msg)'],
 'type': 'ParseError'}```

Like, this is the result of the matching field in an all images search

{'@id': '{ulrbase}images/the_judges_lodgings_-_the_courtroom_-geograph_6932854.jpg',
'@type': 'Image',
'description': '',
'review_state': None,
'title': "The_Judge's_Lodgings_-_the_Courtroom_(geograph_6932854).jpg"},

Having further investigated, the title search fails as soon as it hits the s after the apostrophe.
"The_Judge*" works fine and returns the result as expected. "The_Judge'*" works. 'The_Judge's' does not. Is there something fundamental abou thow the query string fields are expected I am missing?