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:
parent
3f704eb6f9
commit
7ec9bbdc15
32 changed files with 597 additions and 231 deletions
|
@ -1,3 +1,7 @@
|
||||||
"""
|
"""
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
|
|
||||||
|
dojoMacroTemplate = ViewPageTemplateFile('macros.pt')
|
||||||
|
|
|
@ -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="*"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
117
browser/action.py
Normal 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
21
browser/action_macros.pt
Normal 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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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
BIN
browser/icons/user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 741 B |
|
@ -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; }
|
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
21
browser/liquid/presentation.css
Normal file
21
browser/liquid/presentation.css
Normal 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;
|
||||||
|
}
|
|
@ -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 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
111
browser/member.py
Normal 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'),
|
||||||
|
))
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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.'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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(' ', '')
|
text = u' '.join(data).replace(u'\n', u'').replace(u' ', u'')
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def collectText(tags, data):
|
def collectText(tags, data):
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue