plonedexterity

Rename Dexterity object (id) after copy


It's simple to choose the object ID at creation time with INameChooser. But we also want to be able to choose the object ID after a clone (and avoid copy_of in object ID).

We tried several different solutions :

Every time, we get a traceback because we changed the ID "too soon". For example when using Plone API :

  File "/Users/laurent/.buildout/eggs/plone.api-2.0.0a1-py3.7.egg/plone/api/content.py", line 256, in copy
    return target[new_id]
  File "/Users/laurent/.buildout/eggs/plone.folder-3.0.3-py3.7.egg/plone/folder/ordered.py", line 241, in __getitem__
    raise KeyError(key)
KeyError: 'copy_of_87c7f9b7e7924d039b832d3796e7b5a3'

Or, with a Copy / Paste in the Plone instance :

  Module plone.app.content.browser.contents.paste, line 42, in __call__
  Module OFS.CopySupport, line 317, in manage_pasteObjects
  Module OFS.CopySupport, line 229, in _pasteObjects
  Module plone.folder.ordered, line 73, in _getOb
AttributeError: 'copy_of_a7ed3d678f2643bc990309cde61a6bc5'

It's logical, because the ID is stored before events notification / manage_afterClone call for later use.

Even defining a _get_id on containers cannot work to define the ID, because we don't have the object to get attributes from (and generate the ID).

But then, how could we achieve this in a clean way ? Please tell me there is a better solution than redefining _pasteObjects (OFS.CopySupport) !

Thank you for your inputs !


Solution

  • So unfortunately not...

    But you can access the original object from within _get_id.

    For example:

    from OFS.CopySupport import _cb_decode
    from plone import api
    
        ...
        def _get_id(self, id_):
            # copy_or_move => 0 means copy, 1 means move
            copy_or_move, path_segments = _cb_decode(self.REQUEST['__cp']
            source_path = '/'.join(path_segments[0])  # Imlement for loop for more than one copied obj.
            app = self.getPhysicalRoot()
    
            # access to source obj
            source_obj = app.restrictedTraverse(source_path)
            # Do whatever you need - probably using INameChooser
        ....
    

    To have a canonical way of patching this I use collective.monkeypatcher

    Once installed add the following in ZCML:

      <include package="collective.monkeypatcher" />
    
      <monkey:patch
          class="OFS.CopySupport.CopyContainer"
          original="_get_id"
          replacement="patches.patched_get_id"
          />
    

    Where patches.py is your module containing the new method patched_get_id, which replaces _get_id.

    I'm sorry I don't have better news for you, but this is how I solved a similar requirement.

    This code (patched _get_id) adds a counter at the end of a id if already there.

    
    def patched_get_id(self, id_)
        match = re.match('^(.*)-([\d]+)$', id_)
        if match:
            id_ = match.group(1)
            number = int(match.group(2))
        else:
            number = 1
        
        new_id = id_
        while new_id in self.objectIds():
            new_id = '{}-{}'.format(id_, number)
            number += 1
        return new_id