Plone 5.2 - Store a title from an object obtained from uuidToObject as metadata?

I have a vocabulary source that gets a list of simple terms comprised of UUID/Title pairs.
Based on plone documentation that shows dynamic vocabularies, the code I have is:

@provider(IContextSourceBinder)
def organizations(context):

    root = context.portal_url.getPortalObject()
    portal_catalog = root.portal_catalog
    brains = portal_catalog.searchResults(portal_type='Organization')
    result = [ (brain["UID"], brain["Title"]) for brain in brains]
    terms = make_terms(result)

    return SimpleVocabulary(terms)

And then I have a content type, MyPhone, that has uses the source in a choice field.
In the interface I have:

class IMyPhone(model.Schema):

    form.widget('manufacturer',SelectFieldWidget)
    manufacturer = schema.Choice(
        title=u"Manufacturer",
        source=organizations,
        required=False,
        )

I want to store the title of the manufacturer in a metadata column, so in my catalog.xml, I have:

<column value="Manufacturer"/>

I don't know how to store this. I figured maybe I could do something like:

from plone.uuid.utils import uuidToObject
...

@implementer(IMyPhone)
class MyPhone(Container):

    def Manufacturer(self):
        return uuidToObject(self.manufacturer).title

Note: Manufacturer is capitalized, along the lines of the title field in metadata being capitalized as 'Title'

This appears to work at first. I go into the ZMI, goto portal_catalog, and go to the MyPhone, and the 'Manufacturer' metadata column is filled in. Unfortunately, it breaks when I try to rebuild the catalog. It duplicates indexes.

2020-02-20 09:37:25,688 ERROR   [Products.ZCatalog:99][waitress] A different 
document with value '2409cca287ab4a20bc17240bdae48d84' already exists in the index.

Its duplicating all the Organization indexes. I tried a print statement in the vocabulary source:

   @provider(IContextSourceBinder)
   def organizations(context):
       ...
       brains = portal_catalog.searchResults(portal_type='Organization')
       result = [ (brain["UID"], brain["Title"]) for brain in brains]
       for k,v in result:
           print(k,v)
    ...

It was printing the organizations twice like this:

bad4dbf922a844ba84728a401bce0e98 AT&T
2409cca287ab4a20bc17240bdae48d84 Verizon
bad4dbf922a844ba84728a401bce0e98 AT&T
2409cca287ab4a20bc17240bdae48d84 Verizon

And because of duplicates, the vocabulary breaks.

I'm assuming its because of the uuidToObject querying the portal_catalog. Is there any way I could just get the title of the Organization chosen as the Manufacturer to store in the metadata?
The only thing I could think of right now is include a hidden textline 'Manufacturer' in IMyPhone that updates during certain events.

I found something interesting. When trying the hidden textline substitute, I found a moment where I can access the portal catalog,

def updatePhone(obj, event):

    for d in event.descriptions:
        if len(d.attributes) == 0:
            break  #nothing was changed, this runs multiple times
    else:
        catalog = api.portal.get_tool(name='portal_catalog')
        obj.Manufacturer = catalog.searchResults({'UID':obj.manufacturer})[0]['Title']
        obj.reindexObject(idxs=['Manufacturer'])

But I guess that's different since Manufacturer in that case of it being a hidden textline field is directly part of the schema. Is there a way I could manipulate this?

Edit: Also, it turned out it was duplicating the Phone indexes.

Use a custom indexer
https://docs.plone.org/external/plone.app.dexterity/docs/advanced/catalog-indexing-strategies.html#creating-custom-indexers

Edit: I'm not sure how you would get a UUID there and not the IMyPhone object itself. Use a more sensible naming scheme for your functions, variables and objects.

If your manufacturer would be stored as a relation value, you could pass the phone object to the indexer. Something like:

@indexer(ICustomSearch)
def getManufacturerTitle(obj):
    mfg_relation = obj.manufacturer
    mfg_obj = getattr(mfg_relation, 'to_object', None)
    if mfg_obj:
        return mfg_obj.Title()

in configure.zcml

  <adapter name="phone_manufacturer"
        factory=".indexers.getManufacturerTitle"
        />

in catalog.xml

 <index name="phone_manufacturer" meta_type="FieldIndex">
  <indexed_attr value="phone_manufacturer"/>
 </index>

 <column value="phone_manufacturer"/>

in any case, the title of an object is obj.Title() no matter how you got that object :wink:

1 Like

Thank you for your response.
I'm trying to avoid using relations and stick to using uid's.

I'd love to be able to use an indexer to do something similar to the indexer you provided, like:

@indexer(IMyPhone):
def getManufacturerTitle(obj):
    catalog = api.portal.get_tool(name='portal_catalog')
    return catalog.searchResults({'UID':obj.manufacturer})[0]['Title']

This works at first, until I try to rebuild the catalog, then it just creates a duplicate phone index.
I'll perhaps think about using relations again.
Also, thank you for the pointer on naming.

*Just a quick note, I didn't check for None in the indexer because Manufacturer is a required field.

Why ?

Clear and rebuild TTW ?

Plone Foundation Code of Conduct