[Zope] Import from file via management-interface
Tino Wildenhain
tino@wildenhain.de
Sat, 09 Sep 2000 00:40:06 +0200
This is a multi-part message in MIME format.
--------------371DC902889B4D4A37BBC6D5
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Hi all,
I found it very disturbing having to get my .zexp exports
into the subdirectory 'import' of the server and then using
the management-interface to do the actual import.
So I patched ObjectManager and the compaining dtml-file.
Please check them out (included in the mail, sorry for the
traffic) if I should post them to the collector.
Regards
Tino
--------------371DC902889B4D4A37BBC6D5
Content-Type: text/plain; charset=us-ascii;
name="ObjectManager.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="ObjectManager.py"
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__doc__="""Object Manager
$Id: ObjectManager.py,v 1.98.8.7 2000/08/17 20:20:01 shane Exp $"""
__version__='$Revision: 1.98.8.7 $'[11:-2]
import App.Management, Acquisition, Globals, CopySupport, Products
import os, App.FactoryDispatcher, ts_regex, Products
from OFS.Traversable import Traversable
from Globals import HTMLFile, HTMLFile, Persistent
from Globals import MessageDialog, default__class_init__
from webdav.NullResource import NullResource
from webdav.Collection import Collection
from Acquisition import aq_base
from urllib import quote
from cStringIO import StringIO
import marshal
import App.Common
from AccessControl import getSecurityManager
from zLOG import LOG, ERROR
import sys
import XMLExportImport
customImporters={
XMLExportImport.magic: XMLExportImport.importXML,
}
bad_id=ts_regex.compile('[^a-zA-Z0-9-_~\,\. ]').search #TS
class BeforeDeleteException( Exception ): pass # raise to veto deletion
_marker=[]
class ObjectManager(
CopySupport.CopyContainer,
App.Management.Navigation,
App.Management.Tabs,
Acquisition.Implicit,
Persistent,
Collection,
Traversable,
):
"""Generic object manager
This class provides core behavior for collections of heterogeneous objects.
"""
__ac_permissions__=(
('View management screens', ('manage_main','manage_menu')),
('Access contents information',
('objectIds', 'objectValues', 'objectItems',''),
('Anonymous', 'Manager'),
),
('Delete objects', ('manage_delObjects',)),
('FTP access', ('manage_FTPstat','manage_FTPlist')),
('Import/Export objects',
('manage_importObject','manage_importExportForm',
'manage_exportObject')
),
)
meta_type ='Object Manager'
meta_types=() # Sub-object types that are specific to this object
_objects =()
manage_main=HTMLFile('main', globals())
manage_options=(
{'label':'Contents', 'action':'manage_main',
'help':('OFSP','ObjectManager_Contents.stx')},
{'label':'Import/Export', 'action':'manage_importExportForm',
'help':('OFSP','ObjectManager_Import-Export.stx')},
)
isAnObjectManager=1
isPrincipiaFolderish=1
def __class_init__(self):
try: mt=list(self.meta_types)
except: mt=[]
for b in self.__bases__:
try:
for t in b.meta_types:
if t not in mt: mt.append(t)
except: pass
mt.sort()
self.meta_types=tuple(mt)
default__class_init__(self)
def all_meta_types(self):
pmt=()
if hasattr(self, '_product_meta_types'): pmt=self._product_meta_types
elif hasattr(self, 'aq_acquire'):
try: pmt=self.aq_acquire('_product_meta_types')
except: pass
return self.meta_types+Products.meta_types+pmt
def _subobject_permissions(self):
return (Products.__ac_permissions__+
self.aq_acquire('_getProductRegistryData')('ac_permissions')
)
def filtered_meta_types(self, user=None):
# Return a list of the types for which the user has
# adequate permission to add that type of object.
user=getSecurityManager().getUser()
meta_types=[]
if callable(self.all_meta_types):
all=self.all_meta_types()
else:
all=self.all_meta_types
for meta_type in all:
if meta_type.has_key('permission'):
if user.has_permission(meta_type['permission'],self):
meta_types.append(meta_type)
else:
meta_types.append(meta_type)
return meta_types
def _checkId(self, id, allow_dup=0):
# If allow_dup is false, an error will be raised if an object
# with the given id already exists. If allow_dup is true,
# only check that the id string contains no illegal chars.
if not id or (type(id) != type('')):
raise 'Bad Request', 'Empty or invalid specified'
if bad_id(id) != -1:
raise 'Bad Request', (
'The id %s contains characters illegal in URLs.' % id)
if id[0]=='_': raise 'Bad Request', (
'The id %s is invalid - it begins with an underscore.' % id)
if not allow_dup:
if hasattr(aq_base(self), id):
raise 'Bad Request', (
'The id %s is invalid - it is already in use.' % id)
if id == 'REQUEST':
raise 'Bad Request', 'REQUEST is a reserved name.'
if '/' in id:
raise 'Bad Request', (
'The id %s contains characters illegal in URLs.' % id
)
def _setOb(self, id, object): setattr(self, id, object)
def _delOb(self, id): delattr(self, id)
def _getOb(self, id, default=_marker):
if not hasattr(aq_base(self), id):
if default is _marker:
raise AttributeError, id
return default
return getattr(self, id)
def _setObject(self,id,object,roles=None,user=None, set_owner=1):
v=self._checkId(id)
if v is not None: id=v
try: t=object.meta_type
except: t=None
self._objects=self._objects+({'id':id,'meta_type':t},)
# Prepare the _p_jar attribute immediately. _getCopy() may need it.
if hasattr(object, '_p_jar'):
object._p_jar = self._p_jar
self._setOb(id,object)
object=self._getOb(id)
if set_owner:
object.manage_fixupOwnershipAfterAdd()
# Try to give user the local role "Owner", but only if
# no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'):
if object.__ac_local_roles__ is None:
user=getSecurityManager().getUser()
name=user.getUserName()
if name != 'Anonymous User':
object.manage_setLocalRoles(name, ['Owner'])
object.manage_afterAdd(object, self)
return id
def manage_afterAdd(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterAdd'):
object.manage_afterAdd(item, container)
if s is None: object._p_deactivate()
def manage_afterClone(self, item):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterClone'):
object.manage_afterClone(item)
if s is None: object._p_deactivate()
def manage_beforeDelete(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
try:
if hasattr(aq_base(object), 'manage_beforeDelete'):
object.manage_beforeDelete(item, container)
except BeforeDeleteException, ob:
raise
except:
LOG('Zope',ERROR,'manage_beforeDelete() threw',
error=sys.exc_info())
pass
if s is None: object._p_deactivate()
def _delObject(self, id, dp=1):
object=self._getOb(id)
try:
object.manage_beforeDelete(object, self)
except BeforeDeleteException, ob:
raise
except:
LOG('Zope',ERROR,'manage_beforeDelete() threw',
error=sys.exc_info())
pass
self._objects=tuple(filter(lambda i,n=id: i['id']!=n, self._objects))
self._delOb(id)
# Indicate to the object that it has been deleted. This is
# necessary for object DB mount points. Note that we have to
# tolerate failure here because the object being deleted could
# be a Broken object, and it is not possible to set attributes
# on Broken objects.
try: object._v__object_deleted__ = 1
except: pass
def objectIds(self, spec=None):
"""Returns a list of subobject ids of the current object.
If 'spec' is specified, returns objects whose meta_type
matches 'spec'."""
if spec is not None:
if type(spec)==type('s'):
spec=[spec]
set=[]
for ob in self._objects:
if ob['meta_type'] in spec:
set.append(ob['id'])
return set
return map(lambda i: i['id'], self._objects)
def objectValues(self, spec=None):
"""Returns a list of actual subobjects of the current object.
If 'spec' is specified, returns only objects whose meta_type
match 'spec'."""
return map(self._getOb, self.objectIds(spec))
def objectItems(self, spec=None):
# Returns a list of (id, subobject) tuples of the current object.
# If 'spec' is specified, returns only objects whose meta_type match
# 'spec'
r=[]
a=r.append
g=self._getOb
for id in self.objectIds(spec): a((id, g(id)))
return r
def objectMap(self):
# Return a tuple of mappings containing subobject meta-data
return self._objects
def objectIds_d(self,t=None):
if hasattr(self, '_reserved_names'): n=self._reserved_names
else: n=()
if not n: return self.objectIds(t)
r=[]
a=r.append
for id in self.objectIds(t):
if id not in n: a(id)
return r
def objectValues_d(self,t=None):
return map(self._getOb, self.objectIds_d(t))
def objectItems_d(self,t=None):
r=[]
a=r.append
g=self._getOb
for id in self.objectIds_d(t): a((id, g(id)))
return r
def objectMap_d(self,t=None):
if hasattr(self, '_reserved_names'): n=self._reserved_names
else: n=()
if not n: return self._objects
r=[]
a=r.append
for d in self._objects:
if d['id'] not in n: a(d)
return r
def superValues(self,t):
# Return all of the objects of a given type located in
# this object and containing objects.
if type(t)==type('s'): t=(t,)
obj=self
seen={}
vals=[]
have=seen.has_key
x=0
while x < 100:
if not hasattr(obj,'_getOb'): break
get=obj._getOb
if hasattr(obj,'_objects'):
for i in obj._objects:
try:
id=i['id']
if (not have(id)) and (i['meta_type'] in t):
vals.append(get(id))
seen[id]=1
except: pass
if hasattr(obj,'aq_parent'): obj=obj.aq_parent
else: return vals
x=x+1
return vals
manage_addProduct=App.FactoryDispatcher.ProductDispatcher()
def manage_delObjects(self, ids=[], REQUEST=None):
"""Delete a subordinate object
The objects specified in 'ids' get deleted.
"""
if type(ids) is type(''): ids=[ids]
if not ids:
return MessageDialog(title='No items specified',
message='No items were specified!',
action ='./manage_main',)
try: p=self._reserved_names
except: p=()
for n in ids:
if n in p:
return MessageDialog(title='Not Deletable',
message='<EM>%s</EM> cannot be deleted.' % n,
action ='./manage_main',)
while ids:
id=ids[-1]
v=self._getOb(id, self)
if v is self:
raise 'BadRequest', '%s does not exist' % ids[-1]
self._delObject(id)
del ids[-1]
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
def tpValues(self):
# Return a list of subobjects, used by tree tag.
r=[]
if hasattr(aq_base(self), 'tree_ids'):
for id in self.tree_ids:
if hasattr(self, id):
r.append(self._getOb(id))
else:
for id in self._objects:
o=self._getOb(id['id'])
try:
if o.isPrincipiaFolderish: r.append(o)
except: pass
return r
def manage_exportObject(self, id='', download=None, toxml=None,
RESPONSE=None):
"""Exports an object to a file and returns that file."""
if not id:
id=self.id
if callable(id): id=id()
ob=self
else: ob=self._getOb(id)
suffix=toxml and 'xml' or 'zexp'
if download:
f=StringIO()
if toxml: ob._p_jar.exportXML(ob._p_oid, f)
else: ob._p_jar.exportFile(ob._p_oid, f)
RESPONSE.setHeader('Content-type','application/data')
RESPONSE.setHeader('Content-Disposition',
'inline;filename=%s.%s' % (id, suffix))
return f.getvalue()
f=Globals.data_dir+'/%s.%s' % (id, suffix)
if toxml:
XMLExportImport.exportXML(ob._p_jar, ob._p_oid, f)
else:
ob._p_jar.exportFile(ob._p_oid, f)
if RESPONSE is not None:
return MessageDialog(
title="Object exported",
message="<EM>%s</EM> sucessfully\
exported to <pre>%s</pre>." % (id, f),
action="manage_main")
manage_importExportForm=HTMLFile('importExport',globals())
def manage_importObject(self, file, REQUEST=None, set_owner=1):
"""Import an object from a file"""
if type(file)==type(''):
dirname, file=os.path.split(file)
if dirname:
raise 'Bad Request', 'Invalid file name %s' % file
file=os.path.join(INSTANCE_HOME, 'import', file)
if not os.path.exists(file):
raise 'Bad Request', 'File does not exist: %s' % file
# locate a valid connection
connection=self._p_jar
obj=self
while connection is None:
obj=obj.aq_parent
connection=obj._p_jar
ob=connection.importFile(
file, customImporters=customImporters)
if REQUEST: self._verifyObjectPaste(ob, validate_src=0)
id=ob.id
if hasattr(id, 'im_func'): id=id()
self._setObject(id, ob, set_owner=set_owner)
# try to make ownership implicit if possible in the context
# that the object was imported into.
ob=self._getOb(id)
ob.manage_changeOwnershipType(explicit=0)
if REQUEST is not None:
return MessageDialog(
title='Object imported',
message='<EM>%s</EM> sucessfully imported' % id,
action='manage_main'
)
# FTP support methods
def manage_FTPlist(self, REQUEST):
"Directory listing for FTP"
out=()
# check to see if we are being acquiring or not
ob=self
while 1:
if App.Common.is_acquired(ob):
raise ValueError('FTP List not supported on acquired objects')
if not hasattr(ob,'aq_parent'):
break
ob=ob.aq_parent
files=self.objectItems()
try:
files.sort()
except AttributeError:
files=list(files)
files.sort()
if not (hasattr(self,'isTopLevelPrincipiaApplicationObject') and
self.isTopLevelPrincipiaApplicationObject):
files.insert(0,('..',self.aq_parent))
for k,v in files:
# Note that we have to tolerate failure here, because
# Broken objects won't stat correctly. If an object fails
# to be able to stat itself, we will ignore it.
try: stat=marshal.loads(v.manage_FTPstat(REQUEST))
except: stat=None
if stat is not None:
out=out+((k,stat),)
return marshal.dumps(out)
def manage_FTPstat(self,REQUEST):
"Psuedo stat used for FTP listings"
mode=0040000
from AccessControl.User import nobody
# check to see if we are acquiring our objectValues or not
if not (len(REQUEST.PARENTS) > 1 and
self.objectValues() == REQUEST.PARENTS[1].objectValues()):
try:
if getSecurityManager().validateValue(self.manage_FTPlist):
mode=mode | 0770
except: pass
if nobody.allowed(
self.manage_FTPlist,
self.manage_FTPlist.__roles__):
mode=mode | 0007
mtime=self.bobobase_modification_time().timeTime()
# get owner and group
owner=group='Zope'
for user, roles in self.get_local_roles():
if 'Owner' in roles:
owner=user
break
return marshal.dumps((mode,0,0,1,owner,group,0,mtime,mtime,mtime))
def __getitem__(self, key):
v=self._getOb(key, None)
if v is not None: return v
if hasattr(self, 'REQUEST'):
request=self.REQUEST
method=request.get('REQUEST_METHOD', 'GET')
if not method in ('GET', 'POST'):
return NullResource(self, key, request).__of__(self)
raise KeyError, key
Globals.default__class_init__(ObjectManager)
class PUTer(Acquisition.Explicit):
"""Class to support the HTTP PUT protocol."""
def __init__(self, parent, id):
self.id=id
self.__parent__=parent
self.__roles__ =parent.PUT__roles__
def PUT(self, REQUEST, RESPONSE):
"""Adds a document, image or file to the folder when a PUT
request is received."""
name=self.id
type=REQUEST.get_header('content-type', None)
body=REQUEST.get('BODY', '')
if type is None:
type, enc=mimetypes.guess_type(name)
if type is None:
if content_types.find_binary(body) >= 0:
type='application/octet-stream'
else: type=content_types.text_type(body)
type=lower(type)
if type in ('text/html', 'text/xml', 'text/plain'):
self.__parent__.manage_addDTMLDocument(name, '', body)
elif type[:6]=='image/':
ob=Image(name, '', body, content_type=type)
self.__parent__._setObject(name, ob)
else:
ob=File(name, '', body, content_type=type)
self.__parent__._setObject(name, ob)
RESPONSE.setStatus(201)
RESPONSE.setBody('')
return RESPONSE
def __str__(self):
return self.id
--------------371DC902889B4D4A37BBC6D5
Content-Type: text/html; charset=us-ascii;
name="importExport.dtml"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="importExport.dtml"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML lang="en">
<HEAD>
<TITLE>Import Object</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<dtml-var manage_tabs>
<P>
You can export Zope objects to a file in order to transfer
them to a different Zope installation. You can either choose
to download the export file to your local machine, or save it
in the "var" directory of your Zope installation
on the server.
</P>
<FORM ACTION="manage_exportObject" METHOD="POST">
<TABLE CELLSPACING="2">
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Export object id</TH>
<TD ALIGH="LEFT" VALIGN="TOP">
<INPUT TYPE="TEXT" NAME="id" SIZE="25" VALUE="<dtml-if ids><dtml-var
"ids[0]" html_quote></dtml-if>">
</TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Export to</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="RADIO" NAME="download:int" VALUE="1">
Download to local machine<BR>
<INPUT TYPE="RADIO" NAME="download:int" VALUE="0" checked>
Save to file on server
</TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP"></TH>
<TD ALIGH="LEFT" VALIGN="TOP">
<INPUT TYPE="CHECKBOX" NAME="toxml" VALUE="Y"> XML format?
</TD>
</TR>
<TR>
<TD></TD>
<TD><INPUT TYPE="SUBMIT" VALUE="Export"></TD>
</TR>
</TABLE>
</FORM>
<p>
You may import Zope objects which have been previously
exported to a file, by placing the file in the "import"
directory of your Zope installation on the server. You should create
the "import" directory in the root of your Zope installation
if it does not yet exist.
</p>
<p>
Note that by default, you will become the owner of the objects
that you are importing. If you wish the imported objects to retain
their existing ownership information, select "retain existing
ownership information".
</p>
<FORM ACTION="manage_importObject" METHOD="POST">
<TABLE CELLSPACING="2">
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Import file name</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="text" NAME="file" SIZE="25" VALUE="">
</TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">
Ownership
</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<input type="radio" name="set_owner:int" value="1" checked>
Take ownership of imported objects
<br>
<input type="radio" name="set_owner:int" value="0">
Retain existing ownership information
</TD>
</TR>
<TR>
<TD></TD>
<TD><INPUT TYPE="SUBMIT" VALUE="Import">
</TD>
</TR>
</TABLE>
</FORM>
<FORM ACTION="manage_importObject" METHOD="POST" ENCTYPE="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Import file</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="file" NAME="file" SIZE="25" VALUE="">
</TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">
Ownership
</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<input type="radio" name="set_owner:int" value="1" checked>
Take ownership of imported objects
<br>
<input type="radio" name="set_owner:int" value="0">
Retain existing ownership information
</TD>
</TR>
<TR>
<TD></TD>
<TD><INPUT TYPE="SUBMIT" VALUE="Import">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
--------------371DC902889B4D4A37BBC6D5--