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$
"""
from zope.app.pagetemplate import ViewPageTemplateFile
dojoMacroTemplate = ViewPageTemplateFile('macros.pt')

View file

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

View file

@ -1,12 +1,8 @@
<metal:def define-macro="main">
<script type="text/javascript">
djConfig = { isDebug: true,
parseOnLoad: true };
</script>
<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>
</metal:def>

View file

@ -1,3 +1,4 @@
==================
Browser View Tools
==================
@ -5,8 +6,9 @@ Browser View Tools
>>> from zope.interface import Interface, implements
>>> from zope.publisher.interfaces.browser import IBrowserRequest
The Generic View class
----------------------
======================
GenericView is intended as the base class for application-specific views.
The GenericView class itself provides only basic functionality, so you
@ -64,7 +66,7 @@ bodyTemplate attribute.
The View Controller
-------------------
===================
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
@ -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
(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'])
5
>>> m5 = cssMacros[4]
>>> m5 = controller.macros['css'][4]
>>> print m5.name, m5.media, m5.resourceName
css all node.css
@ -158,7 +161,7 @@ We can also access slots that are not predefined:
The View Configurator
---------------------
=====================
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
@ -167,9 +170,9 @@ characteristics of a page. Typical examples for such characteristics are
- the skin to be used
- the logo to show in the corner of the page
The default configurator uses attribute annotations for retrieving view
properties; that means that there could be a form somewhere to edit those
properties and store them in the content object's annotations.
There is a standard configurator that uses attribute annotations for
retrieving view properties; that means that there could be a form somewhere
to edit those properties and store them in the content object's annotations.
>>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
>>> from zope.annotation.attribute import AttributeAnnotations
@ -179,8 +182,8 @@ The configurator is called automatically from the controller if there is
an appropriate adapter:
>>> from cybertools.browser.configurator import IViewConfigurator
>>> from cybertools.browser.configurator import ViewConfigurator
>>> component.provideAdapter(ViewConfigurator, (SomeObject, IBrowserRequest),
>>> from cybertools.browser.configurator import AnnotationViewConfigurator
>>> component.provideAdapter(AnnotationViewConfigurator, (SomeObject, IBrowserRequest),
... IViewConfigurator)
>>> controller = Controller(view, request)
@ -197,18 +200,9 @@ stored in the attribute annotations. So let's set a 'skinName' attribute:
>>> controller.skinName.value
'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
---------------------
=====================
GenericView also provides an update() method that may be called from
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:portlet define-macro="multi_actions">
<tal:sub repeat="macro macro/subMacros">
<metal:sub use-macro="macro" />
</tal:sub>
</metal:portlet>
<!-- portlets and similar collections of actions -->
<metal:portlet define-macro="portlet_left">
<div metal:use-macro="macro/template/macros/portlet" />
@ -45,11 +40,27 @@
</metal:portlet>
<metal:portlet define-macro="portlet">
<div class="box">
<h4 tal:content="macro/title"
i18n:translate="">Navigation</h4>
<div class="box"
tal:define="icon macro/icon|nothing;
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 metal:use-macro="macro/subMacro" />
</div>
</div>
</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
# 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$
"""
from zope.app import zapi
from zope import component
from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
from zope.annotation.attribute import AttributeAnnotations
from zope.cachedescriptors.property import Lazy
from zope.interface import Interface, Attribute, implements
from zope.component import adapts
# interfaces
@ -64,15 +63,28 @@ class IMacroViewProperty(IViewProperty):
#default implementations
ANNOTATION_KEY = 'cybertools.browser.configurator.ViewConfigurator'
class ViewConfigurator(object):
""" Simple/basic default adapter using attribute annotations as storage
for view properties.
""" An base class for adapters that allow the registration of view properties.
"""
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):
self.context = context
self.request = request
@ -83,19 +95,12 @@ class ViewConfigurator(object):
propDefs = ann.get(ANNOTATION_KEY, {})
return [self.setupViewProperty(prop, 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):
return self.viewProperties
def setupViewProperty(self, prop, propDef):
vp = zapi.queryMultiAdapter((self.context, self.request),
vp = component.queryMultiAdapter((self.context, self.request),
IViewProperty, name=prop)
if vp is None:
vp = ViewProperty(self.context, self.request)
@ -104,19 +109,6 @@ class ViewConfigurator(object):
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):
implements(IViewProperty)
@ -136,7 +128,7 @@ class ViewProperty(object):
self.params = params
class MacroViewProperty(object):
class MacroViewProperty(ViewProperty):
implements(IMacroViewProperty)

View file

@ -6,6 +6,8 @@
i18n_domain="zope"
>
<resourceDirectory name="cybertools.icons" directory="icons" />
<page for="*"
name="main.html"
template="main.pt"
@ -25,10 +27,16 @@
permission="zope.Public"
/>
<page name="controller"
<!--<page name="controller"
for="zope.publisher.interfaces.browser.IBrowserView"
class="cybertools.browser.controller.Controller"
permission="zope.Public"
/>-->
<zope:adapter
for="* zope.publisher.interfaces.browser.IBrowserRequest"
factory="cybertools.browser.member.MemberInfoProvider"
permission="zope.Public"
/>
<!-- 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
# 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 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
@ -54,15 +56,11 @@ class Controller(object):
return self.request.URL[0] + skinSetter + '/@@/'
def configure(self):
#configurator = component.queryMultiAdapter((self.context, self.request),
# IViewConfigurator)
# idea: collect multiple configurators:
# collect multiple configurators:
configurators = component.getAdapters((self.context, self.request),
IViewConfigurator)
for conf in configurators:
configurator = conf[1]
#if configurator is not None:
#for item in configurator.viewProperties:
for item in configurator.getActiveViewProperties():
if IMacroViewProperty.providedBy(item):
self.macros.register(item.slot, item.idenitifier,
@ -71,6 +69,12 @@ class Controller(object):
else:
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):
@ -81,7 +85,7 @@ class Macros(dict):
self.identifiers = set()
def register(self, slot, identifier=None, template=None, name=None,
position=None, **kw):
priority=50, **kw):
if identifier:
# make sure a certain resource is only registered once
if identifier in self.identifiers:
@ -91,22 +95,20 @@ class Macros(dict):
template = self.standardTemplate
if name is None:
name = slot
macro = Macro(template, name, **kw)
macro = Macro(template, name, priority, **kw)
entry = self.setdefault(slot, [])
if position is None:
entry.append(macro)
else:
entry.insert(position, macro)
entry.append(macro)
def __getitem__(self, key):
return self.get(key, [])
return list(sorted(self.get(key, []), key=lambda x: x.priority))
class Macro(object):
def __init__(self, template, name, **kw):
def __init__(self, template, name, priority, **kw):
self.template = template
self.name = name
self.priority = priority
for k in kw:
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%}
#content {width:62%}
#sub-section {width:17%}
#footer {clear: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; }
#footer {clear:left; float:left}

View file

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

View file

@ -28,19 +28,26 @@ from cybertools.browser.controller import Controller as BaseController
class Controller(BaseController):
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.setupJs()
super(Controller, self).__init__(context, request)
def setupCss(self):
macros = self.macros
params = [('zope3_tablelayout.css', 'all'),
('base.css', 'screen'),
('print.css', 'print'),
('custom.css', 'all')]
presentationMode = self.request.get('liquid.viewmode') == 'presentation'
params = [('zope3_tablelayout.css', 'all', 20),
('base.css', 'screen', 25),
('print.css', 'print', 30),
('custom.css', 'all', 100)]
if presentationMode:
params.append(('presentation.css', 'all', 30))
for param in params:
macros.register('css', identifier=param[0],
resourceName=param[0], media=param[1])
resourceName=param[0], media=param[1],
priority=param[2])
def setupJs(self):
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.
** For future skin see zope.app.boston.
*/
/*
* { border: 1px dotted red }
**
** $Id$
**
*/
@ -31,7 +29,7 @@ table {
font-size: 100%;
}
a {
a[href] {
text-decoration: none;
color: #369;
background-color: transparent;
@ -46,11 +44,6 @@ img {
vertical-align: middle;
}
p {
margin: 0.5em 0em 1em 0em;
line-height: 1.5em;
}
p a:visited {
color: Purple;
background-color: transparent;
@ -80,7 +73,7 @@ h1, h2, h3, h4, h5, h6 {
clear: left;
font: 100% bold Verdana, Helvetica, Arial, sans-serif;
margin: 0;
padding-top: 0.5em;
padding-top: 0;
border-bottom: 1px solid #369;
}
@ -109,7 +102,7 @@ h6 {
}
ul {
line-height: 1.5em;
line-height: 1.2em;
/* list-style-image: url("bullet.gif"); */
margin-left: 2em;
padding:0;
@ -411,9 +404,6 @@ div.box h4 {
}
#content {
}
#context_information {
padding-top: 1em;
width: 15%;
@ -425,18 +415,6 @@ div.box h4 {
width: 100%;
}
#helpers {
}
#inspectors {
}
#footer {
border-bottom: 1px solid black;
float: left;
clear: both;
}
input.textType {
width: 88%; /* Same as textarea */
}

View file

@ -32,7 +32,8 @@
<base href="." tal:attributes="href request/URL">
</head>
<body tal:content="structure body" />
<body class="tundra"
tal:content="structure body" />
</html>
</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
# 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
viewAnnotations = self.request.annotations.setdefault('cybertools.browser', {})
viewAnnotations['controller'] = controller
if getattr(controller, 'skinName', None) and controller.skinName.value:
self.setSkin(controller.skinName.value)
#if getattr(controller, 'skinName', None) and controller.skinName.value:
# self.setSkin(controller.skinName.value)
controller.skin = self.skin
# this is the place to register special macros with the controller:
self.setupController()
def getController(self):
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)
def __init__(self, context, request):
@ -119,4 +124,3 @@ class GenericView(object):
applySkin(self.request, skin)
self.skin = skin

View file

@ -167,5 +167,6 @@ Macros / renderers
>>> fieldRenderers = form.fieldRenderers
>>> sorted(fieldRenderers.keys())
[u'field', u'field_spacer', u'fields', u'form', u'input_date', u'input_dropdown',
u'input_fileupload', u'input_password', u'input_textarea', u'input_textline']
[u'field', u'field_spacer', u'fields', u'form', u'input_checkbox',
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">
<input type="text" name="field" style="width: 450px"
tal:define="width field/width|nothing"
tal:attributes="name name;
tal:attributes="name name; id name;
style python:
'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 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:attributes="name name;
style python:
'width: %s' % (width and str(width)+'px' or '450px');
value data/?name|string:" />
value data/?name|string:;
required field/required_js" />
<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>
@ -120,12 +127,24 @@
</metal:textarea>
<metal:html define-macro="input_html">
<metal:textarea use-macro="view/fieldRenderers/input_textarea" />
</metal:html>
<metal:upload define-macro="input_fileupload">
<input type="file" name="field"
tal:attributes="name name;" />
</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">
<select name="field" style="width: auto"
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
# it under the terms of the GNU General Public License as published by
@ -42,10 +42,12 @@ class SchemaFactory(object):
fieldMapping = {
schema.TextLine: ('textline',),
schema.ASCIILine: ('textline',),
schema.Password: ('password',),
schema.ASCII: ('textline',),
schema.Text: ('textarea',),
schema.ASCII: ('textarea',),
schema.Date: ('date',),
schema.Datetime: ('date',),
schema.Int: ('number',),
schema.Bool: ('checkbox',),
schema.Choice: ('dropdown',),
@ -68,7 +70,7 @@ class SchemaFactory(object):
fieldType=info[0],
required=field.required,
default=field.default,
#default_method=getattr(field, 'default_method', None),
default_method=getattr(field, 'default_method', None),
vocabulary=voc,
title=field.title,
description=field.description,

View file

@ -22,9 +22,13 @@ Schema fields and related classes.
$Id$
"""
from datetime import datetime
from time import strptime, strftime
from zope.interface import implements
from zope.component import adapts
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.schema.interfaces import IField, IFieldInstance
@ -44,6 +48,7 @@ class Field(Component):
vocabulary = None
renderFactory = None
default = None
default_method = None
def __init__(self, name, title=None, fieldType='textline', **kw):
assert name
@ -60,8 +65,8 @@ class Field(Component):
return self.__name__
def getDefaultValue(self):
if callable(self.default):
return self.default()
if callable(self.default_method):
return self.default_method()
return self.default
def setDefaultValue(self, value):
self.default = value
@ -79,6 +84,10 @@ class Field(Component):
def storeData(self):
return not self.nostore and self.getFieldTypeInfo().storeData
@property
def required_js(self):
return self.required and 'true' or 'false'
def getTitleValue(self):
return self.title or self.name
@ -105,6 +114,8 @@ class FieldInstance(object):
implements(IFieldInstance)
adapts(IField)
clientInstance = None
def __init__(self, context):
self.context = context
self.name = self.__name__ = context.name
@ -112,6 +123,15 @@ class FieldInstance(object):
self.severity = 0
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):
return value or u''
#return toStr(value)
@ -156,11 +176,47 @@ class NumberFieldInstance(FieldInstance):
self.setError('required_missing')
else:
try:
int(value)
self.unmarshall(value)
except (TypeError, ValueError):
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):
def marshall(self, value):
@ -177,6 +233,19 @@ class EmailFieldInstance(FieldInstance):
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):
def marshall(self, value):

