class IWebdavSettings(Interface): """ ExistDB settings """ webdav_url = schema.TextLine( title=_(u'WebDAV server url'), description=_(u'WebDAV base url'), default=u'http://localhost:6080/exist/webdav/db', required=True) webdav_dexterity_subpath = schema.TextLine( title=_(u'Dexterity WebDAV subpath'), description=_(u'Subpath inside WebDAV for Dexterity content'), default=u'', required=False) webdav_username = schema.TextLine(title=_(u'WebDAV username'), description=_(u'WebDAV username'), default=u'admin', required=True) webdav_password = schema.Password(title=_(u'WebDAV password'), description=_(u'WebDAV password'), default=u'', required=True) versioning_enabled = schema.Bool(title=_(u'Versioning enabled'), description=_(u'Versioning enabled'), default=False)
class IConcept(model.Schema): body = RichText( title=_(u'Concept content') ) xml_body = XMLText( title=_(u'XML Content'), required=False )
class IConnector(model.Schema): webdav_subpath = schema.TextLine( title=_(u'Subdirectory in Exist-DB'), description=_(u'Subdirectory in Exist-DB'), required=False) webdav_username = schema.TextLine( title=_(u'(optional) username overriding the system settings'), required=False) webdav_password = schema.TextLine( title=_(u'(optional) password overriding the system settings'), required=False) api_enabled = schema.Bool(title=_(u'Public web API enabled'), default=False, required=False) default_view_anonymous = schema.TextLine( title=_(u'Default view (anonymous)'), description=_( u'Name of a default view for site visitors without edit permission' ), required=False, default=None, ) default_view_authenticated = schema.TextLine( title=_(u'Default view (authenticated)'), description=_(u'Name of a default view for anonymous site visitors'), required=False, default=u'@@view', )
class DBSettingsEditForm(controlpanel.RegistryEditForm): schema = IWebdavSettings label = _(u'XML Director core settings') description = _(u'') def updateFields(self): super(DBSettingsEditForm, self).updateFields() def updateWidgets(self): super(DBSettingsEditForm, self).updateWidgets()
class IXMLDocument(model.Schema): xml_content = XMLText(title=_(u'XML Content'), required=False) xml_xpath = XMLXPath( title=_(u'XML XPath expression'), description=_(u'Format: field=<fieldname>,xpath=<xpath expression>'), required=False) xml_binary = XMLBinary(title=_(u'XML Binary'), required=False) xml_image = XMLImage(title=_(u'XML Image'), required=False)
def __call__(self, *args, **kw): user = getSecurityManager().getUser() # request/force_default_view can be used to force a redirect to the # default anonymous view force_default_view = self.request.form.get('force_default_view', 0) if force_default_view: if user.has_permission(permissions.View, self.context): default_view = self.context.default_view_anonymous return self.request.response.redirect( '{}/{}'.format(self.context.absolute_url(), default_view)) else: LOG.error(u'Unable to redirect to anonymous default view of ({}, {})'.format( user.getUserName(), self.context.absolute_url(1))) raise zExceptions.NotFound() if user.has_permission(permissions.ModifyPortalContent, self.context): default_view = self.context.default_view_authenticated return self.request.response.redirect( '{}/{}'.format(self.context.absolute_url(), default_view)) else: default_view = self.context.default_view_anonymous if default_view: return self.request.response.redirect( '{}/{}'.format(self.context.absolute_url(), default_view)) else: msg = _(u'No default view configured for anonymous visitors') self.context.plone_utils.addPortalMessage(msg, 'error') raise zExceptions.NotFound()
def clear_contents(self): """ Remove all sub content """ handle = self.webdav_handle() for name in handle.listdir(): if handle.isfile(name): handle.remove(name) else: handle.removedir(name, force=True, recursive=False) return self.redirect(_(u'eXist-db collection cleared'))
def zip_import_ui(self, zip_file=None, subpath=None, clean_directories=None): """ Import WebDAV subfolder from an uploaded ZIP file """ try: imported_files = self.zip_import( zip_file, subpath, clean_directories) except Exception as e: msg = u'ZIP import failed' LOG.error(msg, exc_info=True) return self.redirect(msg, 'error') self.logger.log( 'ZIP file imported ({}, {} files)'.format(zip_file, len(imported_files)), details=imported_files) return self.redirect(_(u'Uploaded ZIP archive imported'), subpath=subpath)
class IConnectorSettings(Interface): """ Connector settings """ connector_url = schema.TextLine( title=_(u'Connection URL of storage service'), description=_(u'WebDAV: http://host:port/path/to/webdav,' 'Local filesystem: file://path/to/directory, ' 'AWS S3: s3://bucketname, SFTP sftp://host/path, ' 'FTP: ftp://host/path'), default=u'http://localhost:6080/exist/webdav/db', required=True) connector_dexterity_subpath = schema.TextLine( title=_(u'Dexterity subpath'), description=_(u'Subpath inside storage for Dexterity content'), default=u'', required=False) connector_mode = schema.Choice( title=_(u'Connector mode'), description=_(u'Connector mode (defaults to Exist-DB)'), default=u'existdb', required=True, vocabulary=CONNECTOR_MODE_VOCAB) connector_username = schema.TextLine( title=_(u'Username for external storage'), description=_(u'Username'), default=u'admin', required=True) connector_password = schema.Password( title=_(u'Password for external storage'), description=_(u'Password'), default=u'', required=False)
def __call__(self, *args, **kw): user = getSecurityManager().getUser() if user.has_permission(permissions.ModifyPortalContent, self.context): default_view = self.context.default_view_authenticated return self.request.response.redirect( '{}/{}'.format(self.context.absolute_url(), default_view)) else: default_view = self.context.default_view_anonymous if default_view: return self.request.response.redirect( '{}/{}'.format(self.context.absolute_url(), default_view)) else: msg = _(u'No default view configured for anonymous visitors') self.context.plone_utils.addPortalMessage(msg, 'error') raise zExceptions.NotFound()
def create_collection(self, subpath, name): """ Create a new collection """ if not name: raise ValueError(_(u'No "name" given')) handle = self.webdav_handle(subpath) if handle.exists(name): msg = u'Collection already exists' self.context.plone_utils.addPortalMessage(msg, 'error') else: handle.makedir(name) msg = u'Collection created' self.logger.log('Created {} (subpath: {})'.format(name, subpath)) self.context.plone_utils.addPortalMessage(msg) return self.request.response.redirect( '{}/@@view/{}'.format(self.context.absolute_url(), subpath))
def rename_collection(self, subpath, name, new_name): """ Rename a collection """ if not new_name: raise ValueError(_(u'No new "name" given')) handle = self.webdav_handle(subpath) if handle.exists(name): handle.rename(name, new_name) msg = u'Collection renamed' self.logger.log( 'Renamed {} to {} (subpath: {})'.format(name, new_name, subpath)) self.context.plone_utils.addPortalMessage(msg) else: msg = u'Collection does not exist' self.context.plone_utils.addPortalMessage(msg, 'error') return self.request.response.redirect( '{}/@@view/{}'.format(self.context.absolute_url(), subpath))
def rename_collection(self, subpath, name, new_name): """ Rename a collection """ if not new_name: raise ValueError(_(u'No new "name" given')) handle = self.get_handle(subpath) if handle.exists(name): handle.rename(name, new_name) msg = u'Collection renamed' self.logger.log('Renamed {} to {} (subpath: {})'.format( name, new_name, subpath)) self.context.plone_utils.addPortalMessage(msg) else: msg = u'Collection does not exist' self.context.plone_utils.addPortalMessage(msg, 'error') return self.request.response.redirect('{}/@@view/{}'.format( self.context.absolute_url(), subpath))
def create_collection(self, subpath, name): """ Create a new collection """ if not name: raise ValueError(_(u'No "name" given')) handle = self.get_handle(subpath) # can_unicode = handle.getmeta('unicode_paths') if handle.exists(name): msg = u'Collection already exists' self.context.plone_utils.addPortalMessage(msg, 'error') else: handle.makedir(name) msg = u'Collection created' self.logger.log('Created {} (subpath: {})'.format(name, subpath)) self.context.plone_utils.addPortalMessage(msg) return self.request.response.redirect('{}/@@view/{}'.format( self.context.absolute_url(), subpath))
def zip_import_ui(self, zip_file=None, subpath=None, clean_directories=None): """ Import WebDAV subfolder from an uploaded ZIP file """ try: imported_files = self.zip_import(zip_file, subpath, clean_directories) except Exception as e: msg = u'ZIP import failed' LOG.error(msg, exc_info=True) return self.redirect(msg, 'error') self.logger.log('ZIP file imported ({}, {} files)'.format( zip_file, len(imported_files)), details=imported_files) return self.redirect(_(u'Uploaded ZIP archive imported'), subpath=subpath)
def __call__(self, *args, **kw): qs = self.request.QUERY_STRING user = getSecurityManager().getUser() # request/force_default_view can be used to force a redirect to the # default anonymous view force_default_view = self.request.form.get('force_default_view', 0) if force_default_view: if user.has_permission(permissions.View, self.context): default_view = self.context.default_view_anonymous if qs: default_view += '?' + qs return self.request.response.redirect('{}/{}'.format( self.context.absolute_url(), default_view)) else: LOG.error( u'Unable to redirect to anonymous default view of ({}, {})' .format(user.getUserName(), self.context.absolute_url(1))) raise zExceptions.NotFound() if user.has_permission(permissions.ModifyPortalContent, self.context): default_view = self.context.default_view_authenticated if qs: default_view += '?' + qs return self.request.response.redirect('{}/{}'.format( self.context.absolute_url(), default_view)) else: default_view = self.context.default_view_anonymous if default_view: if qs: default_view += '?' + qs return self.request.response.redirect('{}/{}'.format( self.context.absolute_url(), default_view)) else: msg = _(u'No default view configured for anonymous visitors') self.context.plone_utils.addPortalMessage(msg, 'error') raise zExceptions.NotFound()
from xmldirector.plonecore.dx.xml_binary import XMLBinaryDataManager ################################################################ # XML Image Content ################################################################ class IXMLImage(IField): """ Marker for XML fields """ pass class XMLImage(NamedImageField): zope.interface.implements(IXMLImage) XMLImageFactory = FieldFactory( XMLImage, _(u'label_xml_Image_field', default=u'XML (image)')) XMLImageHandler = plone.supermodel.exportimport.BaseHandler(XMLImage) class XMLImageDataManager(XMLBinaryDataManager): """Attribute field.""" zope.component.adapts( zope.interface.Interface, IXMLImage) suffix = '.img' return_class = NamedImage
class IConnector(model.Schema): connector_url = schema.TextLine( title=_(u'(optional) connection URL of storage'), description=_(u'WebDAV: http://host:port/path/to/webdav, ' 'Local filesystem: file://path/to/directory, ' 'AWS S3: s3://bucketname, ', 'SFTP sftp://host/path, ' 'FTP: ftp://host/path'), required=False ) connector_username = schema.TextLine( title=_(u'(optional) username overriding the system settings'), required=False ) connector_password = schema.Password( title=_(u'(optional) password overriding the system settings'), required=False ) connector_subpath = schema.TextLine( title=_(u'Subdirectory relative to the global connection URL'), description=_( u'Use this value for configuring a more specific subpath'), required=False ) api_enabled = schema.Bool( title=_(u'Public web API enabled'), default=False, required=False ) default_view_anonymous = schema.TextLine( title=_(u'Default view (anonymous)'), description=_( u'Name of a default view for site visitors without edit permission'), required=False, default=None, ) default_view_authenticated = schema.TextLine( title=_(u'Default view (authenticated)'), description=_(u'Name of a default view for anonymous site visitors'), required=False, default=u'@@view', )
################################################################ # XML Binary Content ################################################################ class IXMLBinary(IField): """ Marker for XML fields """ pass class XMLBinary(NamedFileField): zope.interface.implements(IXMLBinary) XMLBinaryFactory = FieldFactory( XMLBinary, _(u'label_xml_binary_field', default=u'XML (binary data)')) XMLBinaryHandler = plone.supermodel.exportimport.BaseHandler(XMLBinary) class XMLBinaryDataManager(AttributeDataManager): """Attribute field.""" zope.component.adapts(zope.interface.Interface, IXMLBinary) suffix = '.bin' return_class = NamedFile @property def storage_key(self): context_id = util.get_storage_key(self.context) if not context_id: context_id = util.new_storage_key(self.context)
class IReference(model.Schema): body = RichText(title=_(u'Reference content')) xml_body = XMLText(title=_(u'XML Content'), required=False)
class XMLText(Text): zope.interface.implements(IXMLText) def validate(self, value): """ Perform XML validation """ if value: try: lxml.etree.fromstring(normalize_xml(value)) except lxml.etree.XMLSyntaxError as e: raise zope.interface.Invalid(u'XML syntax error {}'.format(e)) return super(XMLText, self).validate(value) XMLTextFactory = FieldFactory(XMLText, _(u'label_xml_field', default=u'XML (Text)')) XMLTextHandler = plone.supermodel.exportimport.BaseHandler(XMLText) class XMLFieldDataManager(z3c.form.datamanager.AttributeField): """A dedicated manager for XMLText field.""" zope.component.adapts(zope.interface.Interface, IXMLText) def __init__(self, context, field): self.context = context self.field = field @property def storage_key(self): context_id = util.get_storage_key(self.context)
from plone.namedfile import NamedImage from xmldirector.plonecore.i18n import MessageFactory as _ from xmldirector.plonecore.dx.xml_binary import XMLBinaryDataManager ################################################################ # XML Image Content ################################################################ class IXMLImage(IField): """ Marker for XML fields """ pass class XMLImage(NamedImageField): zope.interface.implements(IXMLImage) XMLImageFactory = FieldFactory( XMLImage, _(u'label_xml_Image_field', default=u'XML (image)')) XMLImageHandler = plone.supermodel.exportimport.BaseHandler(XMLImage) class XMLImageDataManager(XMLBinaryDataManager): """Attribute field.""" zope.component.adapts(zope.interface.Interface, IXMLImage) suffix = '.img' return_class = NamedImage
class XMLXPath(TextLine): zope.interface.implements(IXMLXPath) def validate(self, value): if value: mo = parse_field_expression(value) try: fieldname, xpath_expr = mo except TypeError: raise zope.interface.Invalid( u'Invalid specification ({})'.format(value)) return super(XMLXPath, self).validate(value) XMLXPathFactory = FieldFactory(XMLXPath, _( u'label_xml_xpath_field', default=u'XML (extended XPath expression)')) XMLXPathHandler = plone.supermodel.exportimport.BaseHandler(XMLXPath) from xmldirector.plonecore.dx.xml_field import XMLFieldDataManager class IXPathWidget(IWidget): pass class XPathWidget(text.TextWidget): """ Widget for XPath expressions.""" zope.interface.implementsOnly(IXPathWidget) def xpath_evaluated(self):
def zip_import(self, zip_file=None, subpath=None, clean_directories=[]): """ Import WebDAV subfolder from an uploaded ZIP file """ handle = self.webdav_handle() if not zip_file: zip_filename = self.request.zipfile.filename zip_file = self.request.zipfile else: zip_filename = zip_file zip_file = open(zip_file, 'rb') LOG.info('ZIP import ({})'.format(zip_filename)) # zip_import() can also be used to upload single files # into the current webdav folder if not zip_filename.endswith('.zip'): if subpath: target_filename = '{}/{}'.format(subpath, zip_filename) else: target_filename = zip_filename with handle.open(target_filename, 'wb') as fp: fp.write(zip_file.read()) msg = u'File "{}" imported'.format(zip_filename) self.context.log(msg) return self.redirect(_(msg)) try: with ZipFS(zip_file, 'r') as zip_handle: # Cleanup webdav directory first for i, name in enumerate(handle.listdir()): if name not in clean_directories: continue if handle.isfile(name): handle.remove(name) else: handle.removedir(name, force=True, recursive=False) self.context.log(u'Subdirectory cleared (ZIP import)') # setup progressbar widgets = ['ZIP import: ', Percentage(), ' ', Bar( marker=RotatingMarker()), ' ', ETA(), ' '] files = list(zip_handle.walkfiles()) try: getConfiguration().testinghome show_progress = False except AttributeError: show_progress = True if show_progress: pbar = ProgressBar(widgets=widgets, maxval=len(files)).start() # import all files from ZIP into WebDAV count = 0 for i, name in enumerate(zip_handle.walkfiles()): if show_progress: pbar.update(i) dirname = '/'.join(name.split('/')[:-1]) try: handle.makedir( dirname, recursive=True, allow_recreate=True) except Exception as e: LOG.error( 'Failed creating {} failed ({})'.format(dirname, e)) LOG.info('ZIP filename({})'.format(name)) out_fp = handle.open(name.lstrip('/'), 'wb') zip_fp = zip_handle.open(name, 'rb') out_fp.write(zip_fp.read()) out_fp.close() count += 1 zip_fp.close() if show_progress: pbar.finish() except fs.zipfs.ZipOpenError as e: msg = u'Error opening ZIP file: {}'.format(e) return self.redirect(msg, 'error') self.context.log( u'ZIP file imported ({}, {} files)'.format(zip_filename, count)) return self.redirect(_(u'Uploaded ZIP archive imported'))
################################################################ # XML Image Content ################################################################ class IXMLImage(IField): """ Marker for XML fields """ pass class XMLImage(NamedImageField): zope.interface.implements(IXMLImage) XMLImageFactory = FieldFactory(XMLImage, _(u"label_xml_Image_field", default=u"XML (image)")) XMLImageHandler = plone.supermodel.exportimport.BaseHandler(XMLImage) class XMLImageDataManager(XMLBinaryDataManager): """Attribute field.""" zope.component.adapts(zope.interface.Interface, IXMLImage) suffix = ".img" return_class = NamedImage
class XMLText(Text): zope.interface.implements(IXMLText) def validate(self, value): """ Perform XML validation """ if value: try: lxml.etree.fromstring(normalize_xml(value)) except lxml.etree.XMLSyntaxError as e: raise zope.interface.Invalid(u'XML syntax error {}'.format(e)) return super(XMLText, self).validate(value) XMLTextFactory = FieldFactory( XMLText, _(u'label_xml_field', default=u'XML (Text)')) XMLTextHandler = plone.supermodel.exportimport.BaseHandler(XMLText) class XMLFieldDataManager(z3c.form.datamanager.AttributeField): """A dedicated manager for XMLText field.""" zope.component.adapts( zope.interface.Interface, IXMLText) def __init__(self, context, field): self.context = context self.field = field @property def storage_key(self):
class ITopic(model.Schema): body = RichText(title=_(u'Topic content')) xml_body = XMLText(title=_(u'XML Content'), required=False)
################################################################ # XML Binary Content ################################################################ class IXMLBinary(IField): """ Marker for XML fields """ pass class XMLBinary(NamedFileField): zope.interface.implements(IXMLBinary) XMLBinaryFactory = FieldFactory( XMLBinary, _(u'label_xml_binary_field', default=u'XML (binary data)')) XMLBinaryHandler = plone.supermodel.exportimport.BaseHandler(XMLBinary) class XMLBinaryDataManager(AttributeDataManager): """Attribute field.""" zope.component.adapts( zope.interface.Interface, IXMLBinary) suffix = '.bin' return_class = NamedFile @property def storage_key(self): context_id = util.get_storage_key(self.context)
def validate(self, value): if value: mo = parse_field_expression(value) try: fieldname, xpath_expr = mo except TypeError: raise zope.interface.Invalid( u'Invalid specification ({})'.format(value)) return super(XMLXPath, self).validate(value) XMLXPathFactory = FieldFactory( XMLXPath, _(u'label_xml_xpath_field', default=u'XML (extended XPath expression)')) XMLXPathHandler = plone.supermodel.exportimport.BaseHandler(XMLXPath) from xmldirector.plonecore.dx.xml_field import XMLFieldDataManager class IXPathWidget(IWidget): pass class XPathWidget(text.TextWidget): """ Widget for XPath expressions.""" zope.interface.implementsOnly(IXPathWidget) def xpath_evaluated(self): dm = XMLXPathDataManager(context=self.context, field=self.field)
def zip_import(self, zip_file=None, subpath=None, clean_directories=[]): """ Import WebDAV subfolder from an uploaded ZIP file """ handle = self.webdav_handle() if not zip_file: zip_filename = self.request.zipfile.filename zip_file = self.request.zipfile else: zip_filename = zip_file zip_file = open(zip_file, 'rb') LOG.info('ZIP import ({})'.format(zip_filename)) # zip_import() can also be used to upload single files # into the current webdav folder if not zip_filename.endswith('.zip'): if subpath: target_filename = '{}/{}'.format(subpath, zip_filename) else: target_filename = zip_filename with handle.open(target_filename, 'wb') as fp: fp.write(zip_file.read()) msg = u'File "{}" imported'.format(zip_filename) self.logger.log(msg) return self.redirect(_(msg)) try: with ZipFS(zip_file, 'r') as zip_handle: # Cleanup webdav directory first for i, name in enumerate(handle.listdir()): if name not in clean_directories: continue if handle.isfile(name): handle.remove(name) else: handle.removedir(name, force=True, recursive=False) self.logger.log(u'Subdirectory cleared (ZIP import)') # setup progressbar widgets = ['ZIP import: ', Percentage(), ' ', Bar( marker=RotatingMarker()), ' ', ETA(), ' '] files = list(zip_handle.walkfiles()) try: getConfiguration().testinghome show_progress = False except AttributeError: show_progress = True if show_progress: pbar = ProgressBar( widgets=widgets, maxval=len(files)).start() # import all files from ZIP into WebDAV count = 0 for i, name in enumerate(zip_handle.walkfiles()): if show_progress: pbar.update(i) dirname = '/'.join(name.split('/')[:-1]) try: handle.makedir( dirname, recursive=True, allow_recreate=True) except Exception as e: LOG.error( 'Failed creating {} failed ({})'.format(dirname, e)) LOG.info('ZIP filename({})'.format(name)) out_fp = handle.open(name.lstrip('/'), 'wb') zip_fp = zip_handle.open(name, 'rb') out_fp.write(zip_fp.read()) out_fp.close() count += 1 zip_fp.close() if show_progress: pbar.finish() except fs.zipfs.ZipOpenError as e: msg = u'Error opening ZIP file: {}'.format(e) return self.redirect(msg, 'error') self.logger.log( u'ZIP file imported ({}, {} files)'.format(zip_filename, count)) return self.redirect(_(u'Uploaded ZIP archive imported'))