[Zope3-checkins] CVS: Zope3/src/zope/app/bundle/browser -
__init__.py:1.1 bundle.pt:1.1 configure.zcml:1.1 tests.py:1.1
Stephan Richter
srichter at cosmos.phy.tufts.edu
Wed Mar 10 08:11:14 EST 2004
Update of /cvs-repository/Zope3/src/zope/app/bundle/browser
In directory cvs.zope.org:/tmp/cvs-serv31386/src/zope/app/bundle/browser
Added Files:
__init__.py bundle.pt configure.zcml tests.py
Log Message:
Moved bundles to zope.app.bundle. Noone uses this code, so I did not provide
module aliases.
=== Added File Zope3/src/zope/app/bundle/browser/__init__.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Bundle support.
(See also http://dev.zope.org/Zope3/ThroughTheWebSiteDevelopment .)
A (site-management) bundle is a (site-management) folder with special
status.
Eventually, bundles will be read-only, and the only thing you can do
with bundles is install and uninstall them. At installation time, a
bundle's dependencies and are analized and satisfied, and the
registrations in the bundle are activated, unless they conflict with
existing registrations. This is an interactive process.
XXX This interim code is much less ambitious: it just provides a view
on a (site-management) folder that displays all registrations in a
bundle and lets the user activate them.
$Id: __init__.py,v 1.1 2004/03/10 13:11:13 srichter Exp $
"""
import re
from transaction import get_transaction
from zope.app import zapi
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.container.interfaces import IReadContainer
from zope.app.interfaces.services.registration import \
IRegistration, RegisteredStatus, ActiveStatus, UnregisteredStatus
from zope.app.interfaces.services.service import IServiceRegistration
from zope.component import ComponentLookupError
from zope.publisher.browser import BrowserView
class BundleView(BrowserView):
def __init__(self, context, request):
BrowserView.__init__(self, context, request)
self.mypath = zapi.getPath(self.context)
self.myversion = self.parseVersion(self.mypath)
# Compute sitepath as the parent of mypath
sitepath = zapi.getPath(self.context)
i = sitepath.rfind("/")
if i > 0:
sitepath = sitepath[:i]
elif i == 0:
sitepath = "/"
else:
sitepath = ""
self.sitepath = sitepath
self.registrations = self.findRegistrations(self.context, "")
self.registrations.sort(self.compareRegistrations)
self.services = self.findServices()
# Methods called from the page template (bundle.pt)
def update(self):
if not self.request.form:
return
if zapi.getName(self.context) == "default":
# XXX This is not right: we should be able to tell bundles
# from non-bundles and only allow this command for
# bundles. The Bundle tab should only be present for
# bundles. But for now, we simply prevent the user from
# making a big mistake and changing the default folder.
return "ERROR: Won't change the default folder"
if "allclear" in self.request:
count = 0
for path, obj in self.registrations:
if obj.status != UnregisteredStatus:
obj.status = UnregisteredStatus
count += 1
if count:
get_transaction().note("deactivate bundle")
status = _("unregistered ${count} registrations")
status.mapping = {'count': str(count)}
return status
activated = []
registered = []
for key, value in self.request.form.items():
if value not in (ActiveStatus, RegisteredStatus):
continue
for path, obj in self.registrations:
if key == path:
break
else:
raise ComponentLookupError(key)
for path, obj in self.registrations:
value = self.request.form.get(path)
if value not in (ActiveStatus, RegisteredStatus):
continue
if obj.status != value:
if value == ActiveStatus:
activated.append(path)
obj.status = ActiveStatus
else:
registered.append(path)
obj.status = RegisteredStatus
s = ""
mapping = {}
if activated:
s += _("Activated: ${activated}.\n")
mapping['activated'] = ", ".join(activated)
if registered:
s += _("Registered: ${registered}.\n")
mapping['registered'] = ", ".join(registered)
if s:
get_transaction().note("activate bundle")
# We have to do that again, since the adding to a message id makes it
# a unicode string again.
s = _(s)
s.mapping = mapping
return s
def listServices(self):
infos = []
for name in self.services:
path, insite, inbundle = self.getServiceStatus(name)
d = {"service": name,
"path": path,
"insite": insite,
"inbundle": inbundle}
infos.append(d)
return infos
def listRegistrations(self):
infos = []
for path, obj in self.registrations:
name, advice, conflict = self.getAdvice(obj)
d = {"path": path,
"service": name,
"advice": advice,
"conflict": conflict,
"status": obj.status,
"usage": obj.usageSummary(),
"implementation": obj.implementationSummary()}
infos.append(d)
return infos
# The rest are helper methods
def getServiceStatus(self, name):
try:
svc = zapi.getService(self.context, name)
except:
svc = None
path = ""
insite = False
if svc:
try:
path = zapi.getPath(svc)
except:
pass
else:
insite = (path == self.sitepath or
path.startswith(self.sitepath + "/"))
inbundle = self.findServiceRegistration(name)
return path, insite, inbundle
def getAdvice(self, obj):
name = self.getServiceName(obj)
advice = ActiveStatus
conflict = ""
sm = zapi.getServiceManager(obj)
service = sm.queryLocalService(name)
if service:
registry = service.queryRegistrationsFor(obj)
if registry:
active = registry.active()
if active and active != obj:
conflict = zapi.getPath(active)
if not self.inOlderVersion(active):
advice = RegisteredStatus
return name, advice, conflict
def inOlderVersion(self, obj):
# Return whether obj (an active component) belongs to an older
# version of the same bundle we're proposing to activate here.
# XXX This assumes sites are named with ++etc++site; there is
# no support for the older ++etc++Services.
path = zapi.getPath(obj)
prefix = "/++etc++site/"
i = path.rfind(prefix) # (can the prefix occur twice?)
if i < 0:
return False
i += len(prefix) # points just after the second "/"
i = path.find("/", i) # finds next slash after that
if i >= 0:
path = path[:i]
# Now path is of the form ".../++etc++site/name-version"
version = self.parseVersion(path)
if not version:
return False
i = path.rfind("-") + 1
return self.mypath[:i] == path[:i] and self.myversion > version
nineDigits = re.compile(r"^\d{1,9}$")
def parseVersion(self, path):
# Return a list containing the version numbers, suitably
# modified for sane version comparison. If there is no
# version number, return None. A version number is any number
# of dot-separated integers of at most 9 digits, optionally
# followed by another dot and something like "a1" or "b1"
# indicating an alpha or beta version. If no alpha or beta
# version is present, "f" is assumed (indicating "final").
# ("f" is chosen to compare higher than "a1", "b1" or "c1" but
# lower than "p1"; "p1" is sometimes used to indicate a patch
# release.) Examples:
#
# "/foo/bar-boo" -> None
# "/foo/bar-boo-1.0" -> ["f000000001", "f000000000", "f"]
# "/foo/bar-boo-1.0.f" -> ["f000000001", "f000000000", "f"]
# "/foo/bar-boo-1.0.a1" -> ["f000000001", "f000000000", "a1"]
#
# Note that we do a string compare on the alpha/beta version
# number; "a10" will compare less than "a2". OTOH, the
# integers are padded with leading zeros, so "10" will compare
# higher than "2".
i = path.rfind("/") + 1
base = path[i:]
i = base.rfind("-") + 1
if not i:
return None # No version
version = base[i:]
parts = version.split(".")
last = parts[-1]
if self.nineDigits.match(last):
last = "f"
else:
last = last.lower()
del parts[-1]
if not parts:
return None
for i in range(len(parts)):
p = parts[i]
if not self.nineDigits.match(p):
return None
parts[i] = "f" + "0"*(9-len(p)) + p
parts.append(last)
return parts
def findServiceRegistration(self, name):
for path, obj in self.registrations:
if IServiceRegistration.providedBy(obj):
if obj.name == name:
return path
return None
def findRegistrations(self, f, prefix):
alist = []
for name, obj in f.items():
if IRegistration.providedBy(obj):
alist.append((prefix+name, obj))
elif IReadContainer.providedBy(obj):
alist.extend(self.findRegistrations(obj, prefix+name+"/"))
return alist
def compareRegistrations(self, c1, c2):
path1, obj1 = c1
path2, obj2 = c2
t1 = (self.getAdjustedServiceName(obj1),
obj1.usageSummary(),
obj1.implementationSummary())
t2 = (self.getAdjustedServiceName(obj2),
obj2.usageSummary(),
obj2.implementationSummary())
return cmp(t1, t2)
def findServices(self):
sd = {}
for path, obj in self.registrations:
sd[self.getServiceName(obj)] = 1
services = sd.keys()
services.sort(self.compareServiceNames)
return services
def compareServiceNames(self, n1, n2):
return cmp(self.adjustServiceName(n1), self.adjustServiceName(n2))
def getAdjustedServiceName(self, registration):
name = self.getServiceName(registration)
return self.adjustServiceName(name)
def adjustServiceName(self, name):
# XXX Strange... There's no symbol for it in servicenames.py
if name == "Services":
return ""
else:
return name
def getServiceName(self, registration):
# Return the service associated with a registration.
return registration.serviceType
=== Added File Zope3/src/zope/app/bundle/browser/bundle.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<title metal:fill-slot="title" i18n:translate="">Bundle Information</title>
</head>
<body>
<div metal:fill-slot="body" tal:define="message view/update">
<h1 i18n:translate="">Bundle Information</h1>
<tal:block define="serviceList view/listServices">
<h4 i18n:translate="">Services needed by this bundle</h4>
<ul tal:condition="serviceList">
<li tal:repeat="svc serviceList">
<span i18n:translate="">
<i tal:content="svc/service" i18n:name="service_name">Foo</i> service
</span>:
<span tal:condition="svc/insite" i18n:translate="">
present in site at
<a tal:content="svc/path" tal:attributes="href svc/path"
i18n:name="path">/path</a>
</span>
<span tal:condition="not:svc/insite">
<span tal:condition="svc/inbundle" i18n:translate="">
registered in bundle at
<a tal:content="svc/inbundle"
tal:attributes="href svc/inbundle" i18n:name="path">path</a>
</span>
<span tal:condition="not:svc/inbundle">
<font size="+1" color="red">
<b i18n:translate="">UNFULFILLED DEPENDENCY</b>
</font>
<br />
<b i18n:translate="">
(You must <a href="../default/AddService">add a
<i tal:content="svc/service" i18n:name="service_name">Foo</i>
service to this site</a> before you can activate this bundle)
</b>
</span>
</span>
</li>
</ul>
<p tal:condition="not:serviceList" i18n:translate="">
No services are required by this bundle.
</p>
<h4 i18n:translate="">Registrations in this bundle</h4>
<div class="message" tal:condition="message">
<span tal:replace="message">view/update message here</span>
<br /><br />
<i>
<a href="@@bundle.html" i18n:translate="">(click to clear message)</a>
</i>
</div>
<form action="@@bundle.html" method="get"
tal:define="registrationList view/listRegistrations">
<tal:block tal:repeat="svc serviceList">
<p i18n:translate="">For
<i tal:content="svc/service" i18n:name="service_name">Foo</i> service
</p>
<ul>
<tal:block tal:repeat="cnf registrationList">
<li tal:condition="python: cnf['service'] == svc['service']"
tal:define="activate python:cnf['advice'] == 'Active'">
<a tal:attributes="href cnf/path" i18n:translate="">
<i tal:content="cnf/usage" i18n:name="usage_summary">Usage</i>
implemented by
<i tal:content="cnf/implementation" i18n:name="impl_summary">
Implementation summary</i>
</a>
<span tal:condition="cnf/conflict">
<br />
<font color="red" tal:condition="not:activate"
i18n:translate="">
Conflicts with
</font>
<font color="green" tal:condition="activate" i18n:translate="">
Overrides
</font>
<a tal:content="cnf/conflict"
tal:attributes="href cnf/conflict">path</a>
</span>
<br />
<span tal:condition="python: cnf['advice'] == 'Active'">
<input type="radio" tal:attributes="name cnf/path"
value="Registered" />
<span i18n:translate="">Register only</span>
<b>
<input type="radio" tal:attributes="name cnf/path"
value="Active" checked="checked" />
<span i18n:translate="">Register and activate</span>
</b>
</span>
<span tal:condition="python: cnf['advice'] == 'Registered'">
<b>
<input type="radio" tal:attributes="name cnf/path"
value="Registered" checked="checked" />
<span i18n:translate="">Register only</span>
</b>
<input type="radio" tal:attributes="name cnf/path"
value="Active" />
<span i18n:translate="">Register and activate</span>
</span>
<span i18n:translate="">
(is: <span tal:replace="cnf/status"
i18n:name="active_status">Active</span>)
</span>
</li>
</tal:block>
</ul>
</tal:block>
<p tal:condition="not:registrationList" i18n:translate="">
No registrations are provided by this bundle.
</p>
<p i18n:translate="">
Click "Activate bundle" to perform the above actions.
</p>
<p><input type="submit" value="Activate bundle"
i18n:attributes="value activate-bundle-button"/></p>
<p><input type="reset" value="Reset form"
i18n:attributes="value reset-button"/></p>
</form>
</tal:block>
<form action="@@bundle.html" method="get">
<p i18n:translate="">Click "Deactivate bundle" to unregister all
registrations in this bundle.</p>
<p><input type="submit" value="Deactivate bundle"
i18n:attributes="value deactivate-bundle-button"/></p>
<input type="hidden" name="allclear" value="1" />
</form>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/bundle/browser/configure.zcml ===
<zope:configure
xmlns:zope="http://namespaces.zope.org/zope"
xmlns="http://namespaces.zope.org/browser">
<page
for="zope.app.bundle.interfaces.IBundle"
permission="zope.ManageServices"
class="zope.app.browser.container.contents.JustContents"
name="index.html" attribute="index" />
<page
name="contents.html"
for="zope.app.bundle.interfaces.IBundle"
menu="zmi_views" title="Contents"
permission="zope.ManageServices"
class="zope.app.browser.container.contents.Contents"
attribute="contents" />
<page
name="bundle.html"
for="zope.app.bundle.interfaces.IBundle"
menu="zmi_views" title="Bundle"
permission="zope.ManageServices"
class=".BundleView"
template="bundle.pt" />
</zope:configure>
=== Added File Zope3/src/zope/app/bundle/browser/tests.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Unit tests for BundleView class.
XXX Incomplete.
$Id: tests.py,v 1.1 2004/03/10 13:11:13 srichter Exp $
"""
import unittest
from zope.interface import implements
from zope.app.interfaces.traversing import IPhysicallyLocatable
from zope.app.tests.placelesssetup import PlacelessSetup
from zope.app.bundle.browser import BundleView
class SampleClass(object):
implements(IPhysicallyLocatable)
def __init__(self, path="/foo"):
self.path = path
def getPath(self):
return self.path
def items(self):
return []
class TestBundleView(PlacelessSetup, unittest.TestCase):
def setUp(self):
super(TestBundleView, self).setUp()
def test_parseVersion(self):
bv = BundleView(SampleClass(), None)
pv = bv.parseVersion
self.assertEquals(pv("/foo/bar-666"), ["f000000666", "f"])
self.assertEquals(pv("/foo/bar-1.0"),
["f000000001", "f000000000", "f"])
self.assertEquals(pv("foo-bar-2.3.4"),
["f000000002", "f000000003", "f000000004", "f"])
self.assertEquals(pv("bar-5.6.a7"), ["f000000005", "f000000006", "a7"])
self.assertEquals(pv("foo"), None)
self.assertEquals(pv("foo-bar"), None)
self.assertEquals(pv("foo.1.0"), None)
self.assertEquals(pv("foo-1.a1.0"), None)
def test_inOlderVersion(self):
bv = BundleView(SampleClass("/++etc++site/foo-bar-1.0.0"), None)
iov = bv.inOlderVersion
self.failUnless(iov(SampleClass("/++etc++site/foo-bar-0.9/RM/1")))
self.failIf(iov(SampleClass("/++etc++site/bar-foo-0.9/RM/2")))
self.failIf(iov(SampleClass("/++etc++site/foo-bar-1.1/RM/3")))
def test_listServices(self):
bv = BundleView(SampleClass("/++etc++site/foo-bar-1.0.0"), None)
infos = bv.listServices()
self.assertEquals(infos, [])
def test_listRegistrations(self):
bv = BundleView(SampleClass("/++etc++site/foo-bar-1.0.0"), None)
infos = bv.listRegistrations()
self.assertEquals(infos, [])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestBundleView))
return suite
if __name__ == '__main__':
unittest.main()
More information about the Zope3-Checkins
mailing list