Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows versioning of the Plone Site type for the portal working copy to work #113

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

wesleybl
Copy link
Member

@wesleybl wesleybl commented Oct 2, 2024

This is part of: plone/volto#5284

For the working copy of plone.app.iterate to work on the Plone Site type, it is necessary that this type is versionable. See:

https://github.com/plone/plone.app.iterate/blob/1908aa1f4e9c95c37143fae88655469b72ea451a/plone/app/iterate/browser/control.py#L88-L90

This also allows the "Plone Site" type to be cloned.

Products.CMFEditions uses the pickle module to clone objects. See:

def _cloneByPickle(self, obj):
"""Returns a deep copy of a ZODB object, loading ghosts as needed."""
modifier = getToolByName(self, "portal_modifier")
callbacks = modifier.getOnCloneModifiers(obj)
if callbacks is not None:
pers_id, pers_load, inside_orefs, outside_orefs = callbacks[0:4]
else:
inside_orefs, outside_orefs = (), ()
stream = BytesIO()
p = Pickler(stream, 1)
if callbacks is not None:
p.persistent_id = pers_id
p.dump(aq_base(obj))
approxSize = stream.tell()
stream.seek(0)
u = Unpickler(stream)
if callbacks is not None:
u.persistent_load = pers_load
return approxSize, u.load(), inside_orefs, outside_orefs

These changes prevent the error:

TypeError: Can't pickle objects in acquisition wrappers

When trying to serialize an ImplicitAcquisitionWrapper object with the pickle dump. This error occurred when trying to check in a Plone Site type with plone.app.iterate.

These changes also allow the Plone Site type to be serialized with the pickle. When the persistent_id method of SkipParentPointers returns something other than None, the object is not serialized. So we need to make sure it returns None for the Plone Site type.

@mister-roboto
Copy link

@wesleybl thanks for creating this Pull Request and helping to improve Plone!

TL;DR: Finish pushing changes, pass all other checks, then paste a comment:

@jenkins-plone-org please run jobs

To ensure that these changes do not break other parts of Plone, the Plone test suite matrix needs to pass, but it takes 30-60 min. Other CI checks are usually much faster and the Plone Jenkins resources are limited, so when done pushing changes and all other checks pass either start all Jenkins PR jobs yourself, or simply add the comment above in this PR to start all the jobs automatically.

Happy hacking!

@wesleybl
Copy link
Member Author

wesleybl commented Oct 2, 2024

@jenkins-plone-org please run jobs

@wesleybl
Copy link
Member Author

wesleybl commented Oct 3, 2024

I ran the tests for this PR along with plone/plone.app.iterate#128

See: Plone Jenkins CI - pull-request-6.0-3.11 and Plone Jenkins CI - pull-request-6.0-3.8

Comment on lines +29 to +32
<type name="Plone Site">
<policy name="at_edit_autoversion" />
<policy name="version_on_revert" />
</type>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether I should do an upgrade step for this or just leave it for new portals.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it just for new portals, but we should mention that in the release note.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the text Allows versioning of the Plone Site type for the portal working copy to work. is this not clear? Do you have any other suggestions?

Copy link
Member

@davisagli davisagli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wesleybl Sorry for my long delay on reviewing this...

hasattr(aq_base(obj), "portal_type")
and aq_base(obj).portal_type == "Plone Site"
):
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ImplicitAcquisitionWrapper probably comes from a child object, not the parent pointer. And I expect #117 will solve this so that we don't need this change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davisagli after the merge of #117, I rebased it with the master branch here. After that, the code:

  if isinstance(obj, ImplicitAcquisitionWrapper):
      return True

is no longer needed. Thanks!

But the code:

  if (
      hasattr(aq_base(obj), "portal_type")
      and aq_base(obj).portal_type == "Plone Site"
  ):
      return

is still needed. Without it, I get the error below when trying to checkout in Volto:

2024-12-17 18:20:39,118 ERROR   [Zope.SiteErrorLog:36][waitress-2] AttributeError: http://localhost:3000/@workingcopy
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 390, in publish_module
  Module ZPublisher.WSGIPublisher, line 284, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module ZPublisher.WSGIPublisher, line 68, in call_object
  Module plone.rest.service, line 21, in __call__
  Module plone.restapi.services, line 19, in render
  Module plone.restapi.services.workingcopy.create, line 44, in reply
  Module plone.app.iterate.base, line 64, in checkout
  Module zope.event, line 33, in notify
  Module zope.component.event, line 27, in dispatch
  Module zope.component._api, line 146, in subscribers
  Module zope.interface.registry, line 458, in subscribers
  Module zope.interface.adapter, line 918, in subscribers
  Module plone.app.iterate.subscribers.versioning, line 28, in handleBeforeCheckout
  Module plone.app.iterate.archiver, line 36, in save
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 300, in save
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 455, in _recursiveSave
  Module Products.CMFEditions.ArchivistTool, line 268, in prepare
  Module Products.CMFEditions.ModifierRegistryTool, line 217, in beforeSaveModifier
  Module Products.CMFEditions.StandardModifiers, line 392, in beforeSaveModifier
  Module Products.CMFEditions.StandardModifiers, line 358, in _beforeSaveModifier
AttributeError: 'NoneType' object has no attribute 'objectIds'

The strange thing is that in Plone Classic the checkout works without it. So, it's something related to plone.restapi.

Copy link
Member Author

@wesleybl wesleybl Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm actually both ifs are needed yet. Without the first if, checkout works but checkin gives an error:

024-12-17 18:54:16,453 ERROR   [Zope.SiteErrorLog:36][waitress-2] TypeError: http://localhost:3000/working_copy_of_plone/@workingcopy
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 390, in publish_module
  Module ZPublisher.WSGIPublisher, line 284, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module ZPublisher.WSGIPublisher, line 68, in call_object
  Module plone.rest.service, line 21, in __call__
  Module plone.restapi.services, line 19, in render
  Module plone.restapi.services.workingcopy.update, line 30, in reply
  Module plone.app.iterate.dexterity.policy, line 36, in checkin
  Module zope.event, line 33, in notify
  Module zope.component.event, line 27, in dispatch
  Module zope.component._api, line 146, in subscribers
  Module zope.interface.registry, line 458, in subscribers
  Module zope.interface.adapter, line 918, in subscribers
  Module plone.app.iterate.subscribers.versioning, line 33, in handleAfterCheckin
  Module plone.app.iterate.archiver, line 36, in save
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 300, in save
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 455, in _recursiveSave
  Module Products.CMFEditions.ArchivistTool, line 267, in prepare
  Module Products.CMFEditions.ArchivistTool, line 223, in _cloneByPickle
TypeError: Can't pickle objects in acquisition wrappers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ( hasattr(aq_base(obj), "portal_type") and aq_base(obj).portal_type == "Plone Site" ):
    return

prevents including references to the portal when an object is serialized while cloning. I need to understand better where that reference is in order to see if we want to remove it or restore it, but I haven't been successful at debugging this yet.

Also if we do need special logic to handle this, this isn't the right place for it, because it's not (I think) about parent pointers.

Copy link
Member

@davisagli davisagli Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding my notes since I need to stop for the day:

  • the problem seems to be with the _components attribute of the portal (the site manager)
  • it's not always a problem... if I restart the instance in between creating the working copy and checking it in, then it succeeds
  • I suspect we need a new modifier to prevent including the site manager when storing or retrieving versions of the portal (it's quite surprising/broken if you restore an old version and it also resets the site manager to an old set of components!)
  • but I think this is somewhat orthogonal to working copy support. plone.app.iterate currently always tries to create a new revision after a checkin, even if the content type doesn't have versioning enabled. That's silly! We should fix https://github.com/plone/plone.app.iterate/blob/master/plone/app/iterate/subscribers/versioning.py#L31 so that it only calls archiver.save if archiver.isVersionable() is true. Then I think we won't need to enable versioning for the Plone Site type in order to support working copies.
  • There's a separate bug in the SkipParentPointers modifier. It needs to call aq_inner(obj) before getting the obj's parent, to make sure it's not an object aq-wrapped in itself! This is what led to the AttributeError: 'NoneType' object has no attribute 'objectIds' error

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I think this is somewhat orthogonal to working copy support. plone.app.iterate currently always tries to create a new revision after a checkin, even if the content type doesn't have versioning enabled. That's silly! We should fix https://github.com/plone/plone.app.iterate/blob/master/plone/app/iterate/subscribers/versioning.py#L31 so that it only calls archiver.save if archiver.isVersionable() is true. Then I think we won't need to enable versioning for the Plone Site type in order to support working copies.

@davisagli I'm not remembering exactly now but I think I've come to the conclusion that archiver.isVersionable() will be true only if versioning is enabled for the content type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem seems to be with the _components attribute of the portal (the site manager)

Plone Site has several attributes of type ImplicitAcquisitionWrapper. Not only _components.

Comment on lines +29 to +32
<type name="Plone Site">
<policy name="at_edit_autoversion" />
<policy name="version_on_revert" />
</type>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it just for new portals, but we should mention that in the release note.

@wesleybl wesleybl force-pushed the working_copy_portal branch from 965e383 to de7f30c Compare December 17, 2024 20:53
For the working copy of plone.app.iterate to work on the "Plone Site"
type, it is necessary that this type is versionable. See:

https://github.com/plone/plone.app.iterate/blob/1908aa1f4e9c95c37143fae88655469b72ea451a/plone/app/iterate/browser/control.py#L88-L90
Products.CMFEditions uses the pickle module to clone objects. See:

https://github.com/plone/Products.CMFEditions/blob/c80bc31af46bff45fee2908878b1f01190fda8d8/Products/CMFEditions/ArchivistTool.py#L210-L229

These changes prevent the error:

TypeError: Can't pickle objects in acquisition wrappers

When trying to serialize an ImplicitAcquisitionWrapper object with the
pickle dump. This error occurred when trying to check in a Plone Site
type with plone.app.iterate.

These changes also allow the Plone Site type to be serialized with the
pickle.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants