Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
# -*- coding: utf-8 -*- | ||
from operator import itemgetter | ||
from plone import api | ||
from plone.app.textfield.interfaces import IRichText | ||
from plone.dexterity.interfaces import IDexterityContainer | ||
from plone.dexterity.interfaces import IDexterityFTI | ||
from plone.namedfile.interfaces import INamedFileField | ||
from plone.namedfile.interfaces import INamedImageField | ||
from plone.restapi.interfaces import IJsonCompatible | ||
from plone.restapi.interfaces import ISerializeToJson | ||
from plone.restapi.serializer.converters import json_compatible | ||
from plone.restapi.serializer.dxfields import DefaultFieldSerializer | ||
from Products.Five import BrowserView | ||
from z3c.relationfield.interfaces import IRelationValue | ||
from zope.component import adapter | ||
from zope.component import getMultiAdapter | ||
from zope.i18n import translate | ||
from zope.interface import alsoProvides | ||
from zope.interface import implementer | ||
from zope.interface import Interface | ||
from zope.interface import noLongerProvides | ||
|
||
import base64 | ||
import json | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class ExportRestapi(BrowserView): | ||
|
||
QUERY = {} | ||
DROP_PATHS = [] | ||
|
||
def __call__(self, portal_type=None, include_blobs=False): | ||
self.portal_type = portal_type | ||
if not self.request.form.get('form.submitted', False) or not self.portal_type: | ||
return self.index() | ||
|
||
data = self.export_content(include_blobs=include_blobs) | ||
number = len(data) | ||
msg = u'Exported {} {}'.format(number, self.portal_type) | ||
logger.info(msg) | ||
data = json.dumps(data, sort_keys=True, indent=4) | ||
response = self.request.response | ||
response.setHeader('content-type', 'application/json') | ||
response.setHeader('content-length', len(data)) | ||
response.setHeader( | ||
'content-disposition', | ||
'attachment; filename="{0}.json"'.format(self.portal_type)) | ||
return response.write(data) | ||
|
||
def export_content(self, include_blobs=False): | ||
data = [] | ||
query = {'portal_type': self.portal_type, 'Language': 'all'} | ||
# custom setting per type | ||
query.update(self.QUERY.get(self.portal_type, {})) | ||
brains = api.content.find(**query) | ||
logger.info(u'Exporting {} {}'.format(len(brains), self.portal_type)) | ||
|
||
if not include_blobs: | ||
# remove browserlayer to skip finding the custom serializer | ||
noLongerProvides(self.request, IThemeSpecific) | ||
|
||
for index, brain in enumerate(brains, start=1): | ||
skip = False | ||
for drop in self.DROP_PATHS: | ||
if drop in brain.getPath(): | ||
skip = True | ||
|
||
if skip: | ||
continue | ||
|
||
if not index % 100: | ||
logger.info(u'Handled {} items...'.format(index)) | ||
obj = brain.getObject() | ||
try: | ||
serializer = getMultiAdapter((obj, self.request), ISerializeToJson) | ||
item = serializer() | ||
data.append(item) | ||
except Exception as e: | ||
logger.info(e) | ||
|
||
if not include_blobs: | ||
# restore browserlayer | ||
alsoProvides(self.request, IThemeSpecific) | ||
|
||
return data | ||
|
||
def portal_types(self): | ||
"""A list with info on all content types with existing items. | ||
""" | ||
catalog = api.portal.get_tool('portal_catalog') | ||
portal_types = api.portal.get_tool('portal_types') | ||
results = [] | ||
for fti in portal_types.listTypeInfo(): | ||
if not IDexterityFTI.providedBy(fti): | ||
continue | ||
number = len(catalog(portal_type=fti.id, Language='all')) | ||
if number >= 1: | ||
results.append({ | ||
'number': number, | ||
'value': fti.id, | ||
'title': translate( | ||
fti.title, domain='plone', context=self.request) | ||
}) | ||
return sorted(results, key=itemgetter('title')) | ||
|
||
|
||
# make sure the adapter is more specific than the default from restapi | ||
@adapter(INamedImageField, IDexterityContainer, Interface) | ||
class ImageFieldSerializerWithBlobs(DefaultFieldSerializer): | ||
def __call__(self): | ||
image = self.field.get(self.context) | ||
if not image: | ||
return None | ||
result = { | ||
"filename": image.filename, | ||
"content-type": image.contentType, | ||
"data": base64.b64encode(image.data), | ||
"encoding": "base64", | ||
} | ||
return json_compatible(result) | ||
|
||
|
||
@adapter(INamedFileField, IDexterityContainer, Interface) | ||
class FileFieldSerializerWithBlobs(DefaultFieldSerializer): | ||
def __call__(self): | ||
namedfile = self.field.get(self.context) | ||
if namedfile is None: | ||
return None | ||
|
||
result = { | ||
"filename": namedfile.filename, | ||
"content-type": namedfile.contentType, | ||
"content-type": base64.b64encode(namedfile.data), | ||
"encoding": "base64", | ||
} | ||
return json_compatible(result) | ||
|
||
|
||
@adapter(IRichText, IDexterityContainer, Interface) | ||
class RichttextFieldSerializerWithRawText(DefaultFieldSerializer): | ||
def __call__(self): | ||
value = self.get_value() | ||
if value: | ||
output = value.raw | ||
return { | ||
u"data": json_compatible(output), | ||
u"content-type": json_compatible(value.mimeType), | ||
u"encoding": json_compatible(value.encoding), | ||
} | ||
|
||
|
||
@adapter(IRelationValue) | ||
@implementer(IJsonCompatible) | ||
def relationvalue_converter_uuid(value): | ||
if value.to_object: | ||
return value.to_object.UID() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<html xmlns="http://www.w3.org/1999/xhtml" | ||
xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
i18n:domain="plone.z3cform" | ||
metal:use-macro="context/main_template/macros/master"> | ||
|
||
<div metal:fill-slot="main"> | ||
<tal:main-macro metal:define-macro="main"> | ||
|
||
<h1 class="documentFirstHeading">Export content using plone.restapi</h1> | ||
|
||
<p class="documentDescription">Export all instances of one content types as a json file.</p> | ||
|
||
<form action="@@export_restapi" method="post" enctype="multipart/form-data"> | ||
<div class="field"> | ||
<label for="portal_type"> | ||
<span i18n:translate="">Content Type to export</span> | ||
</label> | ||
<select id="portal_type" | ||
name="portal_type"> | ||
<option selected="" value="" title="" i18n:translate="">Choose one</option> | ||
<option tal:repeat="ptype view/portal_types" | ||
tal:content="string:${ptype/title} - ${ptype/value} (${ptype/number})" | ||
tal:attributes="value ptype/value; title ptype/title;"> | ||
</option> | ||
</select> | ||
</div> | ||
|
||
<div class="field"> | ||
<label> | ||
<input | ||
type="checkbox" | ||
name="include_blobs:boolean" | ||
id="include_blobs" | ||
value="0" | ||
/> | ||
Include images and files as base-64 encoded strings? | ||
</label> | ||
</div> | ||
|
||
<div class="formControls" class="form-group"> | ||
<input type="hidden" name="form.submitted" value="1"/> | ||
<button class="btn btn-primary submit-widget button-field context" | ||
type="submit" name="submit" value="export">Export | ||
</button> | ||
</div> | ||
</form> | ||
|
||
</tal:main-macro> | ||
</div> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<configure | ||
xmlns="http://namespaces.zope.org/zope" | ||
xmlns:zcml="http://namespaces.zope.org/zcml"> | ||
|
||
<configure zcml:condition="installed z3c.relationfield"> | ||
<adapter factory=".export.relationvalue_converter_uuid" /> | ||
</configure> | ||
|
||
</configure> |