Skip to content

Commit

Permalink
Am/paste async button (#613)
Browse files Browse the repository at this point in the history
* Add 'paste async' button to manage content

* Changes

* Add upgrade profile and sample blob solution

* Fix broken blobs in documents
  • Loading branch information
miknevinas authored Oct 11, 2024
1 parent 86a6758 commit 0366d3c
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 11 deletions.
69 changes: 68 additions & 1 deletion castle/cms/browser/content/fc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
from zope.event import notify
from zope.interface import implementer
from zope.lifecycleevent import ObjectModifiedEvent
from zope.component.hooks import getSite
from plone.app.contenttypes.interfaces import IDocument
from zope.annotation.interfaces import IAnnotations
from persistent.mapping import PersistentMapping
from plone.namedfile.file import NamedBlobImage
from ZODB.blob import FilesystemHelper
from pkg_resources import resource_filename
from shutil import copyfile
import os
from re import sub

from plone.app.content.browser.vocabulary import (
Expand Down Expand Up @@ -184,15 +193,73 @@ def get_options(self):

class PasteAsyncActionView(actions.ObjectPasteView):

def create_empty_blob(self, filename):
dirname = os.path.split(filename)[0]
if not os.path.isdir(dirname):
os.makedirs(dirname, 0o700)

source = resource_filename(
'castle.cms',
'static/images/placeholder.png',
)

try:
copyfile(source, filename)
except Exception as e:
logger.error("Error creating replacement blob: {}".format(e))

logger.info("Created missing blob for: %s", filename)


def __call__(self):
try:
paste_data = get_paste_data(self.request)
except CopyError:
return self.copy_error()

site = getSite()
for path in paste_data.get('paths'):
try:
obj = site.restrictedTraverse(path.strip('/'), None)
if obj is None:
logger.error("Object not found: '{}'".format(path))
return
except Exception as e:
logger.error("Error retrieving object: {}".format(e))
return

# Set up FilesystemHelper to handle broken blob references, if any
conn = self.context._p_jar
storage = conn._storage
fshelper = storage.fshelper
base_dir = fshelper.base_dir
zeofshelper = FilesystemHelper(base_dir)
broken_items = []

if IDocument.providedBy(obj):
annotations = IAnnotations(obj)
for key in annotations.keys():
data = annotations[key]

# PersistentMapping tiles contain the blobs we need
if isinstance(data, PersistentMapping):
for item in data.values():
if isinstance(item, dict) and isinstance(item.get('data'), NamedBlobImage):
blobfile = item.get('data')
blob = blobfile._blob
try:
# Check blob
blob._p_activate()
except Exception as e:
# Broken blob reference
broken_items.append('/'.join(obj.getPhysicalPath()))
filename = zeofshelper.getBlobFilename(blob._p_oid, blob._p_serial)
if not os.path.exists(filename):
self.create_empty_blob(filename)

tasks.paste_items.delay(
self.request.form['folder'], paste_data['op'],
paste_data['mdatas'])
paste_data['mdatas'], broken_items)

return self.do_redirect(
self.canonical_object_url,
Expand Down
15 changes: 15 additions & 0 deletions castle/cms/profiles/3017/registry/resources.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<registry xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plone">

<records prefix="plone.bundles/plone"
interface='Products.CMFPlone.interfaces.IBundleRegistry'>
<value key="last_compilation">2024-09-24 00:00:00</value>
</records>

<records prefix="plone.bundles/plone-logged-in"
interface='Products.CMFPlone.interfaces.IBundleRegistry'>
<value key="last_compilation">2024-09-24 00:00:00</value>
</records>

</registry>
2 changes: 1 addition & 1 deletion castle/cms/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>3016</version>
<version>3017</version>
<dependencies>
<dependency>profile-plone.app.querystring:default</dependency>
<dependency>profile-plone.app.mosaic:default</dependency>
Expand Down
Binary file added castle/cms/static/images/placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 26 additions & 8 deletions castle/cms/tasks/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def paste_error_handle(where, op, mdatas):


@retriable(on_retry_exhausted=paste_error_handle)
def _paste_items(where, op, mdatas):
def _paste_items(where, op, mdatas, broken_items):
logger.info('Copying a bunch of items')
portal = api.portal.get()
dest = portal.restrictedTraverse(str(where.lstrip('/')))
Expand Down Expand Up @@ -73,22 +73,40 @@ def _paste_items(where, op, mdatas):
if email:
name = user.getProperty('fullname') or user.getId()
try:
utils.send_email(
recipients=email,
subject="Paste Operation Finished(Site: %s)" % (
subject="Paste Operation Finished(Site: %s)" % (
api.portal.get_registry_record('plone.site_title')),
html="""
html="""
<p>Hi %s,</p>
<p>The site has finished pasting items into /%s folder.</p>""" % (
name, where.lstrip('/')))
name, where.lstrip('/'))
if len(broken_items) > 0:
html+= """
<br />
<p>However, we detected broken file references
during the copy process. These references
have been replaced with a blank placeholder image.
</p>
<br />
<p>Below are the pages containing these affected items:
</p>
<br />"""
for path in broken_items:
html +="""
<p>%s</p>""" % (path)
utils.send_email(
recipients=email,
subject=subject,
html=html
)

except Exception:
logger.warn('Could not send status email ', exc_info=True)


@task()
def paste_items(where, op, mdatas):
_paste_items(where, op, mdatas)
def paste_items(where, op, mdatas, broken_items=[]):
_paste_items(where, op, mdatas, broken_items)


def delete_error_handle(where, op, mdatas):
Expand Down
16 changes: 16 additions & 0 deletions castle/cms/upgrades.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,20 @@
profile="castle.cms:default"
/>

<genericsetup:registerProfile
name="3017"
title="CastleCMS upgrade to 3017 profile"
directory="profiles/3017"
description=""
provides="Products.GenericSetup.interfaces.EXTENSION"
/>
<genericsetup:upgradeStep
title="Upgrade CastleCMS to 3.0.17"
description="3017 - Recompile resources for paste async button"
source="*"
destination="3017"
handler=".upgrades.upgrade_3017"
profile="castle.cms:default"
/>

</configure>
1 change: 1 addition & 0 deletions castle/cms/upgrades/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ def upgrade_3011(site, logger=CASTLE_LOGGER):
upgrade_3014 = default_upgrade_factory('3014')
upgrade_3015 = default_upgrade_factory('3015')
upgrade_3016 = default_upgrade_factory('3016')
upgrade_3017 = default_upgrade_factory('3017')
1 change: 0 additions & 1 deletion versions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ castle.theme = 1.0.6
Products.PloneKeywordManager = 2.2.1
wildcard.hps = 1.4.0


# dependency pins
argon2_cffi = 16.3.0
argon2-cffi = 16.3.0
Expand Down

0 comments on commit 0366d3c

Please sign in to comment.