View file

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

View file

@ -84,14 +84,15 @@ fieldTypes = SimpleVocabulary((
FieldType('textline', 'textline', u'Textline'),
FieldType('password', 'password', u'Password'),
FieldType('textarea', 'textarea', u'Textarea'),
FieldType('html', 'html', u'HTML Text'),
FieldType('number', 'number', u'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',
inputRenderer='input_textline', instanceName='email'),
FieldType('fileupload', 'fileupload', u'File upload',
instanceName='fileupload'),
#FieldType('checkbox', 'checkbox', u'Checkbox'),
FieldType('checkbox', 'checkbox', u'Checkbox', instanceName='boolean'),
FieldType('dropdown', 'dropdown', u'Drop-down selection'),
#FieldType('listbox', 'listbox', u'List box (multiple selection)'),
FieldType('calculated', 'display', u'Calculated Value',

View file

@ -90,6 +90,8 @@ formErrors = dict(
u'Please enter data for required field.'),
invalid_number=FormError(u'Invalid number',
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',
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
# it under the terms of the GNU General Public License as published by
@ -24,14 +24,18 @@ $Id$
import csv
from cStringIO import StringIO
import itertools
from zope import component
from zope.cachedescriptors.property import Lazy
from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.interfaces import ISchema
from cybertools.stateful.interfaces import IStateful
class RegistrationsExportCsv(object):
encoding = 'ISO8859-15'
def __init__(self, context, request):
self.context = context
self.request = request
@ -55,32 +59,75 @@ class RegistrationsExportCsv(object):
state = IStateful(reg).getStateObject()
if state.name == 'temporary' and not withTemporary:
continue
yield [encode(service.title) or service.name,
yield [self.encode(service.title) or service.name,
clientName,
encode(data.get('standard.organization', '')),
encode(data.get('standard.lastName', '')),
encode(data.get('standard.firstName', '')),
encode(data.get('standard.email', '')),
self.encode(data.get('standard.organization', '')),
self.encode(data.get('standard.lastName', '')),
self.encode(data.get('standard.firstName', '')),
self.encode(data.get('standard.email', '')),
reg.number,
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):
methodName = self.request.get('get_data_method', 'getAllDataInColumns')
method = getattr(self, methodName, self.getData)
output = StringIO()
csv.writer(output).writerows(self.getData())
csv.writer(output, dialect='excel', delimiter=';').writerows(method())
result = output.getvalue()
self.setHeaders(len(result))
return result
def render2(self):
# using cybertools.reporter.resultset
rs = self.getData() # returns a ResultSet
rs = self.getData() # should return a ResultSet
result = rs.asCsv()
self.setHeaders(len(result))
return result
def encode(text, encoding='UTF-8'):
if type(text) is unicode:
text = text.encode(encoding)
return text
def encode(self, text):
if type(text) is unicode:
text = text.encode(self.encoding)
return text

View file

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

View file

@ -33,7 +33,7 @@ def htmlToText(html):
data = []
soup = BeautifulSoup(html).html
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
def collectText(tags, data):

View file

@ -45,4 +45,5 @@ class PptTransform(base.BaseFileTransform):
else:
html = self.execute('ppthtml "%s" 2> /dev/null' % filename)
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$
(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.util.config import Configurator
>>> config = Configurator()
@ -93,6 +96,30 @@ The simplified syntax
>>> #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
-----------