merge bbmaster + bbmaster2 to new branch 2master
This commit is contained in:
commit
3c8edc3e90
42 changed files with 362 additions and 142 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,9 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
ajax/dojo/*
|
ajax/dojo/*
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info
|
||||||
*.project
|
*.project
|
||||||
*.pydevproject
|
*.pydevproject
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
|
|
14
MANIFEST.in
Normal file
14
MANIFEST.in
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
global-include *.cfg
|
||||||
|
global-include *.css *.js
|
||||||
|
global-include *.gif *.jpg *.png
|
||||||
|
global-include *.html
|
||||||
|
global-include *.md *.txt
|
||||||
|
global-include *.mht
|
||||||
|
global-include *.mo *.po *.pot
|
||||||
|
global-include *.pt
|
||||||
|
global-include *.xml
|
||||||
|
global-include *.zcml
|
||||||
|
global-include mime.types
|
||||||
|
|
||||||
|
graft cybertools/integrator/tests/data
|
||||||
|
graft cybertools/text/testfiles
|
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
This is a set of utility libraries to be used mainly
|
||||||
|
with Zope 3 / bluebream and the web application platform
|
||||||
|
*loops*.
|
||||||
|
|
||||||
|
More information: see https://www.cyberconcepts.org.
|
|
@ -1,3 +1,6 @@
|
||||||
"""
|
# package cybertools
|
||||||
$Id$
|
|
||||||
"""
|
# module aliases
|
||||||
|
import sys
|
||||||
|
import doctest
|
||||||
|
sys.modules['zope.testing.doctestunit'] = doctest
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="top span-6"
|
<div class="top span-6"
|
||||||
metal:define-slot="top">
|
metal:define-slot="top">
|
||||||
<a href="#" name="top" metal:define-slot="logo"
|
<a href="#" name="top" metal:define-slot="logo"
|
||||||
tal:attributes="href string:${request/URL/1}"><img class="logo"
|
tal:attributes="href string:${view/requestUrl/1}"><img class="logo"
|
||||||
src="logo.gif" border="0" alt="Home"
|
src="logo.gif" border="0" alt="Home"
|
||||||
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
||||||
<div metal:define-slot="top-actions">
|
<div metal:define-slot="top-actions">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2014 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,9 +37,9 @@ class Controller(BaseController):
|
||||||
macros = self.macros
|
macros = self.macros
|
||||||
presentationMode = self.request.get('liquid.viewmode') == 'presentation'
|
presentationMode = self.request.get('liquid.viewmode') == 'presentation'
|
||||||
params = [('blue/blue8.css', 'all', 20, False),
|
params = [('blue/blue8.css', 'all', 20, False),
|
||||||
('print.css', 'print', 25, False),
|
|
||||||
('blue/ie.css', 'all', 30, True),
|
('blue/ie.css', 'all', 30, True),
|
||||||
('custom.css', 'all', 100, False)]
|
('custom.css', 'all', 100, False),
|
||||||
|
('print.css', 'print', 200, False),]
|
||||||
#if presentationMode:
|
#if presentationMode:
|
||||||
# params.append(('presentation.css', 'all', 30, False))
|
# params.append(('presentation.css', 'all', 30, False))
|
||||||
for id, media, prio, ie in params:
|
for id, media, prio, ie in params:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2016 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
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Controller for views, templates, macros.
|
Controller for views, templates, macros.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import component
|
from zope import component
|
||||||
|
|
BIN
browser/icons/ledgreenblue.png
Normal file
BIN
browser/icons/ledgreenblue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/icons/page_copy.png
Normal file
BIN
browser/icons/page_copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 663 B |
BIN
browser/icons/page_delete.png
Normal file
BIN
browser/icons/page_delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 740 B |
|
@ -7,7 +7,7 @@
|
||||||
<div id="global" metal:define-macro="global">
|
<div id="global" metal:define-macro="global">
|
||||||
<div class="top" metal:define-slot="top">
|
<div class="top" metal:define-slot="top">
|
||||||
<a href="#" name="top" metal:define-slot="logo"
|
<a href="#" name="top" metal:define-slot="logo"
|
||||||
tal:attributes="href string:${request/URL/1}"><img class="logo"
|
tal:attributes="href string:${view/requestUrl/1}"><img class="logo"
|
||||||
src="logo.gif" border="0" alt="Home"
|
src="logo.gif" border="0" alt="Home"
|
||||||
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
||||||
<div metal:define-slot="top-actions">
|
<div metal:define-slot="top-actions">
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<div id="global">
|
<div id="global">
|
||||||
<div class="top" metal:define-slot="top">
|
<div class="top" metal:define-slot="top">
|
||||||
<a href="#" name="top" metal:define-slot="logo"
|
<a href="#" name="top" metal:define-slot="logo"
|
||||||
tal:attributes="href string:${request/URL/1}"><img class="logo"
|
tal:attributes="href string:${view/requestUrl/1}"><img class="logo"
|
||||||
src="logo.gif" border="0" alt="Home"
|
src="logo.gif" border="0" alt="Home"
|
||||||
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
region="top" splitter="false">
|
region="top" splitter="false">
|
||||||
<div class="top" metal:define-slot="top">
|
<div class="top" metal:define-slot="top">
|
||||||
<a href="#" name="top" metal:define-slot="logo"
|
<a href="#" name="top" metal:define-slot="logo"
|
||||||
tal:attributes="href string:${request/URL/1}"><img class="logo"
|
tal:attributes="href string:${view/requestUrl/1}"><img class="logo"
|
||||||
src="logo.gif" border="0" alt="Home"
|
src="logo.gif" border="0" alt="Home"
|
||||||
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
tal:attributes="src string:${resourceBase}logo.gif" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2017 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,7 +27,7 @@ from zope.app.container.traversal import ItemTraverser
|
||||||
from zope.interface import Interface, implements
|
from zope.interface import Interface, implements
|
||||||
|
|
||||||
|
|
||||||
TraversalRedirector(ItemTraverser):
|
class TraversalRedirector(ItemTraverser):
|
||||||
|
|
||||||
port = 9083
|
port = 9083
|
||||||
names = ('ctt', 'sona',)
|
names = ('ctt', 'sona',)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2016 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,7 @@ from zope.interface import Interface, implements
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.event import notify
|
from zope.event import notify
|
||||||
|
from zope.publisher.http import URLGetter as BaseURLGetter
|
||||||
from zope.publisher.interfaces.browser import IBrowserSkinType
|
from zope.publisher.interfaces.browser import IBrowserSkinType
|
||||||
|
|
||||||
from cybertools.browser.renderer import CachableRenderer
|
from cybertools.browser.renderer import CachableRenderer
|
||||||
|
@ -63,6 +64,15 @@ class BodyTemplateView(object):
|
||||||
bodyTemplate = UnboundTemplateFile('liquid/body.pt')
|
bodyTemplate = UnboundTemplateFile('liquid/body.pt')
|
||||||
|
|
||||||
|
|
||||||
|
class URLGetter(BaseURLGetter):
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
url = self.__request.getURL()
|
||||||
|
if url.endswith('/@@index.html'):
|
||||||
|
url = url[:-len('/@@index.html')]
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
class GenericView(object):
|
class GenericView(object):
|
||||||
|
|
||||||
index = mainTemplate
|
index = mainTemplate
|
||||||
|
@ -103,6 +113,10 @@ class GenericView(object):
|
||||||
# this is useful for a top-level page only
|
# this is useful for a top-level page only
|
||||||
return self.index(*args, **kw)
|
return self.index(*args, **kw)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requestUrl(self):
|
||||||
|
return URLGetter(self.request)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def isAuthenticated(self):
|
def isAuthenticated(self):
|
||||||
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#-*- coding: UTF-8 -*-
|
#-*- coding: UTF-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2015 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
|
||||||
|
@ -408,12 +408,13 @@ class IOrderItem(ITrack):
|
||||||
shop = Attribute(u'The shop from which the product is ordered.')
|
shop = Attribute(u'The shop from which the product is ordered.')
|
||||||
order = Attribute(u'The order this order item belongs to.')
|
order = Attribute(u'The order this order item belongs to.')
|
||||||
unitPrice = Attribute(u'The basic unit price for one of the product '
|
unitPrice = Attribute(u'The basic unit price for one of the product '
|
||||||
u'items ordered.')
|
u'ites ordered.')
|
||||||
fullPrice = Attribute(u'The full price for the quantity ordered.')
|
fullPrice = Attribute(u'The full price for the quantity ordered.')
|
||||||
quantityShipped = Attribute(u'The total quantity that has been shipped '
|
quantityShipped = Attribute(u'The total quantity that has been shipped '
|
||||||
u'already.')
|
u'already.')
|
||||||
shippingInfo = Attribute(u'A list of mappings, with fields like: '
|
shippingInfo = Attribute(u'A list of mappings, with fields like: '
|
||||||
u'shippingId, shippingDate, quantity, packageId')
|
u'shippingId, shippingDate, quantity, packageId')
|
||||||
|
options = Attribute(u'Product options associated with this order item.')
|
||||||
|
|
||||||
def remove():
|
def remove():
|
||||||
""" Remove the order item from the order or cart.
|
""" Remove the order item from the order or cart.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2015 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
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Order and order item classes.
|
Order and order item classes.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
@ -119,6 +117,10 @@ class OrderItems(object):
|
||||||
def add(self, product, party, shop, order='???', run=0, **kw):
|
def add(self, product, party, shop, order='???', run=0, **kw):
|
||||||
kw['shop'] = self.getUid(shop)
|
kw['shop'] = self.getUid(shop)
|
||||||
existing = self.getCart(party, order, shop, run, product=product)
|
existing = self.getCart(party, order, shop, run, product=product)
|
||||||
|
options = kw.get('options')
|
||||||
|
if options is not None:
|
||||||
|
existing = [item for item in existing
|
||||||
|
if (item.data.get('options') or []) == options]
|
||||||
if existing:
|
if existing:
|
||||||
track = existing[-1]
|
track = existing[-1]
|
||||||
track.modify(track.quantity + kw.get('quantity', 1))
|
track.modify(track.quantity + kw.get('quantity', 1))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<metal:page define-macro="page"
|
<metal:page define-macro="page"
|
||||||
tal:condition="view/update"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
tal:condition="view/update"><!DOCTYPE html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
||||||
tal:define="body view/body;
|
tal:define="body view/body;
|
||||||
layout view/context/template">
|
layout view/context/template">
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</tal:js>
|
</tal:js>
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico"
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico"
|
||||||
tal:attributes="href string:${view/page/resourceBase}${layout/favicon}" />
|
tal:attributes="href string:${view/page/resourceBase}${layout/favicon}" />
|
||||||
<base href="." tal:attributes="href request/URL">
|
<base href="." tal:attributes="href view/requestUrl">
|
||||||
</head>
|
</head>
|
||||||
<body tal:replace="structure body" />
|
<body tal:replace="structure body" />
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2016 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
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Basic view classes for layout-based presentation.
|
Basic view classes for layout-based presentation.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import component
|
from zope import component
|
||||||
|
@ -27,7 +25,9 @@ from zope.interface import Interface, implements
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
||||||
|
from zope.publisher.http import URLGetter as BaseURLGetter
|
||||||
|
|
||||||
|
from cybertools.browser.view import URLGetter
|
||||||
from cybertools.composer.layout.base import Layout
|
from cybertools.composer.layout.base import Layout
|
||||||
from cybertools.composer.layout.interfaces import ILayoutManager
|
from cybertools.composer.layout.interfaces import ILayoutManager
|
||||||
from cybertools.composer.layout.interfaces import ILayout, ILayoutInstance
|
from cybertools.composer.layout.interfaces import ILayout, ILayoutInstance
|
||||||
|
@ -73,6 +73,10 @@ class BaseView(object):
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
return self.template(self)
|
return self.template(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requestUrl(self):
|
||||||
|
return URLGetter(self.request)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def authenticated(self):
|
def authenticated(self):
|
||||||
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
||||||
|
|
|
@ -83,6 +83,7 @@ class Report(Template):
|
||||||
queryCriteria = None
|
queryCriteria = None
|
||||||
outputFields = ()
|
outputFields = ()
|
||||||
sortCriteria = ()
|
sortCriteria = ()
|
||||||
|
sortDescending = False
|
||||||
limits = None
|
limits = None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2016 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
|
||||||
|
@ -129,6 +129,9 @@ class Field(Component):
|
||||||
def getDisplayValue(self, row):
|
def getDisplayValue(self, row):
|
||||||
return self.getValue(row)
|
return self.getValue(row)
|
||||||
|
|
||||||
|
def getExportValue(self, row, format=None, lang=None):
|
||||||
|
return self.getValue(row)
|
||||||
|
|
||||||
def getSortValue(self, row):
|
def getSortValue(self, row):
|
||||||
# TODO: consider 'descending' flag
|
# TODO: consider 'descending' flag
|
||||||
return self.getValue(row)
|
return self.getValue(row)
|
||||||
|
|
|
@ -123,13 +123,15 @@ class ResultSet(object):
|
||||||
|
|
||||||
def __init__(self, context, data,
|
def __init__(self, context, data,
|
||||||
rowFactory=Row, headerRowFactory=GroupHeaderRow,
|
rowFactory=Row, headerRowFactory=GroupHeaderRow,
|
||||||
sortCriteria=None, queryCriteria=BaseQueryCriteria(),
|
sortCriteria=None, sortDescending=False,
|
||||||
|
queryCriteria=BaseQueryCriteria(),
|
||||||
limits=None):
|
limits=None):
|
||||||
self.context = context # the report or report instance
|
self.context = context # the report or report instance
|
||||||
self.data = data
|
self.data = data
|
||||||
self.rowFactory = rowFactory
|
self.rowFactory = rowFactory
|
||||||
self.headerRowFactory = headerRowFactory
|
self.headerRowFactory = headerRowFactory
|
||||||
self.sortCriteria = sortCriteria
|
self.sortCriteria = sortCriteria
|
||||||
|
self.sortDescending = sortDescending
|
||||||
self.queryCriteria = queryCriteria
|
self.queryCriteria = queryCriteria
|
||||||
self.limits = limits
|
self.limits = limits
|
||||||
self.totals = TotalsRow(None, self)
|
self.totals = TotalsRow(None, self)
|
||||||
|
@ -204,7 +206,8 @@ class ResultSet(object):
|
||||||
result = [row for row in result if self.queryCriteria.check(row)]
|
result = [row for row in result if self.queryCriteria.check(row)]
|
||||||
if self.sortCriteria:
|
if self.sortCriteria:
|
||||||
result.sort(key=lambda x:
|
result.sort(key=lambda x:
|
||||||
[f.getSortValue(x) for f in self.sortCriteria])
|
[f.getSortValue(x) for f in self.sortCriteria],
|
||||||
|
reverse=self.sortDescending)
|
||||||
if self.groupColumns:
|
if self.groupColumns:
|
||||||
res = []
|
res = []
|
||||||
groupValues = [None for f in self.groupColumns]
|
groupValues = [None for f in self.groupColumns]
|
||||||
|
|
|
@ -144,7 +144,11 @@
|
||||||
|
|
||||||
<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;"
|
||||||
|
onchange="if (this.form.title.value == '') {
|
||||||
|
var value = this.value.split('\\');
|
||||||
|
this.form.title.value = value[value.length-1];
|
||||||
|
}" />
|
||||||
</metal:upload>
|
</metal:upload>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,8 @@ class SchemaFactory(object):
|
||||||
field = interface[fname]
|
field = interface[fname]
|
||||||
if getattr(field, 'suppress', False):
|
if getattr(field, 'suppress', False):
|
||||||
continue
|
continue
|
||||||
|
if getattr(field, 'hidden', False):
|
||||||
|
continue
|
||||||
info = fieldMapping.get(field.__class__)
|
info = fieldMapping.get(field.__class__)
|
||||||
f = createField(field, info)
|
f = createField(field, info)
|
||||||
if self.schemaProcessor is not None:
|
if self.schemaProcessor is not None:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2014 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,7 +42,33 @@ class GridFieldInstance(ListFieldInstance):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def columnTypes(self):
|
def columnTypes(self):
|
||||||
return [createField(t) for t in self.context.column_types]
|
fields = [createField(t) for t in self.context.column_types]
|
||||||
|
for f in fields:
|
||||||
|
f.linkedFields = [createField(sf)
|
||||||
|
for sf in getattr(f.baseField, 'linkedFields', [])]
|
||||||
|
return fields
|
||||||
|
|
||||||
|
#@Lazy
|
||||||
|
def columnTypesForLayout(self):
|
||||||
|
result = []
|
||||||
|
groups = {}
|
||||||
|
for idx, f in enumerate(self.columnTypes):
|
||||||
|
group = getattr(f.baseField, 'group', None)
|
||||||
|
if group is None:
|
||||||
|
result.append(dict(name=f.name,
|
||||||
|
label=(f.description or f.title),
|
||||||
|
fields=[f], indexes=[idx], group=None))
|
||||||
|
else:
|
||||||
|
g = groups.get(group.name)
|
||||||
|
if g is None:
|
||||||
|
g = dict(name=group.name, label=group.label,
|
||||||
|
fields=[f], indexes=[idx], group=group)
|
||||||
|
groups[group.name] = g
|
||||||
|
result.append(g)
|
||||||
|
else:
|
||||||
|
g['fields'].append(f)
|
||||||
|
g['indexes'].append(idx)
|
||||||
|
return result
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def columnFieldInstances(self):
|
def columnFieldInstances(self):
|
||||||
|
@ -122,6 +148,8 @@ class GridFieldInstance(ListFieldInstance):
|
||||||
def unmarshallRow(self, row, idx=None):
|
def unmarshallRow(self, row, idx=None):
|
||||||
item = {}
|
item = {}
|
||||||
cardinality = getattr(self.context, 'cardinality', None)
|
cardinality = getattr(self.context, 'cardinality', None)
|
||||||
|
ignoreInCheckOnEmpty = list(
|
||||||
|
getattr(self.context, 'ignoreInCheckOnEmpty', []))
|
||||||
for fi in self.columnFieldInstances:
|
for fi in self.columnFieldInstances:
|
||||||
if idx is not None:
|
if idx is not None:
|
||||||
fi.index = idx
|
fi.index = idx
|
||||||
|
@ -133,10 +161,9 @@ class GridFieldInstance(ListFieldInstance):
|
||||||
else:
|
else:
|
||||||
if fi.default is not None:
|
if fi.default is not None:
|
||||||
if value == fi.default:
|
if value == fi.default:
|
||||||
continue
|
ignoreInCheckOnEmpty.append(fi.name)
|
||||||
if value:
|
if value:
|
||||||
item[fi.name] = value
|
item[fi.name] = value
|
||||||
ignoreInCheckOnEmpty = getattr(self.context, 'ignoreInCheckOnEmpty', [])
|
|
||||||
for k, v in item.items():
|
for k, v in item.items():
|
||||||
if k not in ignoreInCheckOnEmpty: #and v != '__no_change__':
|
if k not in ignoreInCheckOnEmpty: #and v != '__no_change__':
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2014 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
|
||||||
|
@ -19,8 +19,6 @@
|
||||||
"""
|
"""
|
||||||
Basic classes for schemas, i.e. sets of fields that may be used for creating
|
Basic classes for schemas, i.e. sets of fields that may be used for creating
|
||||||
editing forms or display views for objects.
|
editing forms or display views for objects.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
|
|
|
@ -40,6 +40,10 @@ It's possible to leave some of the questions unanswered.
|
||||||
>>> resp02 = Response(quest, 'john')
|
>>> resp02 = Response(quest, 'john')
|
||||||
>>> resp02.values = {qu01: 2, qu03: 4}
|
>>> resp02.values = {qu01: 2, qu03: 4}
|
||||||
|
|
||||||
|
|
||||||
|
Evaluation
|
||||||
|
==========
|
||||||
|
|
||||||
Now let's calculate the result for resp01.
|
Now let's calculate the result for resp01.
|
||||||
|
|
||||||
>>> res = resp01.getResult()
|
>>> res = resp01.getResult()
|
||||||
|
@ -55,8 +59,8 @@ Now let's calculate the result for resp01.
|
||||||
fi03 4.0
|
fi03 4.0
|
||||||
fi01 2.4
|
fi01 2.4
|
||||||
|
|
||||||
Grouped Feedback Items
|
Grouped feedback items
|
||||||
======================
|
----------------------
|
||||||
|
|
||||||
>>> from cybertools.knowledge.survey.questionnaire import QuestionGroup
|
>>> from cybertools.knowledge.survey.questionnaire import QuestionGroup
|
||||||
>>> qugroup = QuestionGroup(quest)
|
>>> qugroup = QuestionGroup(quest)
|
||||||
|
@ -65,12 +69,25 @@ Grouped Feedback Items
|
||||||
>>> qugroup.feedbackItems = [fi01, fi02, fi03]
|
>>> qugroup.feedbackItems = [fi01, fi02, fi03]
|
||||||
|
|
||||||
>>> res = resp01.getGroupedResult()
|
>>> res = resp01.getGroupedResult()
|
||||||
>>> for qugroup, fi, score in res:
|
>>> for r in res:
|
||||||
... print fi.text, round(score, 2)
|
... print r['feedback'].text, round(r['score'], 2), r['rank']
|
||||||
fi02 0.58
|
fi02 0.58 1
|
||||||
|
|
||||||
>>> res = resp02.getGroupedResult()
|
>>> res = resp02.getGroupedResult()
|
||||||
>>> for qugroup, fi, score in res:
|
>>> for r in res:
|
||||||
... print fi.text, round(score, 2)
|
... print r['feedback'].text, round(r['score'], 2), r['rank']
|
||||||
fi03 0.75
|
fi03 0.75 1
|
||||||
|
|
||||||
|
Team evaluation
|
||||||
|
---------------
|
||||||
|
|
||||||
|
>>> resp03 = Response(quest, 'mary')
|
||||||
|
>>> resp03.values = {qu01: 1, qu02: 2, qu03: 4}
|
||||||
|
|
||||||
|
>>> resp01.values[qugroup] = resp01.getGroupedResult()[0]['score']
|
||||||
|
>>> resp03.values[qugroup] = resp03.getGroupedResult()[0]['score']
|
||||||
|
|
||||||
|
>>> teamData = resp01.getTeamResult([qugroup], [resp01, resp03])
|
||||||
|
>>> teamData
|
||||||
|
[{'average': 0.6666...}]
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,8 @@ class IResponse(Interface):
|
||||||
questionnaire = Attribute('The questionnaire this response belongs to.')
|
questionnaire = Attribute('The questionnaire this response belongs to.')
|
||||||
party = Attribute('Some identification of the party that responded '
|
party = Attribute('Some identification of the party that responded '
|
||||||
'to this questionnaire.')
|
'to this questionnaire.')
|
||||||
values = Attribute('A mapping associating response values with questions.')
|
values = Attribute('A mapping associating numeric response values with questions.')
|
||||||
|
texts = Attribute('A mapping associating text response values with questions.')
|
||||||
|
|
||||||
def getResult():
|
def getResult():
|
||||||
""" Calculate the result for this response.
|
""" Calculate the result for this response.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2015 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
|
||||||
|
@ -36,6 +36,9 @@ class Questionnaire(object):
|
||||||
self.responses = []
|
self.responses = []
|
||||||
self.defaultAnswerRange = 5
|
self.defaultAnswerRange = 5
|
||||||
|
|
||||||
|
def getQuestionGroups(self, party):
|
||||||
|
return self.questionGroups
|
||||||
|
|
||||||
|
|
||||||
class QuestionGroup(object):
|
class QuestionGroup(object):
|
||||||
|
|
||||||
|
@ -58,12 +61,8 @@ class Question(object):
|
||||||
self.feedbackItems = {}
|
self.feedbackItems = {}
|
||||||
self.text = text
|
self.text = text
|
||||||
self.revertAnswerOptions = False
|
self.revertAnswerOptions = False
|
||||||
|
self.questionType = 'value_selection'
|
||||||
def getAnswerRange(self):
|
self.answerRange = None
|
||||||
return self._answerRange or self.questionnaire.defaultAnswerRange
|
|
||||||
def setAnswerRange(self, value):
|
|
||||||
self._answerRange = value
|
|
||||||
answerRange = property(getAnswerRange, setAnswerRange)
|
|
||||||
|
|
||||||
|
|
||||||
class FeedbackItem(object):
|
class FeedbackItem(object):
|
||||||
|
@ -82,30 +81,68 @@ class Response(object):
|
||||||
self.questionnaire = questionnaire
|
self.questionnaire = questionnaire
|
||||||
self.party = party
|
self.party = party
|
||||||
self.values = {}
|
self.values = {}
|
||||||
|
self.texts = {}
|
||||||
|
|
||||||
def getResult(self):
|
def getResult(self):
|
||||||
result = {}
|
result = {}
|
||||||
for question, value in self.values.items():
|
for question, value in self.values.items():
|
||||||
|
if question.questionType != 'value_selection':
|
||||||
|
continue
|
||||||
for fi, rf in question.feedbackItems.items():
|
for fi, rf in question.feedbackItems.items():
|
||||||
if question.revertAnswerOptions:
|
if question.revertAnswerOptions:
|
||||||
value = question.answerRange - value - 1
|
answerRange = (question.answerRange or
|
||||||
|
self.questionnaire.defaultAnswerRange)
|
||||||
|
value = answerRange - value - 1
|
||||||
result[fi] = result.get(fi, 0.0) + rf * value
|
result[fi] = result.get(fi, 0.0) + rf * value
|
||||||
return sorted(result.items(), key=lambda x: -x[1])
|
return sorted(result.items(), key=lambda x: -x[1])
|
||||||
|
|
||||||
def getGroupedResult(self):
|
def getGroupedResult(self):
|
||||||
result = []
|
result = []
|
||||||
for qugroup in self.questionnaire.questionGroups:
|
for qugroup in self.questionnaire.getQuestionGroups(self.party):
|
||||||
score = scoreMax = 0.0
|
score = scoreMax = 0.0
|
||||||
for qu in qugroup.questions:
|
for qu in qugroup.questions:
|
||||||
value = self.values.get(qu)
|
if qu.questionType not in (None, 'value_selection'):
|
||||||
if value is None:
|
|
||||||
continue
|
continue
|
||||||
|
value = self.values.get(qu)
|
||||||
|
if value is None or isinstance(value, basestring):
|
||||||
|
continue
|
||||||
|
answerRange = (qu.answerRange or
|
||||||
|
self.questionnaire.defaultAnswerRange)
|
||||||
if qu.revertAnswerOptions:
|
if qu.revertAnswerOptions:
|
||||||
value = qu.answerRange - value - 1
|
value = answerRange - value - 1
|
||||||
score += value
|
score += value
|
||||||
scoreMax += qu.answerRange - 1
|
scoreMax += answerRange - 1
|
||||||
if scoreMax > 0.0:
|
if scoreMax > 0.0:
|
||||||
relScore = score / scoreMax
|
relScore = score / scoreMax
|
||||||
wScore = relScore * len(qugroup.feedbackItems) - 0.00001
|
wScore = relScore * len(qugroup.feedbackItems) - 0.00001
|
||||||
result.append((qugroup, qugroup.feedbackItems[int(wScore)], relScore))
|
if qugroup.feedbackItems:
|
||||||
|
feedback = qugroup.feedbackItems[int(wScore)]
|
||||||
|
else:
|
||||||
|
feedback = FeedbackItem()
|
||||||
|
result.append(dict(
|
||||||
|
group=qugroup,
|
||||||
|
feedback=feedback,
|
||||||
|
score=relScore))
|
||||||
|
ranks = getRanks([r['score'] for r in result])
|
||||||
|
for idx, r in enumerate(result):
|
||||||
|
r['rank'] = ranks[idx]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def getTeamResult(self, groups, teamData):
|
||||||
|
result = []
|
||||||
|
for idx, group in enumerate(groups):
|
||||||
|
values = [data.values.get(group) for data in teamData]
|
||||||
|
values = [v for v in values if v is not None]
|
||||||
|
#avg = sum(values) / len(teamData)
|
||||||
|
if not values:
|
||||||
|
continue
|
||||||
|
avg = sum(values) / len(values)
|
||||||
|
result.append(dict(group=group, average=avg))
|
||||||
|
ranks = getRanks([r['average'] for r in result])
|
||||||
|
for idx, r in enumerate(result):
|
||||||
|
r['rank'] = ranks[idx]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getRanks(values):
|
||||||
|
ordered = list(reversed(sorted(values)))
|
||||||
|
return [ordered.index(v) + 1 for v in values]
|
||||||
|
|
|
@ -29,11 +29,9 @@ from logging import getLogger
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os, re, sys
|
import os, re, sys
|
||||||
|
|
||||||
from zope import component
|
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from cybertools.media.interfaces import IMediaAsset, IFileTransform
|
from cybertools.media.interfaces import IMediaAsset
|
||||||
from cybertools.media.piltransform import PILTransform
|
from cybertools.media.piltransform import PILTransform
|
||||||
from cybertools.storage.filesystem import FileSystemStorage
|
|
||||||
|
|
||||||
TRANSFORM_STATEMENT = re.compile(r"\s*(\+?)([\w]+[\w\d]*)\(([^\)]*)\)\s*")
|
TRANSFORM_STATEMENT = re.compile(r"\s*(\+?)([\w]+[\w\d]*)\(([^\)]*)\)\s*")
|
||||||
|
|
||||||
|
@ -41,13 +39,16 @@ DEFAULT_FORMATS = {
|
||||||
"image": "image/jpeg"
|
"image": "image/jpeg"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parseTransformStatements(txStr):
|
def parseTransformStatements(txStr):
|
||||||
""" Parse statements in transform chain strings."""
|
""" Parse statements in transform chain strings."""
|
||||||
statements = TRANSFORM_STATEMENT.findall(txStr)
|
statements = TRANSFORM_STATEMENT.findall(txStr)
|
||||||
return statements
|
return statements
|
||||||
|
|
||||||
|
|
||||||
def getMimeBasetype(mimetype):
|
def getMimeBasetype(mimetype):
|
||||||
return mimetype.split("/",1)[0]
|
return mimetype.split("/", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
def getMimetypeExt(mimetype):
|
def getMimetypeExt(mimetype):
|
||||||
exts = mimetypes.guess_all_extensions(mimetype)
|
exts = mimetypes.guess_all_extensions(mimetype)
|
||||||
|
@ -74,9 +75,9 @@ class MediaAssetFile(object):
|
||||||
getLogger('cybertools.media.asset.MediaAssetFile').warn(
|
getLogger('cybertools.media.asset.MediaAssetFile').warn(
|
||||||
'Media asset directory %r not found.' % path)
|
'Media asset directory %r not found.' % path)
|
||||||
self.transform()
|
self.transform()
|
||||||
#return self.getOriginalData()
|
# return self.getOriginalData()
|
||||||
f = open(path, 'rb')
|
f = open(path, 'rb')
|
||||||
data =f.read()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2015 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
|
||||||
|
@ -20,8 +20,6 @@
|
||||||
Views for displaying media assets.
|
Views for displaying media assets.
|
||||||
|
|
||||||
Authors: Johann Schimpf, Erich Seifert.
|
Authors: Johann Schimpf, Erich Seifert.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
@ -39,6 +37,8 @@ from zope.interface import implements
|
||||||
from cybertools.media.interfaces import IMediaAsset, IFileTransform
|
from cybertools.media.interfaces import IMediaAsset, IFileTransform
|
||||||
from cybertools.storage.filesystem import FileSystemStorage
|
from cybertools.storage.filesystem import FileSystemStorage
|
||||||
|
|
||||||
|
logger = getLogger('cybertools.media.piltransform.PILTransform')
|
||||||
|
|
||||||
|
|
||||||
def mimetypeToPIL(mimetype):
|
def mimetypeToPIL(mimetype):
|
||||||
return mimetype.split("/",1)[-1]
|
return mimetype.split("/",1)[-1]
|
||||||
|
@ -55,8 +55,7 @@ class PILTransform(object):
|
||||||
try:
|
try:
|
||||||
self.im = Image.open(path)
|
self.im = Image.open(path)
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
from logging import getLogger
|
logger.warn(e)
|
||||||
getLogger('cybertools.media.piltransform.PILTransform').warn(e)
|
|
||||||
self.im = None
|
self.im = None
|
||||||
|
|
||||||
def rotate(self, angle, resize):
|
def rotate(self, angle, resize):
|
||||||
|
@ -97,7 +96,7 @@ class PILTransform(object):
|
||||||
box = (left, upper, right, lower)
|
box = (left, upper, right, lower)
|
||||||
self.im = self.im.crop(box)
|
self.im = self.im.crop(box)
|
||||||
|
|
||||||
def resize(self, width, height=None):
|
def resize(self, width, height=None, fill=False):
|
||||||
if self.im is None:
|
if self.im is None:
|
||||||
return
|
return
|
||||||
if not height:
|
if not height:
|
||||||
|
@ -105,10 +104,21 @@ class PILTransform(object):
|
||||||
ratio = float(ow) / float(oh)
|
ratio = float(ow) / float(oh)
|
||||||
height = int(round(float(width) / ratio))
|
height = int(round(float(width) / ratio))
|
||||||
dims = (width, height)
|
dims = (width, height)
|
||||||
self.im.thumbnail(dims, Image.ANTIALIAS)
|
if fill:
|
||||||
|
image = self.im
|
||||||
|
image.thumbnail(dims, Image.ANTIALIAS)
|
||||||
|
new = Image.new('RGBA', dims, (255, 255, 255, 0)) #with alpha
|
||||||
|
new.paste(image,((dims[0] - image.size[0]) / 2,
|
||||||
|
(dims[1] - image.size[1]) / 2))
|
||||||
|
self.im = new
|
||||||
|
return new
|
||||||
|
return self.im.thumbnail(dims, Image.ANTIALIAS)
|
||||||
|
|
||||||
def save(self, path, mimetype):
|
def save(self, path, mimetype):
|
||||||
if self.im is None:
|
if self.im is None:
|
||||||
return
|
return
|
||||||
format = mimetypeToPIL(mimetype)
|
format = mimetypeToPIL(mimetype)
|
||||||
|
try:
|
||||||
self.im.save(path)
|
self.im.save(path)
|
||||||
|
except IOError, e:
|
||||||
|
logger.warn(e)
|
||||||
|
|
|
@ -86,6 +86,7 @@ class IPerson(Interface):
|
||||||
description=_(u'The date of birth - should be a '
|
description=_(u'The date of birth - should be a '
|
||||||
'datetime.date object.'),
|
'datetime.date object.'),
|
||||||
required=False,)
|
required=False,)
|
||||||
|
birthDate.hideTime = True
|
||||||
|
|
||||||
age = schema.Int(
|
age = schema.Int(
|
||||||
title=_(u'Age'),
|
title=_(u'Age'),
|
||||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ msgstr ""
|
||||||
|
|
||||||
"Project-Id-Version: $Id$\n"
|
"Project-Id-Version: $Id$\n"
|
||||||
"POT-Creation-Date: 2008-12-01 12:00 CET\n"
|
"POT-Creation-Date: 2008-12-01 12:00 CET\n"
|
||||||
"PO-Revision-Date: 2013-05-17 12:00 CET\n"
|
"PO-Revision-Date: 2014-05-12 12:00 CET\n"
|
||||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -66,17 +66,31 @@ msgid "End date"
|
||||||
msgstr "Ende"
|
msgstr "Ende"
|
||||||
|
|
||||||
msgid "Street, number"
|
msgid "Street, number"
|
||||||
msgstr "Straße, Nr."
|
msgstr "Straße, Hausnummer"
|
||||||
|
|
||||||
msgid "City"
|
msgid "Street and number"
|
||||||
msgstr "Stadt"
|
msgstr "Straße und Hausnummer"
|
||||||
|
|
||||||
msgid "ZIP code"
|
msgid "ZIP code"
|
||||||
msgstr "PLZ"
|
msgstr "Postleitzahl"
|
||||||
|
|
||||||
|
msgid "ZIP code, postal code"
|
||||||
|
msgstr "Postleitzahl"
|
||||||
|
|
||||||
|
msgid "City"
|
||||||
|
msgstr "Ort"
|
||||||
|
|
||||||
|
msgid "Name of the city"
|
||||||
|
msgstr "Name der Stadt/des Orts"
|
||||||
|
|
||||||
|
msgid "Country code"
|
||||||
|
msgstr "Ländercode"
|
||||||
|
|
||||||
|
msgid "International two-letter country code"
|
||||||
|
msgstr "Aus zwei Buchstaben bestehender Ländercode"
|
||||||
|
|
||||||
msgid "Additional lines"
|
msgid "Additional lines"
|
||||||
msgstr "Zusätzliche Zeilen"
|
msgstr "Zusätzliche Adresszeilen"
|
||||||
|
|
||||||
msgid "Additional address lines"
|
msgid "Additional address lines"
|
||||||
msgstr "Zusätzliche Zeilen in Anschrift"
|
msgstr "Zusätzliche Adresszeilen"
|
||||||
|
|
||||||
|
|
117
organize/work.py
117
organize/work.py
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2018 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,7 +42,7 @@ def workItemStates():
|
||||||
return StatesDefinition('workItemStates',
|
return StatesDefinition('workItemStates',
|
||||||
State('new', 'new',
|
State('new', 'new',
|
||||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||||
'cancel', 'reopen'),
|
'cancel', 'reopen'), # 'move', # ?
|
||||||
color='red'),
|
color='red'),
|
||||||
State('planned', 'planned',
|
State('planned', 'planned',
|
||||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||||
|
@ -52,14 +52,14 @@ def workItemStates():
|
||||||
'move', 'cancel', 'modify'),
|
'move', 'cancel', 'modify'),
|
||||||
color='yellow'),
|
color='yellow'),
|
||||||
State('running', 'running',
|
State('running', 'running',
|
||||||
('work', 'finish', 'move', 'cancel', 'modify'),
|
('work', 'finish', 'move', 'cancel', 'modify'), # 'delegate', # ?
|
||||||
color='orange'),
|
color='orange'),
|
||||||
State('done', 'done',
|
State('done', 'done',
|
||||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||||
'move', 'cancel', 'modify'), color='lightgreen'),
|
'move', 'cancel', 'modify'), color='lightgreen'),
|
||||||
State('finished', 'finished',
|
State('finished', 'finished',
|
||||||
('plan', 'accept', 'start', 'work', 'finish',
|
('plan', 'accept', 'start', 'work', 'finish',
|
||||||
'move', 'modify', 'close'),
|
'move', 'modify', 'close', 'cancel'),
|
||||||
color='green'),
|
color='green'),
|
||||||
State('cancelled', 'cancelled',
|
State('cancelled', 'cancelled',
|
||||||
('plan', 'accept', 'start', 'work', 'move', 'modify', 'close'),
|
('plan', 'accept', 'start', 'work', 'move', 'modify', 'close'),
|
||||||
|
@ -79,8 +79,12 @@ def workItemStates():
|
||||||
State('replaced', 'replaced', (), color='grey'),
|
State('replaced', 'replaced', (), color='grey'),
|
||||||
State('planned_x', 'planned', (), color='red'),
|
State('planned_x', 'planned', (), color='red'),
|
||||||
State('accepted_x', 'accepted', (), color='yellow'),
|
State('accepted_x', 'accepted', (), color='yellow'),
|
||||||
State('done_x', 'done', (), color='lightgreen'),
|
State('done_x', 'done',
|
||||||
State('finished_x', 'finished', (), color='green'),
|
('modify', 'move', 'cancel'), color='lightgreen'),
|
||||||
|
State('finished_x', 'finished',
|
||||||
|
('modify','move', 'cancel'), color='green'),
|
||||||
|
#State('done_y', 'done', (), color='grey'),
|
||||||
|
#State('finished_y', 'finished', (), color='grey'),
|
||||||
# transitions:
|
# transitions:
|
||||||
Transition('plan', 'plan', 'planned'),
|
Transition('plan', 'plan', 'planned'),
|
||||||
Transition('accept', 'accept', 'accepted'),
|
Transition('accept', 'accept', 'accepted'),
|
||||||
|
@ -96,7 +100,8 @@ def workItemStates():
|
||||||
initialState='new')
|
initialState='new')
|
||||||
|
|
||||||
|
|
||||||
fieldNames = ['title', 'description', 'deadline', 'start', 'end',
|
fieldNames = ['title', 'description', 'deadline', 'priority', 'activity',
|
||||||
|
'start', 'end',
|
||||||
'duration', 'effort',
|
'duration', 'effort',
|
||||||
'comment', 'party'] # for use in editingRules
|
'comment', 'party'] # for use in editingRules
|
||||||
|
|
||||||
|
@ -106,21 +111,21 @@ fieldNames = ['title', 'description', 'deadline', 'start', 'end',
|
||||||
# . default (may be empty)
|
# . default (may be empty)
|
||||||
|
|
||||||
editingRules = dict(
|
editingRules = dict(
|
||||||
plan = {'*': '+++.....+'},
|
plan = {'*': '+++++.....+'},
|
||||||
accept = {'*': '+++.....-',
|
accept = {'*': '+++++.....-',
|
||||||
'planned': '+++++++.-',
|
'planned': '+++++++++.-',
|
||||||
'accepted': '+++++++.-'},
|
'accepted': '+++++++++.-'},
|
||||||
start = {'*': '+++./...-'},
|
start = {'*': '+++++./...-'},
|
||||||
work = {'*': '+++.....-',
|
work = {'*': '+++++.....-',
|
||||||
'running': '++++....-'},
|
'running': '++++++....-'},
|
||||||
finish = {'*': '+++.....-',
|
finish = {'*': '+++++.....-',
|
||||||
'running': '++++....-'},
|
'running': '++++++....-'},
|
||||||
cancel = {'*': '+++////./'},
|
cancel = {'*': '+++++////./'},
|
||||||
modify = {'*': '+++++++++'},
|
modify = {'*': '+++++++++++'},
|
||||||
delegate= {'*': '+++++++.+'},
|
delegate= {'*': '+++++++++.+'},
|
||||||
move = {'*': '+++++++.-'},
|
move = {'*': '+++++++++.-'},
|
||||||
close = {'*': '+++////./'},
|
close = {'*': '+++++////./'},
|
||||||
reopen = {'*': '+++////./'},
|
reopen = {'*': '+++++////./'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +143,8 @@ class WorkItemType(object):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.description = description
|
self.description = description
|
||||||
self.actions = actions or list(editingRules)
|
self.actions = actions or list(editingRules)
|
||||||
self.fields = fields or ('deadline', 'start-end', 'duration-effort')
|
self.fields = fields or ('deadline', 'priority', 'activity',
|
||||||
|
'start-end', 'duration-effort')
|
||||||
self.indicator = indicator
|
self.indicator = indicator
|
||||||
self.delegatedState = delegatedState
|
self.delegatedState = delegatedState
|
||||||
self.prefillDate = prefillDate
|
self.prefillDate = prefillDate
|
||||||
|
@ -156,9 +162,10 @@ workItemTypes = Jeep((
|
||||||
fields =('deadline',),
|
fields =('deadline',),
|
||||||
indicator='work_deadline'),
|
indicator='work_deadline'),
|
||||||
WorkItemType('checkup', u'Check-up',
|
WorkItemType('checkup', u'Check-up',
|
||||||
actions=('plan', 'accept', 'finish', 'cancel',
|
actions=('plan', 'accept', 'start', 'finish', 'cancel',
|
||||||
'modify', 'delegate', 'close', 'reopen'),
|
'modify', 'delegate', 'close', 'reopen'),
|
||||||
fields =('deadline', 'start-end',),
|
#fields =('deadline', 'start-end',),
|
||||||
|
fields =('deadline', 'daterange',),
|
||||||
indicator='work_checkup',
|
indicator='work_checkup',
|
||||||
delegatedState='closed', prefillDate=False),
|
delegatedState='closed', prefillDate=False),
|
||||||
))
|
))
|
||||||
|
@ -177,7 +184,7 @@ class WorkItem(Stateful, Track):
|
||||||
statesDefinition = 'organize.workItemStates'
|
statesDefinition = 'organize.workItemStates'
|
||||||
|
|
||||||
initAttributes = set(['workItemType', 'party', 'title', 'description',
|
initAttributes = set(['workItemType', 'party', 'title', 'description',
|
||||||
'deadline', 'start', 'end',
|
'deadline', 'priority', 'activity', 'start', 'end',
|
||||||
'duration', 'effort'])
|
'duration', 'effort'])
|
||||||
|
|
||||||
def __init__(self, taskId, runId, userName, data):
|
def __init__(self, taskId, runId, userName, data):
|
||||||
|
@ -225,13 +232,16 @@ class WorkItem(Stateful, Track):
|
||||||
return list(getParent(self).query(runId=self.runId))
|
return list(getParent(self).query(runId=self.runId))
|
||||||
|
|
||||||
def doAction(self, action, userName, **kw):
|
def doAction(self, action, userName, **kw):
|
||||||
if self != self.currentWorkItems[-1]:
|
#if self != self.currentWorkItems[-1]:
|
||||||
raise ValueError("Actions are only allowed on the last item of a run.")
|
# raise ValueError("Actions are only allowed on the last item of a run.")
|
||||||
if action not in [t.name for t in self.getAvailableTransitions()]:
|
if action not in [t.name for t in self.getAvailableTransitions()]:
|
||||||
raise ValueError("Action '%s' not allowed in state '%s'" %
|
raise ValueError("Action '%s' not allowed in state '%s'" %
|
||||||
(action, self.state))
|
(action, self.state))
|
||||||
if action in self.specialActions:
|
if action in self.specialActions:
|
||||||
return self.specialActions[action](self, userName, **kw)
|
return self.specialActions[action](self, userName, **kw)
|
||||||
|
return self.doStandardAction(action, userName, **kw)
|
||||||
|
|
||||||
|
def doStandardAction(self, action, userName, **kw):
|
||||||
if self.state == 'new':
|
if self.state == 'new':
|
||||||
self.setData(**kw)
|
self.setData(**kw)
|
||||||
self.doTransition(action)
|
self.doTransition(action)
|
||||||
|
@ -244,6 +254,9 @@ class WorkItem(Stateful, Track):
|
||||||
elif self.state in ('planned', 'accepted', 'done'):
|
elif self.state in ('planned', 'accepted', 'done'):
|
||||||
self.state = self.state + '_x'
|
self.state = self.state + '_x'
|
||||||
self.reindex('state')
|
self.reindex('state')
|
||||||
|
elif self.state in ('finished',) and action == 'cancel':
|
||||||
|
self.state = self.state + '_x'
|
||||||
|
self.reindex('state')
|
||||||
new.doTransition(action)
|
new.doTransition(action)
|
||||||
new.reindex()
|
new.reindex()
|
||||||
return new
|
return new
|
||||||
|
@ -266,6 +279,9 @@ class WorkItem(Stateful, Track):
|
||||||
if self.state in ('planned', 'accepted', 'delegated', 'moved', 'done'):
|
if self.state in ('planned', 'accepted', 'delegated', 'moved', 'done'):
|
||||||
self.state = self.state + '_x'
|
self.state = self.state + '_x'
|
||||||
self.reindex('state')
|
self.reindex('state')
|
||||||
|
#elif self.state == 'running':
|
||||||
|
# self.doAction('work', userName,
|
||||||
|
# end=(kw.get('end') or getTimeStamp()))
|
||||||
xkw = dict(kw)
|
xkw = dict(kw)
|
||||||
xkw.pop('party', None)
|
xkw.pop('party', None)
|
||||||
delegated = self.createNew('delegate', userName, **xkw)
|
delegated = self.createNew('delegate', userName, **xkw)
|
||||||
|
@ -279,25 +295,57 @@ class WorkItem(Stateful, Track):
|
||||||
delegated.data['target'] = new.name
|
delegated.data['target'] = new.name
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
def doStart(self, userName, **kw):
|
||||||
|
action = 'start'
|
||||||
|
# stop any running work item of user:
|
||||||
|
# TODO: check: party query OK?
|
||||||
|
if (userName == self.userName and
|
||||||
|
self.workItemType in (None, 'work') and
|
||||||
|
self.state != 'running'):
|
||||||
|
running = IWorkItems(getParent(self)).query(
|
||||||
|
party=userName, state='running')
|
||||||
|
for wi in running:
|
||||||
|
if wi.workItemType in (None, 'work'):
|
||||||
|
wi.doAction('work', userName,
|
||||||
|
end=(kw.get('start') or getTimeStamp()))
|
||||||
|
# standard creation of new work item:
|
||||||
|
if not kw.get('start'):
|
||||||
|
kw['start'] = getTimeStamp()
|
||||||
|
kw['end'] = None
|
||||||
|
kw['duration'] = kw['effort'] = 0
|
||||||
|
return self.doStandardAction(action, userName, **kw)
|
||||||
|
|
||||||
def move(self, userName, **kw):
|
def move(self, userName, **kw):
|
||||||
xkw = dict(kw)
|
xkw = dict(kw)
|
||||||
for k in ('deadline', 'start', 'end'):
|
for k in ('deadline', 'start', 'end'):
|
||||||
xkw.pop(k, None) # do not change on source item
|
xkw.pop(k, None) # do not change on source item
|
||||||
|
if self.state == 'new': # should this be possible?
|
||||||
|
moved = self
|
||||||
|
self.setData(kw)
|
||||||
|
if self.state in ('done', 'finished', 'running', 'done_x', 'finished_x'):
|
||||||
|
moved = self # is this OK? or better new state ..._y?
|
||||||
|
else:
|
||||||
moved = self.createNew('move', userName, **xkw)
|
moved = self.createNew('move', userName, **xkw)
|
||||||
moved.userName = self.userName
|
moved.userName = self.userName
|
||||||
moved.state = 'moved'
|
|
||||||
moved.reindex()
|
|
||||||
task = kw.pop('task', None)
|
task = kw.pop('task', None)
|
||||||
new = moved.createNew(None, userName, taskId=task, runId=0, **kw)
|
new = moved.createNew(None, userName, taskId=task, runId=0, **kw)
|
||||||
new.userName = self.userName
|
new.userName = self.userName
|
||||||
new.data['source'] = moved.name
|
new.data['source'] = moved.name
|
||||||
|
if self.state == 'new':
|
||||||
|
new.state = 'planned'
|
||||||
|
else:
|
||||||
new.state = self.state
|
new.state = self.state
|
||||||
new.reindex()
|
new.reindex()
|
||||||
moved.data['target'] = new.name
|
moved.data['target'] = new.name
|
||||||
if self.state in ('planned', 'accepted', 'delegated', 'moved',
|
moved.state = 'moved'
|
||||||
'done', 'finished'):
|
moved.reindex()
|
||||||
|
if self.state in ('planned', 'accepted', 'delegated', 'moved'):
|
||||||
|
#'done', 'finished'):
|
||||||
self.state = self.state + '_x'
|
self.state = self.state + '_x'
|
||||||
self.reindex('state')
|
self.reindex('state')
|
||||||
|
#elif self.state in ('done', 'finished'):
|
||||||
|
# self.state = self.state + '_y'
|
||||||
|
# self.reindex('state')
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def close(self, userName, **kw):
|
def close(self, userName, **kw):
|
||||||
|
@ -315,7 +363,8 @@ class WorkItem(Stateful, Track):
|
||||||
item.reindex('state')
|
item.reindex('state')
|
||||||
return new
|
return new
|
||||||
|
|
||||||
specialActions = dict(modify=modify, delegate=delegate, move=move,
|
specialActions = dict(modify=modify, delegate=delegate,
|
||||||
|
start=doStart, move=move,
|
||||||
close=close)
|
close=close)
|
||||||
|
|
||||||
def setData(self, ignoreParty=False, **kw):
|
def setData(self, ignoreParty=False, **kw):
|
||||||
|
@ -328,7 +377,7 @@ class WorkItem(Stateful, Track):
|
||||||
self.reindex('userName')
|
self.reindex('userName')
|
||||||
start = kw.get('start') or kw.get('deadline') # TODO: check OK?
|
start = kw.get('start') or kw.get('deadline') # TODO: check OK?
|
||||||
if start is not None:
|
if start is not None:
|
||||||
self.timeStamp = start
|
self.timeStamp = start # TODO: better use end
|
||||||
self.reindex('timeStamp')
|
self.reindex('timeStamp')
|
||||||
data = self.data
|
data = self.data
|
||||||
for k, v in kw.items():
|
for k, v in kw.items():
|
||||||
|
|
|
@ -100,7 +100,7 @@ but may also be specified explicitly.
|
||||||
>>> wi03 = wi02.doAction('start', 'jim', start=1229958000)
|
>>> wi03 = wi02.doAction('start', 'jim', start=1229958000)
|
||||||
>>> wi03
|
>>> wi03
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'running']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'running']:
|
||||||
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
{'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||||
|
|
||||||
Stopping and finishing work
|
Stopping and finishing work
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -113,7 +113,7 @@ as "running" will be replaced by a new one.
|
||||||
|
|
||||||
>>> wi03
|
>>> wi03
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'replaced']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'replaced']:
|
||||||
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
{'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||||
>>> wi04
|
>>> wi04
|
||||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'done']:
|
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'done']:
|
||||||
{'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>
|
{'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>
|
||||||
|
|
15
setup.py
15
setup.py
|
@ -1,18 +1,18 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
version = '2.0'
|
version = '2.1.4'
|
||||||
|
|
||||||
setup(name='cybertools',
|
setup(name='cybertools',
|
||||||
version=version,
|
version=version,
|
||||||
description="cybertools",
|
description="cybertools: basic utilities for Zope3/bluebream/loops",
|
||||||
long_description="""\
|
long_description="""\
|
||||||
""",
|
""",
|
||||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||||
keywords='',
|
keywords='',
|
||||||
author='Helmut Merz',
|
author='cyberconcepts.org team',
|
||||||
author_email='helmutm@cy55.de',
|
author_email='team@cyberconcepts.org',
|
||||||
url='',
|
url='https://www.cyberconcepts.org',
|
||||||
license='GPL',
|
license='GPL',
|
||||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
@ -20,14 +20,11 @@ setup(name='cybertools',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
# -*- Extra requirements: -*-
|
# -*- Extra requirements: -*-
|
||||||
'lxml',
|
'lxml',
|
||||||
#'PIL',
|
#'Pillow',
|
||||||
'zope.app.catalog',
|
'zope.app.catalog',
|
||||||
'zope.app.file',
|
'zope.app.file',
|
||||||
'zope.app.intid',
|
'zope.app.intid',
|
||||||
'zope.app.preview',
|
|
||||||
'zope.app.renderer',
|
|
||||||
'zope.app.session',
|
'zope.app.session',
|
||||||
'zope.dublincore',
|
|
||||||
'zope.sendmail',
|
'zope.sendmail',
|
||||||
],
|
],
|
||||||
entry_points="""
|
entry_points="""
|
||||||
|
|
|
@ -50,7 +50,7 @@ class Stateful(object):
|
||||||
|
|
||||||
def getStateObject(self):
|
def getStateObject(self):
|
||||||
states = self.getStatesDefinition().states
|
states = self.getStatesDefinition().states
|
||||||
if self.state not in states:
|
if self.getState() not in states:
|
||||||
self.state = self.getStatesDefinition().initialState
|
self.state = self.getStatesDefinition().initialState
|
||||||
return states[self.state]
|
return states[self.state]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2016 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
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ZODB-/BTree-based implementation of user interaction tracking.
|
ZODB-/BTree-based implementation of user interaction tracking.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
@ -142,11 +140,12 @@ class TrackingStorage(BTreeContainer):
|
||||||
|
|
||||||
def setupIndexes(self):
|
def setupIndexes(self):
|
||||||
changed = False
|
changed = False
|
||||||
for idx in self.indexAttributes:
|
for idx in self.trackFactory.index_attributes:
|
||||||
if idx not in self.indexes:
|
if idx not in self.indexes:
|
||||||
self.indexes[idx] = FieldIndex()
|
self.indexes[idx] = FieldIndex()
|
||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
|
self.indexAttributes = self.trackFactory.index_attributes
|
||||||
self.reindexTracks()
|
self.reindexTracks()
|
||||||
|
|
||||||
def idFromNum(self, num):
|
def idFromNum(self, num):
|
||||||
|
@ -237,6 +236,7 @@ class TrackingStorage(BTreeContainer):
|
||||||
self.unindexTrack(trackNum, track)
|
self.unindexTrack(trackNum, track)
|
||||||
|
|
||||||
def indexTrack(self, trackNum, track, idx=None):
|
def indexTrack(self, trackNum, track, idx=None):
|
||||||
|
#self.setupIndexes()
|
||||||
if not trackNum:
|
if not trackNum:
|
||||||
trackNum = int(track.__name__)
|
trackNum = int(track.__name__)
|
||||||
data = track.indexdata
|
data = track.indexdata
|
||||||
|
@ -292,7 +292,11 @@ class TrackingStorage(BTreeContainer):
|
||||||
value = [value]
|
value = [value]
|
||||||
resultx = None
|
resultx = None
|
||||||
for v in value:
|
for v in value:
|
||||||
resultx = self.union(resultx, self.indexes[idx].apply((v, v)))
|
v2 = v
|
||||||
|
if isinstance(v, basestring) and v.endswith('*'):
|
||||||
|
v = v[:-1]
|
||||||
|
v2 = v + 'z'
|
||||||
|
resultx = self.union(resultx, self.indexes[idx].apply((v, v2)))
|
||||||
result = self.intersect(result, resultx)
|
result = self.intersect(result, resultx)
|
||||||
elif idx == 'timeFrom':
|
elif idx == 'timeFrom':
|
||||||
result = self.intersect(result,
|
result = self.intersect(result,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2014 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
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Interface definitions for tracking of user interactions.
|
Interface definitions for tracking of user interactions.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
from zope.interface import Interface, Attribute
|
||||||
|
@ -50,6 +48,12 @@ class ITrack(Interface):
|
||||||
# """ Return the internal name (ID) of the track.
|
# """ Return the internal name (ID) of the track.
|
||||||
# """
|
# """
|
||||||
|
|
||||||
|
def update(newData, overwrite=False):
|
||||||
|
""" Update the track with new data, by default creating a new
|
||||||
|
track. Overwrite the existing track if the corresponding
|
||||||
|
flag is set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ITrackingStorage(Interface):
|
class ITrackingStorage(Interface):
|
||||||
""" A utility for storing user tracks.
|
""" A utility for storing user tracks.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div id="global" metal:define-macro="global">
|
<div id="global" metal:define-macro="global">
|
||||||
<div class="top" metal:define-slot="top">
|
<div class="top" metal:define-slot="top">
|
||||||
<a href="#" name="top" metal:define-slot="logo"
|
<a href="#" name="top" metal:define-slot="logo"
|
||||||
tal:attributes="href string:${request/URL/1}"><img class="logo"
|
tal:attributes="href string:${view/requestUrl/1}"><img class="logo"
|
||||||
border="0" alt="Home"
|
border="0" alt="Home"
|
||||||
tal:attributes="src string:${resourceBase}logo.png" /></a>
|
tal:attributes="src string:${resourceBase}logo.png" /></a>
|
||||||
<div metal:define-slot="top-actions">
|
<div metal:define-slot="top-actions">
|
||||||
|
|
|
@ -58,6 +58,7 @@ class ExternalEditorView(object):
|
||||||
r.append('cookie:' + cookie)
|
r.append('cookie:' + cookie)
|
||||||
r.append('')
|
r.append('')
|
||||||
r.append(fromUnicode(data))
|
r.append(fromUnicode(data))
|
||||||
|
r = [str(item) for item in r]
|
||||||
result = '\n'.join(r)
|
result = '\n'.join(r)
|
||||||
self.setHeaders(len(result))
|
self.setHeaders(len(result))
|
||||||
return result
|
return result
|
||||||
|
|
Loading…
Add table
Reference in a new issue