Commit 6ddf078f authored by Hanno Schlichting's avatar Hanno Schlichting

Split out `Products.TemporaryFolder` into its own project.

parent 3eb0469d
......@@ -25,6 +25,9 @@ Features Added
Restructuring
+++++++++++++
- Split `Products.TemporaryFolder` and `Products.ZODBMountPoint` into
one new project called `Products.TemporaryFolder`.
- Split a WSGI part out of `zopeschema.xml`. This reduces the supported
`zope.conf` directives when run under WSGI.
......
......@@ -56,7 +56,6 @@ eggs =
# RestrictedPython has an optional dependency on DateTime, make sure to run its
# tests with DateTime being available
RestrictedPython
tempstorage
zExceptions
ZopeUndo
# Test optional dependencies.
......@@ -68,9 +67,11 @@ eggs =
Products.Sessions
Products.SiteErrorLog
Products.StandardCacheManagers
Products.TemporaryFolder
Products.ZCatalog
Products.ZCTextIndex
Record
tempstorage
zLOG
......
......@@ -65,7 +65,6 @@ setup(
'five.globalrequest',
'repoze.retry',
'setuptools',
'tempstorage',
'transaction',
'waitress',
'zdaemon',
......
......@@ -12,7 +12,6 @@ ExtensionClass = git ${remotes:github}/ExtensionClass pushurl=${remotes:github_p
five.globalrequest = git ${remotes:github}/five.globalrequest pushurl=${remotes:github_push}/five.globalrequest
MultiMapping = git ${remotes:github}/MultiMapping pushurl=${remotes:github_push}/MultiMapping
Persistence = git ${remotes:github}/Persistence pushurl=${remotes:github_push}/Persistence
tempstorage = git ${remotes:github}/tempstorage pushurl=${remotes:github_push}/tempstorage
zExceptions = git ${remotes:github}/zExceptions pushurl=${remotes:github_push}/zExceptions
zope.globalrequest = git ${remotes:github}/zope.globalrequest pushurl=${remotes:github_push}/zope.globalrequest
ZopeUndo = git ${remotes:github}/ZopeUndo pushurl=${remotes:github_push}/ZopeUndo
......@@ -27,9 +26,11 @@ Products.PythonScripts = git ${remotes:github}/Products.PythonScripts pushurl=${
Products.Sessions = git ${remotes:github}/Products.Sessions.git pushurl=${remotes:github_push}/Products.Sessions
Products.SiteErrorLog = git ${remotes:github}/Products.SiteErrorLog pushurl=${remotes:github_push}/Products.SiteErrorLog
Products.StandardCacheManagers = git ${remotes:github}/Products.StandardCacheManagers pushurl=${remotes:github_push}/Products.StandardCacheManagers
Products.TemporaryFolder = git ${remotes:github}/Products.TemporaryFolder pushurl=${remotes:github_push}/Products.TemporaryFolder branch=master
Products.ZCatalog = git ${remotes:github}/Products.ZCatalog pushurl=${remotes:github_push}/Products.ZCatalog branch=master
Products.ZCTextIndex = git ${remotes:github}/Products.ZCTextIndex pushurl=${remotes:github_push}/Products.ZCTextIndex
Record = git ${remotes:github}/Record pushurl=${remotes:github_push}/Record
tempstorage = git ${remotes:github}/tempstorage pushurl=${remotes:github_push}/tempstorage
zLOG = git ${remotes:github}/zLOG pushurl=${remotes:github_push}/zLOG
# ZTK
......
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ZODB.Connection import Connection
from ZODB.POSException import ConflictError
from cPickle import Unpickler
from cStringIO import StringIO
class LowConflictConnection(Connection):
def setstate(self, object):
"""
Unlike the 'stock' Connection class' setstate, this method
doesn't raise ConflictErrors. This is potentially dangerous
for applications that need absolute consistency, but
sessioning is not one of those.
"""
oid=object._p_oid
invalid = self._invalid
if invalid(None):
# only raise a conflict if there was
# a mass invalidation, but not if we see this
# object's oid as invalid
raise ConflictError, `oid`
p, serial = self._storage.load(oid, self._version)
file=StringIO(p)
unpickler=Unpickler(file)
unpickler.persistent_load=self._persistent_load
unpickler.load()
state = unpickler.load()
if hasattr(object, '__setstate__'):
object.__setstate__(state)
else:
d=object.__dict__
for k,v in state.items(): d[k]=v
object._p_serial=serial
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
""" Mounted database support
A MountedTemporaryFolder is an object that is a mount point. It mounts a
TemporaryStorage-backed database and masquerades as its root object.
When you traverse one of these things, the __of__ method of the mount
point object is called, and that returns a Folder object that actually
lives in another ZODB.
To understand this fully, you'll need to read the source of
Products.TemporaryFolder.mount.MountPoint.
"""
from App.special_dtml import DTMLFile
from App.special_dtml import HTMLFile
from OFS.Folder import Folder
from OFS.SimpleItem import Item
from tempstorage.TemporaryStorage import TemporaryStorage
from ZODB.DB import DB
from Products.TemporaryFolder.mount import MountPoint
ADD_TEMPORARY_FOLDER_PERM="Add Temporary Folder"
def constructTemporaryFolder(self, id, title=None, REQUEST=None):
""" """
ms = MountedTemporaryFolder(id, title)
self._setObject(id, ms)
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
constructTemporaryFolderForm=HTMLFile('dtml/addTemporaryFolder', globals())
class SimpleTemporaryContainer(Folder):
# dbtab-style container class
meta_type = 'Temporary Folder'
class MountedTemporaryFolder(MountPoint, Item):
"""
A mounted RAM database with a basic interface for displaying the
reason the database did not connect.
XXX this is only here for backwards compatibility purposes:
DBTab uses the SimpleTemporaryContainer class instead.
"""
manage_options = ({'label':'Traceback', 'action':'manage_traceback'},)
meta_type = 'Broken Temporary Folder'
def __init__(self, id, title='', params=None):
self.id = str(id)
self.title = title
MountPoint.__init__(self, path='/') # Eep
manage_traceback = DTMLFile('dtml/mountfail', globals())
def _createDB(self):
""" Create a mounted RAM database """
return DB(TemporaryStorage())
def _getMountRoot(self, root):
sdc = root.get('folder', None)
if sdc is None:
sdc = root['folder'] = Folder()
self._populate(sdc, root)
return sdc
def mount_error_(self):
return self._v_connect_error
def _populate(self, folder, root):
# Set up our folder object
folder.id = self.id
folder.title = self.title
s=folder.manage_options[1:]
folder.manage_options = (
{'label':'Contents', 'action':'manage_main'},
)+s
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Temporary Folder initialization routines
"""
import ZODB # for testrunner to be happy
# we import this so that config files can use the shorter name,
# it's not used directly
from TemporaryFolder import SimpleTemporaryContainer as TemporaryContainer
def initialize(context):
import TemporaryFolder
context.registerClass(
TemporaryFolder.MountedTemporaryFolder,
permission=TemporaryFolder.ADD_TEMPORARY_FOLDER_PERM,
meta_type='Temporary Folder',
constructors=(TemporaryFolder.constructTemporaryFolderForm,
TemporaryFolder.constructTemporaryFolder),
visibility=0 # dont show this in the add list for 2.7+ (use dbtab)
)
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add Temporary Folder'
)">
<form action="constructTemporaryFolder" method="POST">
<div class="form-help">
<p>
Temporary Folders are folderish objects which keep their contents entirely
in volatile memory (RAM). Their contents are flushed on each Zope restart.
They are useful for storing limited numbers of temporary objects.
</p>
</div>
<table cellspacing="2">
<tr>
<td align="LEFT" valign="TOP">
<div class="form-label">
Id
</div>
</td>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="id" size="20" value="temp_folder" />
</td>
</tr>
<tr>
<td align="LEFT" valign="TOP">
<div class="form-label">
<em>Title</em>
</div>
</td>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="40" />
</td>
</tr>
<tr>
<td></td>
<td><br><input class="form-element" type="SUBMIT" value=" Add "></td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<HTML><HEAD><TITLE>Mount Failure Traceback</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<dtml-var manage_tabs>
<h3>Mount Failure Traceback</h3>
<dtml-let exc=mount_error_>
<dtml-if exc>
<strong>Error type:</strong> <dtml-var "exc[0]" html_quote><br>
<strong>Error value:</strong> <dtml-var "exc[1]" html_quote><br>
<pre>
<dtml-var "exc[2]" html_quote>
</pre>
<dtml-else>
Database not mounted.
</dtml-if>
</dtml-let>
</BODY>
</HTML>
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Mounted database support
"""
import time
import thread
import logging
import persistent
from Acquisition import Implicit
from Acquisition import ImplicitAcquisitionWrapper
from Acquisition import aq_base
from ZODB.POSException import StorageError
class MountedStorageError(StorageError):
"""Unable to access mounted storage."""
logger = logging.getLogger('ZODB.Mount')
# dbs is a holder for all DB objects, needed to overcome
# threading issues. It maps connection params to a DB object
# and a mapping of mount points.
dbs = {}
# dblock is locked every time dbs is accessed.
dblock = thread.allocate_lock()
def parentClassFactory(jar, module, name):
# Use the class factory from the parent database.
parent_conn = getattr(jar, '_mount_parent_jar', None)
parent_db = getattr(parent_conn, '_db', None)
if parent_db is None:
_globals = {}
_silly = ('__doc__',)
return getattr(__import__(
module, _globals, _globals, _silly), name)
else:
return parent_db.classFactory(parent_conn, module, name)
class MountPoint(persistent.Persistent, Implicit):
'''The base class for a Zope object which, when traversed,
accesses a different database.
'''
# Default values for non-persistent variables.
_v_db = None
_v_data = None
_v_connect_error = None
def __init__(self, path, params=None, classDefsFromRoot=None):
'''
@arg path The path within the mounted database from which
to derive the root.
@arg params The parameters used to connect to the database.
No particular format required.
If there is more than one mount point referring to a
database, MountPoint will detect the matching params
and use the existing database. Include the class name of
the storage. For example,
ZEO params might be "ZODB.ZEOClient localhost 1081".
'''
# The only reason we need a __mountpoint_id is to
# be sure we don't close a database prematurely when
# it is mounted more than once and one of the points
# is unmounted.
self.__mountpoint_id = '%s_%f' % (id(self), time.time())
if params is None:
# We still need something to use as a hash in
# the "dbs" dictionary.
params = self.__mountpoint_id
self._params = repr(params)
self._path = path
def _createDB(self):
'''Gets the database object, usually by creating a Storage object
and returning ZODB.DB(storage).
'''
raise NotImplementedError
def _getDB(self):
'''Creates or opens a DB object.
'''
newMount = 0
dblock.acquire()
try:
params = self._params
dbInfo = dbs.get(params, None)
if dbInfo is None:
logger.info('Opening database for mounting: %s', params)
db = self._createDB()
newMount = 1
dbs[params] = (db, {self.__mountpoint_id:1})
else:
db, mounts = dbInfo
# Be sure this object is in the list of mount points.
if not mounts.has_key(self.__mountpoint_id):
newMount = 1
mounts[self.__mountpoint_id] = 1
self._v_db = db
finally:
dblock.release()
return db, newMount
def _getMountpointId(self):
return self.__mountpoint_id
def _getMountParams(self):
return self._params
def __repr__(self):
return "%s(%s, %s)" % (self.__class__.__name__, repr(self._path),
self._params)
def _openMountableConnection(self, parent):
# Opens a new connection to the database.
db = self._v_db
if db is None:
self._v_close_db = 0
db, newMount = self._getDB()
else:
newMount = 0
jar = getattr(self, '_p_jar', None)
if jar is None:
# Get _p_jar from parent.
self._p_jar = jar = parent._p_jar
conn = db.open()
# Add an attribute to the connection which
# makes it possible for us to find the primary
# database connection. See ClassFactoryForMount().
conn._mount_parent_jar = jar
mcc = MountedConnectionCloser(self, conn)
jar.onCloseCallback(mcc)
return conn, newMount, mcc
def _getObjectFromConnection(self, conn):
obj = self._getMountRoot(conn.root())
data = aq_base(obj)
# Store the data object in a tuple to hide from acquisition.
self._v_data = (data,)
return data
def _getOrOpenObject(self, parent):
t = self._v_data
if t is None:
self._v_connect_error = None
conn = None
newMount = 0
mcc = None
try:
conn, newMount, mcc = self._openMountableConnection(parent)
data = self._getObjectFromConnection(conn)
except:
# Possibly broken database.
if mcc is not None:
# Note that the next line may be a little rash--
# if, for example, a working database throws an
# exception rather than wait for a new connection,
# this will likely cause the database to be closed
# prematurely. Perhaps DB.py needs a
# countActiveConnections() method.
mcc.setCloseDb()
logger.warning('Failed to mount database. %s (%s)',
exc_info=True)
raise
if newMount:
try: id = data.getId()
except: id = '???' # data has no getId() method. Bad.
p = '/'.join(parent.getPhysicalPath() + (id,))
logger.info('Mounted database %s at %s',
self._getMountParams(), p)
else:
data = t[0]
return data.__of__(parent)
def __of__(self, parent):
# Accesses the database, returning an acquisition
# wrapper around the connected object rather than around self.
try:
return self._getOrOpenObject(parent)
except:
return ImplicitAcquisitionWrapper(self, parent)
def _test(self, parent):
'''Tests the database connection.
'''
self._getOrOpenObject(parent)
return 1
def _getMountRoot(self, root):
'''Gets the object to be mounted.
Can be overridden to provide different behavior.
'''
try:
app = root['Application']
except:
raise MountedStorageError(
"No 'Application' object exists in the mountable database.")
try:
return app.unrestrictedTraverse(self._path)
except:
raise MountedStorageError(
"The path '%s' was not found in the mountable database."
% self._path)
class MountedConnectionCloser:
'''Closes the connection used by the mounted database
while performing other cleanup.
'''
close_db = 0
def __init__(self, mountpoint, conn):
# conn is the child connection.
self.mp = mountpoint
self.conn = conn
def setCloseDb(self):
self.close_db = 1
def __call__(self):
# The onCloseCallback handler.
# Closes a single connection to the database
# and possibly the database itself.
conn = self.conn
close_db = 0
if conn is not None:
mp = self.mp
# Remove potential circular references.
self.conn = None
self.mp = None
# Detect whether we should close the database.
close_db = self.close_db
t = mp.__dict__.get('_v_data', None)
if t is not None:
del mp.__dict__['_v_data']
data = t[0]
if not close_db and data.__dict__.get(
'_v__object_deleted__', 0):
# This mount point has been deleted.
del data.__dict__['_v__object_deleted__']
close_db = 1
# Close the child connection.
try:
del conn._mount_parent_jar
except:
pass
conn.close()
if close_db:
# Stop using this database. Close it if no other
# MountPoint is using it.
dblock.acquire()
try:
params = mp._getMountParams()
mp._v_db = None
if dbs.has_key(params):
dbInfo = dbs[params]
db, mounts = dbInfo
try: del mounts[mp._getMountpointId()]
except: pass
if len(mounts) < 1:
# No more mount points are using this database.
del dbs[params]
db.close()
logger.info('Closed database: %s', params)
finally:
dblock.release()
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""ZODBMountPoint product.
"""
def initialize(context):
# Configure and load databases if not already done.
import MountedObject
context.registerClass(
MountedObject.MountedObject,
constructors=(MountedObject.manage_addMountsForm,
MountedObject.manage_getMountStatus,
MountedObject.manage_addMounts,),
)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Tests of ZODBMountPoint
"""
import os
import os.path
import sys
import unittest
import transaction
from OFS.Application import Application
from OFS.Folder import Folder
import App.config
from Products.ZODBMountPoint.MountedObject import manage_addMounts
from Products.ZODBMountPoint.MountedObject import getMountPoint
from Products.ZODBMountPoint.MountedObject import manage_getMountStatus
from Zope2.Startup.datatypes import DBTab
try:
__file__
except NameError:
__file__ = os.path.abspath(sys.argv[0])
class TestDBConfig:
def __init__(self, fname, mpoints):
self.fname = fname
self.mpoints = mpoints
def getDB(self):
from ZODB.config import DemoStorage
from ZODB.Connection import Connection
from Zope2.Startup.datatypes import ZopeDatabase
self.name = self.fname
self.base = None
self.path = os.path.join(os.path.dirname(__file__), self.fname)
self.cache_size = 5000
self.cache_size_bytes = 0
self.class_factory = None
self.connection_class = Connection
self.container_class = None
self.create = None
self.factories = ()
self.historical_pool_size = 3
self.historical_cache_size = 1000
self.historical_cache_size_bytes = 0
self.historical_timeout = 300
self.mount_points = self.mpoints
self.pool_size = 7
self.pool_timeout = 1<<31
self.quota = None
self.read_only = None
self.storage = DemoStorage(self)
self.version_cache_size = 100
self.version_pool_size = 3
self.allow_implicit_cross_references = False
self.large_record_size = 1<<24
return ZopeDatabase(self)
def getSectionName(self):
return self.name
original_config = None
class MountingTests(unittest.TestCase):
def setUp(self):
global original_config
if original_config is None:
# stow away original config so we can reset it
original_config = App.config.getConfiguration()
databases = [TestDBConfig('test_main.fs', ['/']).getDB(),
TestDBConfig('test_mount1.fs', ['/mount1']).getDB(),
TestDBConfig('test_mount2.fs', ['/mount2']).getDB(),
TestDBConfig('test_mount3.fs', ['/i/mount3']).getDB(),
]
mount_points = {}
mount_factories = {}
for database in databases:
points = database.getVirtualMountPaths()
name = database.config.getSectionName()
mount_factories[name] = database
for point in points:
mount_points[point] = name
conf = DBTab(mount_factories, mount_points)
d = App.config.DefaultConfiguration()
d.dbtab = conf
App.config.setConfiguration(d)
self.conf = conf
db = conf.getDatabase('/')
conn = db.open()
root = conn.root()
root['Application'] = app = Application()
self.app = app
# login
from AccessControl.User import system
from AccessControl.SecurityManagement import newSecurityManager
newSecurityManager(None, system)
transaction.commit() # Get app._p_jar set
manage_addMounts(app, ('/mount1', '/mount2', '/i/mount3'))
transaction.commit() # Get the mount points ready
def tearDown(self):
# logout
from AccessControl.SecurityManagement import noSecurityManager
noSecurityManager()
App.config.setConfiguration(original_config)
transaction.abort()
self.app._p_jar.close()
del self.app
for db in self.conf.databases.values():
db.close()
del self.conf
def testRead(self):
self.assertEqual(self.app.mount1.id, 'mount1')
self.assertEqual(self.app.mount2.id, 'mount2')
self.assertEqual(self.app.i.mount3.id, 'mount3')
def testWrite(self):
app = self.app
app.mount1.a1 = '1'
app.mount2.a2 = '2'
app.a3 = '3'
self.assertEqual(app.mount1._p_changed, 1)
self.assertEqual(app.mount2._p_changed, 1)
self.assertEqual(app._p_changed, 1)
transaction.commit()
self.assertEqual(app.mount1._p_changed, 0)
self.assertEqual(app.mount2._p_changed, 0)
self.assertEqual(app._p_changed, 0)
self.assertEqual(app.mount1.a1, '1')
self.assertEqual(app.mount2.a2, '2')
self.assertEqual(app.a3, '3')
def testGetMountPoint(self):
self.assert_(getMountPoint(self.app) is None)
self.assert_(getMountPoint(self.app.mount1) is not None)
self.assertEqual(getMountPoint(self.app.mount1)._path, '/mount1')
self.assert_(getMountPoint(self.app.mount2) is not None)
self.assertEqual(getMountPoint(self.app.mount2)._path, '/mount2')
self.assertEqual(getMountPoint(self.app.i.mount3)._path, '/i/mount3')
del self.app.mount2
self.app.mount2 = Folder()
self.app.mount2.id = 'mount2'
self.assert_(getMountPoint(self.app.mount2) is None)
transaction.commit()
self.assert_(getMountPoint(self.app.mount2) is None)
def test_manage_getMountStatus(self):
status = manage_getMountStatus(self.app)
expected = [{'status': 'Ok',
'path': '/mount1',
'name': 'test_mount1.fs',
'exists': 1},
{'status': 'Ok',
'path': '/mount2',
'name': 'test_mount2.fs',
'exists': 1},
{'status': 'Ok',
'path': '/i/mount3',
'name': 'test_mount3.fs',
'exists': 1},
]
self.assertEqual(sorted(expected), sorted(status))
del self.app.mount2
status = manage_getMountStatus(self.app)
expected = [{'status': 'Ok',
'path': '/mount1',
'name': 'test_mount1.fs',
'exists': 1},
{'status': 'Ready to create',
'path': '/mount2',
'name': 'test_mount2.fs',
'exists': 0},
{'status': 'Ok',
'path': '/i/mount3',
'name': 'test_mount3.fs',
'exists': 1},
]
self.assertEqual(sorted(expected), sorted(status))
self.app.mount2 = Folder('mount2')
status = manage_getMountStatus(self.app)
expected = [{'status': 'Ok',
'path': '/mount1',
'name': 'test_mount1.fs',
'exists': 1},
{'status': '** Something is in the way **',
'path': '/mount2',
'name': 'test_mount2.fs',
'exists': 1},
{'status': 'Ok',
'path': '/i/mount3',
'name': 'test_mount3.fs',
'exists': 1},
]
self.assertEqual(sorted(expected), sorted(status))
def test_close(self):
app = self.app
app.mount1.a1 = '1'
app.mount2.a2 = '2'
app.a3 = '3'
conn1 = app.mount1._p_jar
conn2 = app.mount2._p_jar
conn3 = app.i.mount3._p_jar
transaction.abort()
# Close the main connection
app._p_jar.close()
self.assertEqual(app._p_jar.opened, None)
# Check that secondary connections have been closed too
self.assertEqual(conn1.opened, None)
self.assertEqual(conn2.opened, None)
self.assertEqual(conn3.opened, None)
def test_suite():
return unittest.makeSuite(MountingTests, 'test')
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add ZODB Mount Points"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Use this form to finalize the mount points configured in zope.conf.
To make more mount points available, edit zope.conf.
</p>
<div tal:define="stats here/manage_getMountStatus">
<div tal:condition="stats">
<form action="manage_addMounts" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<th>
</th>
<th align="left">
Path
</th>
<th align="left">
Database
</th>
<th align="left">
Status
</th>
</tr>
<tr tal:repeat="stat stats">
<td>
<input tal:condition="not: stat/exists" tal:attributes="value stat/path"
type="checkbox" name="paths:list" checked="checked" />
</td>
<td valign="top" nowrap="nowrap">
<span tal:content="stat/path">/virtual_hosts</span>
</td>
<td valign="top" nowrap="nowrap">
<span tal:content="stat/name">Virtual Hosts</span>
</td>
<td valign="top">
<span tal:content="stat/status">Ok</span>
</td>
</tr>
</table>
&nbsp;<br />
<input class="form-element" type="checkbox" name="create_mount_points" />
<span class="form-element">Create new folders if the mounted objects don't yet exist</span>
<br />
<input class="form-element" type="submit" name="submit"
value="Create selected mount points" />
</form>
</div>
<div tal:condition="not:stats">
<em>No mount points configured.</em>
</div>
</div>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure here/manage_page_header" />
<h2 tal:replace="structure here/manage_tabs" />
<h3>Mount Failure Traceback</h3>
<div tal:define="exc here/mount_error_">
<strong>Error type:</strong>
<span tal:replace="python: exc[0]">Error</span><br />
<strong>Error value:</strong>
<span tal:replace="python: exc[1]">An error occurred.</span><br />
<pre tal:content="python: exc[2]">
Traceback
</pre>
</div>
<h1 tal:replace="structure here/manage_page_footer" />
......@@ -23,6 +23,7 @@ Products.PythonScripts = 3.0
Products.Sessions = 4.0
Products.SiteErrorLog = 4.0
Products.StandardCacheManagers = 3.0
Products.TemporaryFolder = 4.0
Products.ZCatalog = 4.0a1
Products.ZCTextIndex = 3.0
pytz = 2016.6.1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment