merged Dojo 1.0 branch

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2387 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2008-02-10 09:56:27 +00:00
parent 3f704eb6f9
commit 7ec9bbdc15
32 changed files with 597 additions and 231 deletions

View file

@ -1,3 +1,7 @@
""" """
$Id$ $Id$
""" """
from zope.app.pagetemplate import ViewPageTemplateFile
dojoMacroTemplate = ViewPageTemplateFile('macros.pt')

View file

@ -6,7 +6,6 @@
i18n_domain="zope"> i18n_domain="zope">
<resourceDirectory name="ajax.dojo" directory="dojo" /> <resourceDirectory name="ajax.dojo" directory="dojo" />
<!-- <resourceDirectory name="ajax.dojo1" directory="dojo1" /> -->
<page <page
for="*" for="*"

View file

@ -1,12 +1,8 @@
<metal:def define-macro="main"> <metal:def define-macro="main">
<script type="text/javascript">
djConfig = { isDebug: true,
parseOnLoad: true };
</script>
<script type="text/javascript" src="ajax.dojo/dojo.js" <script type="text/javascript" src="ajax.dojo/dojo.js"
tal:attributes="src context/++resource++ajax.dojo/dojo.js"> tal:attributes="src context/++resource++ajax.dojo/dojo/dojo.js;
djConfig macro/djConfig|nothing">
</script> </script>
</metal:def> </metal:def>

View file

@ -1,3 +1,4 @@
==================
Browser View Tools Browser View Tools
================== ==================
@ -5,8 +6,9 @@ Browser View Tools
>>> from zope.interface import Interface, implements >>> from zope.interface import Interface, implements
>>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.publisher.interfaces.browser import IBrowserRequest
The Generic View class The Generic View class
---------------------- ======================
GenericView is intended as the base class for application-specific views. GenericView is intended as the base class for application-specific views.
The GenericView class itself provides only basic functionality, so you The GenericView class itself provides only basic functionality, so you
@ -64,7 +66,7 @@ bodyTemplate attribute.
The View Controller The View Controller
------------------- ===================
There is a special view class that does not directly adapt to a real context There is a special view class that does not directly adapt to a real context
(i.e. typically a content) object but to a view instead. Thus it can provide (i.e. typically a content) object but to a view instead. Thus it can provide
@ -131,10 +133,11 @@ Calling a macro provided by Controller.macros[] returns the real ZPT macro:
The pre-set collection of macros for a certain slot may be extended The pre-set collection of macros for a certain slot may be extended
(this may be done by overriding the view's setupController() method, e.g.): (this may be done by overriding the view's setupController() method, e.g.):
>>> controller.macros.register('css', 'node.css', resourceName='node.css', media='all') >>> controller.macros.register('css', 'node.css', resourceName='node.css',
... media='all', priority=110)
>>> len(controller.macros['css']) >>> len(controller.macros['css'])
5 5
>>> m5 = cssMacros[4] >>> m5 = controller.macros['css'][4]
>>> print m5.name, m5.media, m5.resourceName >>> print m5.name, m5.media, m5.resourceName
css all node.css css all node.css
@ -158,7 +161,7 @@ We can also access slots that are not predefined:
The View Configurator The View Configurator
--------------------- =====================
A view configurator is typically a multiadapter for a content object that provides A view configurator is typically a multiadapter for a content object that provides
a set of properties to be used for setting up special presentation a set of properties to be used for setting up special presentation
@ -167,9 +170,9 @@ characteristics of a page. Typical examples for such characteristics are
- the skin to be used - the skin to be used
- the logo to show in the corner of the page - the logo to show in the corner of the page
The default configurator uses attribute annotations for retrieving view There is a standard configurator that uses attribute annotations for
properties; that means that there could be a form somewhere to edit those retrieving view properties; that means that there could be a form somewhere
properties and store them in the content object's annotations. to edit those properties and store them in the content object's annotations.
>>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations >>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
>>> from zope.annotation.attribute import AttributeAnnotations >>> from zope.annotation.attribute import AttributeAnnotations
@ -179,8 +182,8 @@ The configurator is called automatically from the controller if there is
an appropriate adapter: an appropriate adapter:
>>> from cybertools.browser.configurator import IViewConfigurator >>> from cybertools.browser.configurator import IViewConfigurator
>>> from cybertools.browser.configurator import ViewConfigurator >>> from cybertools.browser.configurator import AnnotationViewConfigurator
>>> component.provideAdapter(ViewConfigurator, (SomeObject, IBrowserRequest), >>> component.provideAdapter(AnnotationViewConfigurator, (SomeObject, IBrowserRequest),
... IViewConfigurator) ... IViewConfigurator)
>>> controller = Controller(view, request) >>> controller = Controller(view, request)
@ -197,18 +200,9 @@ stored in the attribute annotations. So let's set a 'skinName' attribute:
>>> controller.skinName.value >>> controller.skinName.value
'SuperSkin' 'SuperSkin'
Another way of providing view configurations is using a view configurator
as a utility, this can be used for setting view properties by certain
packages.
>>> from cybertools.browser.configurator import GlobalViewConfigurator
>>> component.provideUtility(GlobalViewConfigurator())
>>> gvc = component.getUtility(IViewConfigurator)
Processing form input Processing form input
--------------------- =====================
GenericView also provides an update() method that may be called from GenericView also provides an update() method that may be called from
templates that might receive form information. templates that might receive form information.

117
browser/action.py Normal file
View file

@ -0,0 +1,117 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base classes (sort of views) for action portlet items.
$Id$
"""
from copy import copy
from urllib import urlencode
from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
action_macros = ViewPageTemplateFile('action_macros.pt')
class Action(object):
template = action_macros
macroName = 'action'
priority = 50
condition = True
permission = None
url = '.'
viewName = ''
targetWindow = ''
title = ''
description = ''
icon = ''
cssClass = ''
onClick = ''
innerHtmlId = ''
prerequisites = []
def __init__(self, view, **kw):
self.view = view
for k, v in kw.items():
setattr(self, k, v)
@Lazy
def macro(self):
return self.template.macros[self.macroName]
@Lazy
def url(self):
return self.getActionUrl(self.view.url)
def getActionUrl(self, baseUrl):
if self.viewName:
return '/'.join((baseUrl, self.viewName))
else:
return baseUrl
class ActionRegistry(object):
""" Use this object (probably as a global utility) to collect all kinds
of action definitions that should be available on the system.
"""
def __init__(self):
self.actionsByName = {}
self.actionsByCategory = {}
def register(self, name, category='object', cls=Action, **kw):
action = cls(None, name=name, category=category, **kw)
nameItem = self.actionsByName.setdefault(name, [])
nameItem.append(action)
catItem = self.actionsByCategory.setdefault(category, [])
catItem.append(action)
def get(self, category=None, names=[], view=None, **kw):
if view is None:
raise ValueError("The 'view' argument is missing.")
if names:
result = []
for n in names:
result.extend(self.actionsByName.get(n, []))
if category is not None:
result = [r for r in result if r.category == category]
elif category is not None:
result = self.actionsByCategory.get(category, [])
else:
raise ValueError("One of 'name' or 'category' arguments must be given.")
for action in sorted(result, key=lambda x: x.priority):
action = copy(action)
action.view = view
for k, v in kw.items():
setattr(action, k, v)
for p in action.prerequisites:
method = p
if isinstance(method, str):
method = getattr(view, p, None)
if method is not None:
method()
yield action
# TODO: register as a global utility
actions = ActionRegistry()

21
browser/action_macros.pt Normal file
View file

@ -0,0 +1,21 @@
<!-- action macros -->
<metal:action define-macro="action">
<div tal:condition="action/condition">
<a href="#" target="target_window" title="Description text"
tal:attributes="href action/url;
target action/targetWindow;
title action/description;
onClick action/onClick;"
i18n:attributes="title"><img src="#" alt="icon"
tal:condition="action/icon"
tal:attributes="src string:$resourceBase${action/icon};
alt action/description" />
<span i18n:translate=""
tal:condition="action/title"
tal:content="action/title">Action Title</span></a>
</div>
<span id="inner.Id"
tal:condition="action/innerHtmlId"
tal:attributes="id action/innerHtmlId"></span>
</metal:action>

View file

@ -29,12 +29,7 @@
</metal:js> </metal:js>
<metal:portlet define-macro="multi_actions"> <!-- portlets and similar collections of actions -->
<tal:sub repeat="macro macro/subMacros">
<metal:sub use-macro="macro" />
</tal:sub>
</metal:portlet>
<metal:portlet define-macro="portlet_left"> <metal:portlet define-macro="portlet_left">
<div metal:use-macro="macro/template/macros/portlet" /> <div metal:use-macro="macro/template/macros/portlet" />
@ -45,11 +40,27 @@
</metal:portlet> </metal:portlet>
<metal:portlet define-macro="portlet"> <metal:portlet define-macro="portlet">
<div class="box"> <div class="box"
<h4 tal:content="macro/title" tal:define="icon macro/icon|nothing;
i18n:translate="">Navigation</h4> url macro/url|nothing">
<h4>
<a tal:omit-tag="not:url"
tal:attributes="href url"
i18n:translate=""><img
tal:condition="icon"
tal:attributes="src string:$resourceBase$icon"/>
<span tal:content="macro/title">Navigation</span></a>
</h4>
<div class="body"> <div class="body">
<div metal:use-macro="macro/subMacro" /> <div metal:use-macro="macro/subMacro" />
</div> </div>
</div> </div>
</metal:portlet> </metal:portlet>
<metal:portlet define-macro="multi_actions">
<tal:sub repeat="macro macro/subMacros">
<metal:sub use-macro="macro" />
</tal:sub>
</metal:portlet>

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -22,12 +22,11 @@ A view configurator provides configuration data for a view controller.
$Id$ $Id$
""" """
from zope.app import zapi from zope import component
from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
from zope.annotation.attribute import AttributeAnnotations from zope.annotation.attribute import AttributeAnnotations
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.interface import Interface, Attribute, implements from zope.interface import Interface, Attribute, implements
from zope.component import adapts
# interfaces # interfaces
@ -64,15 +63,28 @@ class IMacroViewProperty(IViewProperty):
#default implementations #default implementations
ANNOTATION_KEY = 'cybertools.browser.configurator.ViewConfigurator'
class ViewConfigurator(object): class ViewConfigurator(object):
""" Simple/basic default adapter using attribute annotations as storage """ An base class for adapters that allow the registration of view properties.
for view properties.
""" """
implements(IViewConfigurator) implements(IViewConfigurator)
def __init__(self, context, request):
self.context = context
self.request = request
self.viewProperties = []
def getActiveViewProperties(self):
return self.viewProperties
ANNOTATION_KEY = 'cybertools.browser.configurator.ViewConfigurator'
class AnnotationViewConfigurator(ViewConfigurator):
""" Simple adapter using attribute annotations as storage
for view properties.
"""
def __init__(self, context, request): def __init__(self, context, request):
self.context = context self.context = context
self.request = request self.request = request
@ -83,19 +95,12 @@ class ViewConfigurator(object):
propDefs = ann.get(ANNOTATION_KEY, {}) propDefs = ann.get(ANNOTATION_KEY, {})
return [self.setupViewProperty(prop, propDef) return [self.setupViewProperty(prop, propDef)
for prop, propDef in propDefs.items() if propDef] for prop, propDef in propDefs.items() if propDef]
# idea: include properties from GlobalViewConfigurator;
# there also may be other view configurators e.g. based on
# the class (or some sort of type) of the context object.
# Also the view properties may be filtered by permission
# or other conditions.
# Note: collecting configurators may be solved by getting
# multiple configurators (+ utilities) in the controller!
def getActiveViewProperties(self): def getActiveViewProperties(self):
return self.viewProperties return self.viewProperties
def setupViewProperty(self, prop, propDef): def setupViewProperty(self, prop, propDef):
vp = zapi.queryMultiAdapter((self.context, self.request), vp = component.queryMultiAdapter((self.context, self.request),
IViewProperty, name=prop) IViewProperty, name=prop)
if vp is None: if vp is None:
vp = ViewProperty(self.context, self.request) vp = ViewProperty(self.context, self.request)
@ -104,19 +109,6 @@ class ViewConfigurator(object):
return vp return vp
class GlobalViewConfigurator(object):
""" A global utility that allows the registration of view properties.
"""
implements(IViewConfigurator)
def __init__(self):
self.viewProperties = []
def getActiveViewProperties(self):
return self.viewProperties
class ViewProperty(object): class ViewProperty(object):
implements(IViewProperty) implements(IViewProperty)
@ -136,7 +128,7 @@ class ViewProperty(object):
self.params = params self.params = params
class MacroViewProperty(object): class MacroViewProperty(ViewProperty):
implements(IMacroViewProperty) implements(IMacroViewProperty)

View file

@ -6,6 +6,8 @@
i18n_domain="zope" i18n_domain="zope"
> >
<resourceDirectory name="cybertools.icons" directory="icons" />
<page for="*" <page for="*"
name="main.html" name="main.html"
template="main.pt" template="main.pt"
@ -25,10 +27,16 @@
permission="zope.Public" permission="zope.Public"
/> />
<page name="controller" <!--<page name="controller"
for="zope.publisher.interfaces.browser.IBrowserView" for="zope.publisher.interfaces.browser.IBrowserView"
class="cybertools.browser.controller.Controller" class="cybertools.browser.controller.Controller"
permission="zope.Public" permission="zope.Public"
/>-->
<zope:adapter
for="* zope.publisher.interfaces.browser.IBrowserRequest"
factory="cybertools.browser.member.MemberInfoProvider"
permission="zope.Public"
/> />
<!-- a tableless layout skin --> <!-- a tableless layout skin -->

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -27,6 +27,8 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from cybertools.browser.configurator import IViewConfigurator, IMacroViewProperty from cybertools.browser.configurator import IViewConfigurator, IMacroViewProperty
from cybertools.browser.member import IMemberInfoProvider
from cybertools.util.jeep import Jeep
# layout controller: collects information about head elements, skins, portlets, etc # layout controller: collects information about head elements, skins, portlets, etc
@ -54,15 +56,11 @@ class Controller(object):
return self.request.URL[0] + skinSetter + '/@@/' return self.request.URL[0] + skinSetter + '/@@/'
def configure(self): def configure(self):
#configurator = component.queryMultiAdapter((self.context, self.request), # collect multiple configurators:
# IViewConfigurator)
# idea: collect multiple configurators:
configurators = component.getAdapters((self.context, self.request), configurators = component.getAdapters((self.context, self.request),
IViewConfigurator) IViewConfigurator)
for conf in configurators: for conf in configurators:
configurator = conf[1] configurator = conf[1]
#if configurator is not None:
#for item in configurator.viewProperties:
for item in configurator.getActiveViewProperties(): for item in configurator.getActiveViewProperties():
if IMacroViewProperty.providedBy(item): if IMacroViewProperty.providedBy(item):
self.macros.register(item.slot, item.idenitifier, self.macros.register(item.slot, item.idenitifier,
@ -71,6 +69,12 @@ class Controller(object):
else: else:
setattr(self, item.slot, item) setattr(self, item.slot, item)
@Lazy
def memberInfo(self):
provider = component.queryMultiAdapter((self.context, self.request),
IMemberInfoProvider)
return provider is not None and provider.data or None
class Macros(dict): class Macros(dict):
@ -81,7 +85,7 @@ class Macros(dict):
self.identifiers = set() self.identifiers = set()
def register(self, slot, identifier=None, template=None, name=None, def register(self, slot, identifier=None, template=None, name=None,
position=None, **kw): priority=50, **kw):
if identifier: if identifier:
# make sure a certain resource is only registered once # make sure a certain resource is only registered once
if identifier in self.identifiers: if identifier in self.identifiers:
@ -91,22 +95,20 @@ class Macros(dict):
template = self.standardTemplate template = self.standardTemplate
if name is None: if name is None:
name = slot name = slot
macro = Macro(template, name, **kw) macro = Macro(template, name, priority, **kw)
entry = self.setdefault(slot, []) entry = self.setdefault(slot, [])
if position is None: entry.append(macro)
entry.append(macro)
else:
entry.insert(position, macro)
def __getitem__(self, key): def __getitem__(self, key):
return self.get(key, []) return list(sorted(self.get(key, []), key=lambda x: x.priority))
class Macro(object): class Macro(object):
def __init__(self, template, name, **kw): def __init__(self, template, name, priority, **kw):
self.template = template self.template = template
self.name = name self.name = name
self.priority = priority
for k in kw: for k in kw:
setattr(self, k, kw[k]) setattr(self, k, kw[k])

BIN
browser/icons/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

View file

@ -28,75 +28,4 @@ body {
#menu {width:20%} #menu {width:20%}
#content {width:62%} #content {width:62%}
#sub-section {width:17%} #sub-section {width:17%}
#footer {clear:left} #footer {clear:left; float:left}
/* more general stuff */
.top image {
margin-top: -1px;
}
div.box {
margin: 12px 12px 8px 10px;
border: 1px solid #ccc;
border-bottom: none;
}
div.box h4 {
font: 110% Verdana, Tahoma, Arial, Helvetica, sans-serif;
color: #000040;
border: none;
border-bottom: 1px solid #ccc;
padding: 4px;
padding-top: 1px;
padding-bottom: 3px;
background-color: #ddd;
height: auto;
}
table.listing {
margin: 1px;
margin-top: 6px;
}
table.listing th {
font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;
color: #000040;
}
.footer {
text-align: center;
border-top: 1px solid #ccc;
border-bottom: none;
margin-top: 12px;
padding-top: 6px;
}
.itemViews {
border-bottom-width: 2px;
}
.button {
margin: 1em 0 1em 0;
}
.button a, .button a:visited {
padding: 2px 4px 2px 4px;
background-color: #e8e8e8;
text-decoration: None;
color: Black;
border-width: 2px;
border-style: solid;
border-color: #f4f4f4 #989898 #989898 #f4f4f4;
}
.button a:active {
border-color: #989898 #f4f4f4 #f4f4f4 #989898;
}
pre {
background-color: #f4f4f4;
}
#footer { border-bottom: none; }

View file

@ -43,6 +43,8 @@
layer="cybertools.browser.liquid.Liquid" /> layer="cybertools.browser.liquid.Liquid" />
<resource name="print.css" file="print.css" <resource name="print.css" file="print.css"
layer="cybertools.browser.liquid.Liquid" /> layer="cybertools.browser.liquid.Liquid" />
<resource name="presentation.css" file="presentation.css"
layer="cybertools.browser.liquid.Liquid" />
<resource name="custom.css" file="custom.css" <resource name="custom.css" file="custom.css"
layer="cybertools.browser.liquid.Liquid" /> layer="cybertools.browser.liquid.Liquid" />

View file

@ -28,19 +28,26 @@ from cybertools.browser.controller import Controller as BaseController
class Controller(BaseController): class Controller(BaseController):
def __init__(self, context, request): def __init__(self, context, request):
self.view = view = context # the controller is adapted to a view
self.context = context.context
self.request = request
self.setupCss() self.setupCss()
self.setupJs() self.setupJs()
super(Controller, self).__init__(context, request) super(Controller, self).__init__(context, request)
def setupCss(self): def setupCss(self):
macros = self.macros macros = self.macros
params = [('zope3_tablelayout.css', 'all'), presentationMode = self.request.get('liquid.viewmode') == 'presentation'
('base.css', 'screen'), params = [('zope3_tablelayout.css', 'all', 20),
('print.css', 'print'), ('base.css', 'screen', 25),
('custom.css', 'all')] ('print.css', 'print', 30),
('custom.css', 'all', 100)]
if presentationMode:
params.append(('presentation.css', 'all', 30))
for param in params: for param in params:
macros.register('css', identifier=param[0], macros.register('css', identifier=param[0],
resourceName=param[0], media=param[1]) resourceName=param[0], media=param[1],
priority=param[2])
def setupJs(self): def setupJs(self):
return return

View file

@ -0,0 +1,21 @@
/*
$Id$
*/
.body {
margin: 4em;
}
.top, #header, #menu, #sub-section, #footer, #xedit_icon {
display: none;
}
#content {
width: 100%;
color: #000077;
}
h1, h2, h3, h4, h5 {
color: #005599;
}

View file

@ -1,10 +1,8 @@
/* /*
** Zope3 style sheet for CSS2-capable browsers. ** Zope3 style sheet for CSS2-capable browsers.
** For future skin see zope.app.boston. **
*/ ** $Id$
**
/*
* { border: 1px dotted red }
*/ */
@ -31,7 +29,7 @@ table {
font-size: 100%; font-size: 100%;
} }
a { a[href] {
text-decoration: none; text-decoration: none;
color: #369; color: #369;
background-color: transparent; background-color: transparent;
@ -46,11 +44,6 @@ img {
vertical-align: middle; vertical-align: middle;
} }
p {
margin: 0.5em 0em 1em 0em;
line-height: 1.5em;
}
p a:visited { p a:visited {
color: Purple; color: Purple;
background-color: transparent; background-color: transparent;
@ -80,7 +73,7 @@ h1, h2, h3, h4, h5, h6 {
clear: left; clear: left;
font: 100% bold Verdana, Helvetica, Arial, sans-serif; font: 100% bold Verdana, Helvetica, Arial, sans-serif;
margin: 0; margin: 0;
padding-top: 0.5em; padding-top: 0;
border-bottom: 1px solid #369; border-bottom: 1px solid #369;
} }
@ -109,7 +102,7 @@ h6 {
} }
ul { ul {
line-height: 1.5em; line-height: 1.2em;
/* list-style-image: url("bullet.gif"); */ /* list-style-image: url("bullet.gif"); */
margin-left: 2em; margin-left: 2em;
padding:0; padding:0;
@ -411,9 +404,6 @@ div.box h4 {
} }
#content {
}
#context_information { #context_information {
padding-top: 1em; padding-top: 1em;
width: 15%; width: 15%;
@ -425,18 +415,6 @@ div.box h4 {
width: 100%; width: 100%;
} }
#helpers {
}
#inspectors {
}
#footer {
border-bottom: 1px solid black;
float: left;
clear: both;
}
input.textType { input.textType {
width: 88%; /* Same as textarea */ width: 88%; /* Same as textarea */
} }

View file

@ -32,7 +32,8 @@
<base href="." tal:attributes="href request/URL"> <base href="." tal:attributes="href request/URL">
</head> </head>
<body tal:content="structure body" /> <body class="tundra"
tal:content="structure body" />
</html> </html>
</metal:block> </metal:block>

111
browser/member.py Normal file
View file

@ -0,0 +1,111 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
A member information provider is used to collect user/member/person attributes.
$Id$
"""
from zope import component
from zope.app.security.interfaces import IAuthentication
from zope.cachedescriptors.property import Lazy
from zope.interface import Interface, Attribute, implements
from cybertools.util.jeep import Jeep
# interfaces
class IMemberInfoProvider(Interface):
""" Usually implemented by an adapter; provides a set of
user/member/person properties.
"""
priority = Attribute('A number denoting the priority of the provider; '
'a provider with a high number may be overriden with a lower number.')
data = Attribute('A collection/ordered mapping of member property objects '
'for the currently logged-in user.')
def getData(principalId):
""" Return the member properties for the principal identified by
the principal id given.
"""
def getDataForCategory(category, principalId=None):
""" Return a collection of the properties for the category given.
If no principal id is given use the currently logged-in user.
"""
class IMemberProperty(Interface):
name = Attribute('The name/identifier of the property.')
title = Attribute('A short and descriptive title.')
category = Attribute('A string denoting a category or classification.')
#default implementation
class MemberProperty(object):
implements(IMemberProperty)
def __init__(self, name, value, title=None, category='default'):
self.name = name
self.value = value
self.title = title or name
self.category = category
class MemberInfoProvider(object):
implements(IMemberInfoProvider)
defaultData = Jeep((MemberProperty('id', '???', u'ID'),
MemberProperty('title', u'unknown', u'Title'),
MemberProperty('description', u'',
u'Description'),
))
def __init__(self, context, request):
self.context = context
self.request = request
@Lazy
def data(self):
return self.getData()
def getData(self, principalId=None):
if principalId is None:
principal = self.request.principal
else:
pau = component.getUtility(IAuthentication)
principal = pau.getPrincipal(principalId)
if principal is not None:
return self.getPrincipalData(principal)
else:
return self.defaultData
def getPrincipalData(self, principal):
return Jeep((MemberProperty('id', principal.id, u'ID'),
MemberProperty('title', principal.title, u'Title'),
MemberProperty('description', principal.description,
u'Description'),
))

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -58,14 +58,19 @@ class GenericView(object):
# make the (one and only controller) available via the request # make the (one and only controller) available via the request
viewAnnotations = self.request.annotations.setdefault('cybertools.browser', {}) viewAnnotations = self.request.annotations.setdefault('cybertools.browser', {})
viewAnnotations['controller'] = controller viewAnnotations['controller'] = controller
if getattr(controller, 'skinName', None) and controller.skinName.value: #if getattr(controller, 'skinName', None) and controller.skinName.value:
self.setSkin(controller.skinName.value) # self.setSkin(controller.skinName.value)
controller.skin = self.skin controller.skin = self.skin
# this is the place to register special macros with the controller: # this is the place to register special macros with the controller:
self.setupController() self.setupController()
def getController(self): def getController(self):
viewAnnotations = self.request.annotations.setdefault('cybertools.browser', {}) viewAnnotations = self.request.annotations.setdefault('cybertools.browser', {})
return viewAnnotations.get('controller', None) cont = viewAnnotations.get('controller', None)
if cont is None:
cont = component.queryMultiAdapter((self, self.request), name='controller')
if cont is not None:
self.setController(cont)
return cont
controller = property(getController, setController) controller = property(getController, setController)
def __init__(self, context, request): def __init__(self, context, request):
@ -119,4 +124,3 @@ class GenericView(object):
applySkin(self.request, skin) applySkin(self.request, skin)
self.skin = skin self.skin = skin

View file

@ -167,5 +167,6 @@ Macros / renderers
>>> fieldRenderers = form.fieldRenderers >>> fieldRenderers = form.fieldRenderers
>>> sorted(fieldRenderers.keys()) >>> sorted(fieldRenderers.keys())
[u'field', u'field_spacer', u'fields', u'form', u'input_date', u'input_dropdown', [u'field', u'field_spacer', u'fields', u'form', u'input_checkbox',
u'input_fileupload', u'input_password', u'input_textarea', u'input_textline'] u'input_date', u'input_dropdown', u'input_fileupload', u'input_html',
u'input_password', u'input_textarea', u'input_textline']

View file

@ -80,20 +80,27 @@
<metal:textline define-macro="input_textline"> <metal:textline define-macro="input_textline">
<input type="text" name="field" style="width: 450px" <input type="text" name="field" style="width: 450px"
tal:define="width field/width|nothing" tal:define="width field/width|nothing"
tal:attributes="name name; tal:attributes="name name; id name;
style python: style python:
'width: %s' % (width and str(width)+'px' or '450px'); 'width: %s' % (width and str(width)+'px' or '450px');
value data/?name|string:" /> value data/?name|string:;
required field/required_js;" />
</metal:textline> </metal:textline>
<metal:textline define-macro="input_date"> <metal:textline define-macro="input_date">
<input type="text" name="field" style="width: 450px" <input type="text" name="field" style="width: 8em"
dojoType="dijit.form.DateTextBox"
tal:define="width field/width|nothing" tal:define="width field/width|nothing"
tal:attributes="name name; tal:attributes="name name;
style python: value data/?name|string:;
'width: %s' % (width and str(width)+'px' or '450px'); required field/required_js" />
value data/?name|string:" /> <input type="text" name="field" style="width: 6em"
dojoType="dijit.form.TimeTextBox"
tal:define="width field/width|nothing"
tal:attributes="name name;
value data/?name|string:;
required field/required_js" />
</metal:textline> </metal:textline>
@ -120,12 +127,24 @@
</metal:textarea> </metal:textarea>
<metal:html define-macro="input_html">
<metal:textarea use-macro="view/fieldRenderers/input_textarea" />
</metal:html>
<metal:upload define-macro="input_fileupload"> <metal:upload define-macro="input_fileupload">
<input type="file" name="field" <input type="file" name="field"
tal:attributes="name name;" /> tal:attributes="name name;" />
</metal:upload> </metal:upload>
<metal:checkbox define-macro="input_checkbox">
<input type="checkbox" name="field" value="true"
tal:attributes="name name;
checked data/?name|nothing" />
</metal:checkbox>
<metal:dropdown define-macro="input_dropdown"> <metal:dropdown define-macro="input_dropdown">
<select name="field" style="width: auto" <select name="field" style="width: auto"
tal:define="width field/width|nothing" tal:define="width field/width|nothing"

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -42,10 +42,12 @@ class SchemaFactory(object):
fieldMapping = { fieldMapping = {
schema.TextLine: ('textline',), schema.TextLine: ('textline',),
schema.ASCIILine: ('textline',),
schema.Password: ('password',), schema.Password: ('password',),
schema.ASCII: ('textline',),
schema.Text: ('textarea',), schema.Text: ('textarea',),
schema.ASCII: ('textarea',),
schema.Date: ('date',), schema.Date: ('date',),
schema.Datetime: ('date',),
schema.Int: ('number',), schema.Int: ('number',),
schema.Bool: ('checkbox',), schema.Bool: ('checkbox',),
schema.Choice: ('dropdown',), schema.Choice: ('dropdown',),
@ -68,7 +70,7 @@ class SchemaFactory(object):
fieldType=info[0], fieldType=info[0],
required=field.required, required=field.required,
default=field.default, default=field.default,
#default_method=getattr(field, 'default_method', None), default_method=getattr(field, 'default_method', None),
vocabulary=voc, vocabulary=voc,
title=field.title, title=field.title,
description=field.description, description=field.description,

View file

@ -22,9 +22,13 @@ Schema fields and related classes.
$Id$ $Id$
""" """
from datetime import datetime
from time import strptime, strftime
from zope.interface import implements from zope.interface import implements
from zope.component import adapts from zope.component import adapts
from zope import component from zope import component
from zope.i18n.format import DateTimeParseError
from zope.i18n.locales import locales
from cybertools.composer.base import Component from cybertools.composer.base import Component
from cybertools.composer.schema.interfaces import IField, IFieldInstance from cybertools.composer.schema.interfaces import IField, IFieldInstance
@ -44,6 +48,7 @@ class Field(Component):
vocabulary = None vocabulary = None
renderFactory = None renderFactory = None
default = None default = None
default_method = None
def __init__(self, name, title=None, fieldType='textline', **kw): def __init__(self, name, title=None, fieldType='textline', **kw):
assert name assert name
@ -60,8 +65,8 @@ class Field(Component):
return self.__name__ return self.__name__
def getDefaultValue(self): def getDefaultValue(self):
if callable(self.default): if callable(self.default_method):
return self.default() return self.default_method()
return self.default return self.default
def setDefaultValue(self, value): def setDefaultValue(self, value):
self.default = value self.default = value
@ -79,6 +84,10 @@ class Field(Component):
def storeData(self): def storeData(self):
return not self.nostore and self.getFieldTypeInfo().storeData return not self.nostore and self.getFieldTypeInfo().storeData
@property
def required_js(self):
return self.required and 'true' or 'false'
def getTitleValue(self): def getTitleValue(self):
return self.title or self.name return self.title or self.name
@ -105,6 +114,8 @@ class FieldInstance(object):
implements(IFieldInstance) implements(IFieldInstance)
adapts(IField) adapts(IField)
clientInstance = None
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
self.name = self.__name__ = context.name self.name = self.__name__ = context.name
@ -112,6 +123,15 @@ class FieldInstance(object):
self.severity = 0 self.severity = 0
self.change = None self.change = None
@property
def default(self):
dm = self.context.default_method
if dm and isinstance(dm, str) and self.clientInstance:
method = getattr(self.clientInstance.context, dm, None)
if method:
return method()
return self.context.defaultValue
def marshall(self, value): def marshall(self, value):
return value or u'' return value or u''
#return toStr(value) #return toStr(value)
@ -156,11 +176,47 @@ class NumberFieldInstance(FieldInstance):
self.setError('required_missing') self.setError('required_missing')
else: else:
try: try:
int(value) self.unmarshall(value)
except (TypeError, ValueError): except (TypeError, ValueError):
self.setError('invalid_number') self.setError('invalid_number')
class DateFieldInstance(NumberFieldInstance):
def marshall(self, value):
if value is None:
return ''
return strftime('%Y-%m-%dT%H:%M', value.timetuple())
def display(self, value):
if value is None:
return ''
view = self.clientInstance.view
langInfo = view and view.languageInfo or None
if langInfo:
locale = locales.getLocale(langInfo.language)
fmt = locale.dates.getFormatter('dateTime', 'short')
return fmt.format(value)
return str(value)
def unmarshall(self, value):
if not value:
return None
value = ''.join(value)
return datetime(*(strptime(value, '%Y-%m-%dT%H:%M:%S')[:6]))
def validate(self, value, data=None):
if value in ('', None):
if self.context.required:
self.setError('required_missing')
else:
try:
self.unmarshall(value)
except (TypeError, ValueError, DateTimeParseError), e:
print '*** invalid_datetime:', value, e
self.setError('invalid_datetime')
class FileUploadFieldInstance(FieldInstance): class FileUploadFieldInstance(FieldInstance):
def marshall(self, value): def marshall(self, value):
@ -177,6 +233,19 @@ class EmailFieldInstance(FieldInstance):
self.setError('invalid_email_address') self.setError('invalid_email_address')
class BooleanFieldInstance(FieldInstance):
def marshall(self, value):
return value
def display(self, value):
#return value and _(u'Yes') or _(u'No')
return value and u'X' or u'-'
def unmarshall(self, value):
return bool(value)
class CalculatedFieldInstance(FieldInstance): class CalculatedFieldInstance(FieldInstance):
def marshall(self, value): def marshall(self, value):

View file

@ -52,8 +52,8 @@ class Instance(BaseInstance):
continue continue
fi = f.getFieldInstance(self) fi = f.getFieldInstance(self)
name = f.name name = f.name
value = getattr(self.context, name, f.defaultValue) #value = getattr(self.context, name, f.defaultValue)
#value = getattr(self.context, name, u'') value = getattr(self.context, name) or fi.default
value = (mode == 'view' and fi.display(value)) or fi.marshall(value) value = (mode == 'view' and fi.display(value)) or fi.marshall(value)
result[name] = value result[name] = value
return result return result
@ -103,7 +103,7 @@ class Editor(BaseInstance):
for f in self.template.components: for f in self.template.components:
if f.readonly: if f.readonly:
continue continue
fi = f.getFieldInstance() fi = f.getFieldInstance(self)
value = data.get(f.name) value = data.get(f.name)
fi.validate(value, data) fi.validate(value, data)
formState.fieldInstances.append(fi) formState.fieldInstances.append(fi)

View file

@ -84,14 +84,15 @@ fieldTypes = SimpleVocabulary((
FieldType('textline', 'textline', u'Textline'), FieldType('textline', 'textline', u'Textline'),
FieldType('password', 'password', u'Password'), FieldType('password', 'password', u'Password'),
FieldType('textarea', 'textarea', u'Textarea'), FieldType('textarea', 'textarea', u'Textarea'),
FieldType('html', 'html', u'HTML Text'),
FieldType('number', 'number', u'Number', FieldType('number', 'number', u'Number',
inputRenderer='input_textline', instanceName='number'), inputRenderer='input_textline', instanceName='number'),
FieldType('date', 'date', u'Date'), FieldType('date', 'date', u'Date', instanceName='date'),
FieldType('email', 'email', u'E-Mail Address', FieldType('email', 'email', u'E-Mail Address',
inputRenderer='input_textline', instanceName='email'), inputRenderer='input_textline', instanceName='email'),
FieldType('fileupload', 'fileupload', u'File upload', FieldType('fileupload', 'fileupload', u'File upload',
instanceName='fileupload'), instanceName='fileupload'),
#FieldType('checkbox', 'checkbox', u'Checkbox'), FieldType('checkbox', 'checkbox', u'Checkbox', instanceName='boolean'),
FieldType('dropdown', 'dropdown', u'Drop-down selection'), FieldType('dropdown', 'dropdown', u'Drop-down selection'),
#FieldType('listbox', 'listbox', u'List box (multiple selection)'), #FieldType('listbox', 'listbox', u'List box (multiple selection)'),
FieldType('calculated', 'display', u'Calculated Value', FieldType('calculated', 'display', u'Calculated Value',

View file

@ -90,6 +90,8 @@ formErrors = dict(
u'Please enter data for required field.'), u'Please enter data for required field.'),
invalid_number=FormError(u'Invalid number', invalid_number=FormError(u'Invalid number',
u'Please enter a number, only digits allowed.'), u'Please enter a number, only digits allowed.'),
invalid_datetime=FormError(u'Invalid date/time',
u'Please enter a string denoting a valid date/time.'),
invalid_email_address=FormError(u'Invalid E-Mail Address', invalid_email_address=FormError(u'Invalid E-Mail Address',
u'Please enter a valid email address.'), u'Please enter a valid email address.'),
) )

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,14 +24,18 @@ $Id$
import csv import csv
from cStringIO import StringIO from cStringIO import StringIO
import itertools
from zope import component from zope import component
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from cybertools.composer.interfaces import IInstance from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.interfaces import ISchema
from cybertools.stateful.interfaces import IStateful from cybertools.stateful.interfaces import IStateful
class RegistrationsExportCsv(object): class RegistrationsExportCsv(object):
encoding = 'ISO8859-15'
def __init__(self, context, request): def __init__(self, context, request):
self.context = context self.context = context
self.request = request self.request = request
@ -55,32 +59,75 @@ class RegistrationsExportCsv(object):
state = IStateful(reg).getStateObject() state = IStateful(reg).getStateObject()
if state.name == 'temporary' and not withTemporary: if state.name == 'temporary' and not withTemporary:
continue continue
yield [encode(service.title) or service.name, yield [self.encode(service.title) or service.name,
clientName, clientName,
encode(data.get('standard.organization', '')), self.encode(data.get('standard.organization', '')),
encode(data.get('standard.lastName', '')), self.encode(data.get('standard.lastName', '')),
encode(data.get('standard.firstName', '')), self.encode(data.get('standard.firstName', '')),
encode(data.get('standard.email', '')), self.encode(data.get('standard.email', '')),
reg.number, reg.number,
state.title state.title
] ]
def getAllDataInColumns(self):
""" Yield all data available, with a column for each service and
columns for all data fields of all data templates.
"""
withTemporary = self.request.get('with_temporary')
context = self.context
services = context.getServices()
schemas = [s for s in context.getClientSchemas() if ISchema.providedBy(s)]
yield (['Client ID']
+ list(itertools.chain(*[[self.encode(f.title)
for f in s.fields]
for s in schemas]))
+ [self.encode(s.title) for s in services])
clients = context.getClients()
for name, client in clients.items():
hasRegs = False
regs = []
for service in services:
reg = service.registrations.get(name)
if reg is None:
regs.append(0)
else:
state = IStateful(reg).getStateObject()
if state.name == 'temporary' and not withTemporary:
regs.append(0)
else:
number = reg.number
regs.append(reg.number)
if number:
hasRegs = True
if not hasRegs:
continue
result = [name]
for schema in schemas:
instance = IInstance(client)
instance.template = schema
data = instance.applyTemplate()
for f in schema.fields:
result.append(self.encode(data.get(f.name, '')))
result += regs
yield result
def render(self): def render(self):
methodName = self.request.get('get_data_method', 'getAllDataInColumns')
method = getattr(self, methodName, self.getData)
output = StringIO() output = StringIO()
csv.writer(output).writerows(self.getData()) csv.writer(output, dialect='excel', delimiter=';').writerows(method())
result = output.getvalue() result = output.getvalue()
self.setHeaders(len(result)) self.setHeaders(len(result))
return result return result
def render2(self): def render2(self):
# using cybertools.reporter.resultset # using cybertools.reporter.resultset
rs = self.getData() # returns a ResultSet rs = self.getData() # should return a ResultSet
result = rs.asCsv() result = rs.asCsv()
self.setHeaders(len(result)) self.setHeaders(len(result))
return result return result
def encode(self, text):
def encode(text, encoding='UTF-8'): if type(text) is unicode:
if type(text) is unicode: text = text.encode(self.encoding)
text = text.encode(encoding) return text
return text

View file

@ -204,10 +204,10 @@ class CheckoutView(ServiceManagerView):
regs = sorted(regs.getRegistrations(), key=self.sortKey) regs = sorted(regs.getRegistrations(), key=self.sortKey)
for reg in regs: for reg in regs:
service = reg.service service = reg.service
result.append(dict(service=service.title, result.append(dict(service=service.title or '???',
fromTo=self.getFromTo(service), fromTo=self.getFromTo(service),
location=service.location, location=service.location or '',
locationUrl=service.locationUrl, locationUrl=service.locationUrl or '',
number=reg.number, number=reg.number,
serviceObject=service)) serviceObject=service))
return result return result
@ -225,7 +225,7 @@ class CheckoutView(ServiceManagerView):
result = [] result = []
for info in self.getRegistrationsInfo(): for info in self.getRegistrationsInfo():
location, locationUrl = info['location'], info['locationUrl'] location, locationUrl = info['location'], info['locationUrl']
if locationUrl.startswith('/'): if locationUrl and locationUrl.startswith('/'):
locationUrl = self.request.get('SERVER_URL') + locationUrl locationUrl = self.request.get('SERVER_URL') + locationUrl
locationInfo = (locationUrl and '%s (%s)' % (location, locationUrl) locationInfo = (locationUrl and '%s (%s)' % (location, locationUrl)
or location) or location)

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -37,7 +37,7 @@ class CachedImage(object):
def __init__(self, path, name=None): def __init__(self, path, name=None):
self.path = path self.path = path
if name is None: if name is None:
self.name = randomname.generateName(lambda x: x not in cachedImages) self.name = randomname.generateName(lambda x: x not in cachedImages.keys())
else: else:
self.name = name self.name = name
self.timeStamp = int(time.time()) self.timeStamp = int(time.time())

View file

@ -33,7 +33,7 @@ def htmlToText(html):
data = [] data = []
soup = BeautifulSoup(html).html soup = BeautifulSoup(html).html
collectText([soup], data) collectText([soup], data)
text = u' '.join(data).replace('\n', '').replace('&nbsp;', '') text = u' '.join(data).replace(u'\n', u'').replace(u'&nbsp;', u'')
return text return text
def collectText(tags, data): def collectText(tags, data):

View file

@ -45,4 +45,5 @@ class PptTransform(base.BaseFileTransform):
else: else:
html = self.execute('ppthtml "%s" 2> /dev/null' % filename) html = self.execute('ppthtml "%s" 2> /dev/null' % filename)
data = htmlToText(html) data = htmlToText(html)
return data.decode('ISO8859-15') return data
#return data.decode('ISO8859-15')

View file

@ -4,6 +4,9 @@ Setting Configuration Options
$Id$ $Id$
(TO DO / exercises: try to formulate a typical buildout.cfg, a configure.zcml,
or a generic setup configuration file using this syntax.)
>>> from cybertools import util >>> from cybertools import util
>>> from cybertools.util.config import Configurator >>> from cybertools.util.config import Configurator
>>> config = Configurator() >>> config = Configurator()
@ -93,6 +96,30 @@ The simplified syntax
>>> #print config >>> #print config
A better simplified syntax
--------------------------
Open a section with the ``use`` method.
use(ui.web)
port = 11080
use(crawl[1])
type = 'outlook'
folder = 'inbox'
or - even more better...
use(ui.web,
port=11080,
)
use(crawl[1],
type='outlook',
folder='inbox',
)
Cleaning up Cleaning up
----------- -----------