diff --git a/browser/blue/controller.py b/browser/blue/controller.py index b678076..0df524a 100644 --- a/browser/blue/controller.py +++ b/browser/blue/controller.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -37,9 +37,9 @@ class Controller(BaseController): macros = self.macros presentationMode = self.request.get('liquid.viewmode') == 'presentation' params = [('blue/blue8.css', 'all', 20, False), - ('print.css', 'print', 25, False), ('blue/ie.css', 'all', 30, True), - ('custom.css', 'all', 100, False)] + ('custom.css', 'all', 100, False), + ('print.css', 'print', 200, False),] #if presentationMode: # params.append(('presentation.css', 'all', 30, False)) for id, media, prio, ie in params: diff --git a/browser/icons/page_copy.png b/browser/icons/page_copy.png new file mode 100644 index 0000000..195dc6d Binary files /dev/null and b/browser/icons/page_copy.png differ diff --git a/browser/icons/page_delete.png b/browser/icons/page_delete.png new file mode 100644 index 0000000..3141467 Binary files /dev/null and b/browser/icons/page_delete.png differ diff --git a/composer/layout/browser/standard.pt b/composer/layout/browser/standard.pt index dc36878..4bb846f 100644 --- a/composer/layout/browser/standard.pt +++ b/composer/layout/browser/standard.pt @@ -1,5 +1,5 @@ + tal:condition="view/update"> diff --git a/composer/report/base.py b/composer/report/base.py index 5b63a09..b3b34d3 100644 --- a/composer/report/base.py +++ b/composer/report/base.py @@ -83,6 +83,7 @@ class Report(Template): queryCriteria = None outputFields = () sortCriteria = () + sortDescending = False limits = None diff --git a/composer/report/result.py b/composer/report/result.py index 195a4a3..1dab6d7 100755 --- a/composer/report/result.py +++ b/composer/report/result.py @@ -115,13 +115,15 @@ class ResultSet(object): def __init__(self, context, data, rowFactory=Row, headerRowFactory=GroupHeaderRow, - sortCriteria=None, queryCriteria=BaseQueryCriteria(), + sortCriteria=None, sortDescending=False, + queryCriteria=BaseQueryCriteria(), limits=None): self.context = context # the report or report instance self.data = data self.rowFactory = rowFactory self.headerRowFactory = headerRowFactory self.sortCriteria = sortCriteria + self.sortDescending = sortDescending self.queryCriteria = queryCriteria self.limits = limits self.totals = TotalsRow(None, self) @@ -184,7 +186,8 @@ class ResultSet(object): result = [row for row in result if self.queryCriteria.check(row)] if self.sortCriteria: 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: res = [] groupValues = [None for f in self.groupColumns] diff --git a/composer/schema/browser/schema_macros.pt b/composer/schema/browser/schema_macros.pt index f5138df..20d9334 100755 --- a/composer/schema/browser/schema_macros.pt +++ b/composer/schema/browser/schema_macros.pt @@ -144,7 +144,11 @@ + tal:attributes="name name;" + onchange="if (this.form.title.value == '') { + var value = this.value.split('\\'); + this.form.title.value = value[value.length-1]; + }" /> diff --git a/composer/schema/field.py b/composer/schema/field.py index 5bbc020..8222e01 100644 --- a/composer/schema/field.py +++ b/composer/schema/field.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -42,6 +42,14 @@ from cybertools.composer.schema.schema import formErrors from cybertools.util.format import toStr, toUnicode +class FieldGroup(object): + + def __init__(self, name, label, sublabels=[]): + self.name = name + self.label = label + self.sublabels = sublabels + + class Field(Component): implements(IField) diff --git a/composer/schema/grid/field.py b/composer/schema/grid/field.py index 2e596b6..5fca6c6 100644 --- a/composer/schema/grid/field.py +++ b/composer/schema/grid/field.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -42,7 +42,33 @@ class GridFieldInstance(ListFieldInstance): @Lazy 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 def columnFieldInstances(self): @@ -122,6 +148,8 @@ class GridFieldInstance(ListFieldInstance): def unmarshallRow(self, row, idx=None): item = {} cardinality = getattr(self.context, 'cardinality', None) + ignoreInCheckOnEmpty = list( + getattr(self.context, 'ignoreInCheckOnEmpty', [])) for fi in self.columnFieldInstances: if idx is not None: fi.index = idx @@ -133,10 +161,9 @@ class GridFieldInstance(ListFieldInstance): else: if fi.default is not None: if value == fi.default: - continue + ignoreInCheckOnEmpty.append(fi.name) if value: item[fi.name] = value - ignoreInCheckOnEmpty = getattr(self.context, 'ignoreInCheckOnEmpty', []) for k, v in item.items(): if k not in ignoreInCheckOnEmpty: #and v != '__no_change__': return item @@ -215,7 +242,8 @@ class KeyTableFieldInstance(RecordsFieldInstance): for k, v in value.items(): row = [k] for idx, fi in enumerate(self.columnFieldInstances[1:]): - row.append(fi.display(v[idx])) + if idx < len(v): + row.append(fi.display(v[idx])) rows.append(row) return dict(headers=headers, rows=rows) @@ -226,7 +254,8 @@ class KeyTableFieldInstance(RecordsFieldInstance): for k, v in value.items(): item = {self.keyName: k} for idx, name in enumerate(self.dataNames): - item[name] = v[idx] + if idx < len(v): + item[name] = v[idx] result.append(item) return result diff --git a/composer/schema/grid/grid_macros.pt b/composer/schema/grid/grid_macros.pt index aaf16f7..5422b50 100755 --- a/composer/schema/grid/grid_macros.pt +++ b/composer/schema/grid/grid_macros.pt @@ -57,7 +57,7 @@ tal:attributes="class string:${column/baseField/cssClass|string:}"> diff --git a/composer/schema/schema.py b/composer/schema/schema.py index c8bd8eb..92f50e0 100644 --- a/composer/schema/schema.py +++ b/composer/schema/schema.py @@ -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 # 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 editing forms or display views for objects. - -$Id$ """ from zope.interface import implements diff --git a/knowledge/survey/README.txt b/knowledge/survey/README.txt index 9315131..44da8f7 100644 --- a/knowledge/survey/README.txt +++ b/knowledge/survey/README.txt @@ -40,6 +40,10 @@ It's possible to leave some of the questions unanswered. >>> resp02 = Response(quest, 'john') >>> resp02.values = {qu01: 2, qu03: 4} + +Evaluation +========== + Now let's calculate the result for resp01. >>> res = resp01.getResult() @@ -55,8 +59,8 @@ Now let's calculate the result for resp01. fi03 4.0 fi01 2.4 -Grouped Feedback Items -====================== +Grouped feedback items +---------------------- >>> from cybertools.knowledge.survey.questionnaire import QuestionGroup >>> qugroup = QuestionGroup(quest) @@ -65,12 +69,25 @@ Grouped Feedback Items >>> qugroup.feedbackItems = [fi01, fi02, fi03] >>> res = resp01.getGroupedResult() - >>> for qugroup, fi, score in res: - ... print fi.text, round(score, 2) - fi02 0.58 + >>> for r in res: + ... print r['feedback'].text, round(r['score'], 2), r['rank'] + fi02 0.58 1 >>> res = resp02.getGroupedResult() - >>> for qugroup, fi, score in res: - ... print fi.text, round(score, 2) - fi03 0.75 + >>> for r in res: + ... print r['feedback'].text, round(r['score'], 2), r['rank'] + 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...}] diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index 6bc8c0b..1ea9164 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -80,7 +80,8 @@ class IResponse(Interface): questionnaire = Attribute('The questionnaire this response belongs to.') party = Attribute('Some identification of the party that responded ' '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(): """ Calculate the result for this response. diff --git a/knowledge/survey/questionnaire.py b/knowledge/survey/questionnaire.py index b73e9e6..45efa0f 100644 --- a/knowledge/survey/questionnaire.py +++ b/knowledge/survey/questionnaire.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -58,12 +58,8 @@ class Question(object): self.feedbackItems = {} self.text = text self.revertAnswerOptions = False - - def getAnswerRange(self): - return self._answerRange or self.questionnaire.defaultAnswerRange - def setAnswerRange(self, value): - self._answerRange = value - answerRange = property(getAnswerRange, setAnswerRange) + self.questionType = 'value_selection' + self.answerRange = None class FeedbackItem(object): @@ -82,13 +78,18 @@ class Response(object): self.questionnaire = questionnaire self.party = party self.values = {} + self.texts = {} def getResult(self): result = {} for question, value in self.values.items(): + if question.questionType != 'value_selection': + continue for fi, rf in question.feedbackItems.items(): 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 return sorted(result.items(), key=lambda x: -x[1]) @@ -97,15 +98,46 @@ class Response(object): for qugroup in self.questionnaire.questionGroups: score = scoreMax = 0.0 for qu in qugroup.questions: - value = self.values.get(qu) - if value is None: + if qu.questionType not in (None, 'value_selection'): 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: - value = qu.answerRange - value - 1 + value = answerRange - value - 1 score += value - scoreMax += qu.answerRange - 1 + scoreMax += answerRange - 1 if scoreMax > 0.0: relScore = score / scoreMax 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 + + 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) + 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] diff --git a/organize/interfaces.py b/organize/interfaces.py index b880f77..3575a6f 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -86,6 +86,7 @@ class IPerson(Interface): description=_(u'The date of birth - should be a ' 'datetime.date object.'), required=False,) + birthDate.hideTime = True age = schema.Int( title=_(u'Age'), diff --git a/organize/locales/de/LC_MESSAGES/cybertools.organize.mo b/organize/locales/de/LC_MESSAGES/cybertools.organize.mo index 2826550..002cbd1 100644 Binary files a/organize/locales/de/LC_MESSAGES/cybertools.organize.mo and b/organize/locales/de/LC_MESSAGES/cybertools.organize.mo differ diff --git a/organize/locales/de/LC_MESSAGES/cybertools.organize.po b/organize/locales/de/LC_MESSAGES/cybertools.organize.po index 1db0f8f..ed16268 100644 --- a/organize/locales/de/LC_MESSAGES/cybertools.organize.po +++ b/organize/locales/de/LC_MESSAGES/cybertools.organize.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: $Id$\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 \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -66,17 +66,31 @@ msgid "End date" msgstr "Ende" msgid "Street, number" -msgstr "Straße, Nr." +msgstr "Straße, Hausnummer" -msgid "City" -msgstr "Stadt" +msgid "Street and number" +msgstr "Straße und Hausnummer" 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" -msgstr "Zusätzliche Zeilen" +msgstr "Zusätzliche Adresszeilen" msgid "Additional address lines" -msgstr "Zusätzliche Zeilen in Anschrift" - +msgstr "Zusätzliche Adresszeilen" diff --git a/organize/work.py b/organize/work.py index 39b8fb7..63f8979 100644 --- a/organize/work.py +++ b/organize/work.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -41,8 +41,8 @@ _not_found = object() def workItemStates(): return StatesDefinition('workItemStates', State('new', 'new', - ('plan', 'accept', 'start', 'work', 'finish', 'delegate', - 'cancel', 'reopen'), + ('plan', 'accept', 'start', 'work', 'finish', 'delegate', + 'cancel', 'reopen'), # 'move', # ? color='red'), State('planned', 'planned', ('plan', 'accept', 'start', 'work', 'finish', 'delegate', @@ -59,7 +59,7 @@ def workItemStates(): 'move', 'cancel', 'modify'), color='lightgreen'), State('finished', 'finished', ('plan', 'accept', 'start', 'work', 'finish', - 'move', 'modify', 'close'), + 'move', 'modify', 'close', 'cancel'), color='green'), State('cancelled', 'cancelled', ('plan', 'accept', 'start', 'work', 'move', 'modify', 'close'), @@ -79,8 +79,12 @@ def workItemStates(): State('replaced', 'replaced', (), color='grey'), State('planned_x', 'planned', (), color='red'), State('accepted_x', 'accepted', (), color='yellow'), - State('done_x', 'done', (), color='lightgreen'), - State('finished_x', 'finished', (), color='green'), + State('done_x', 'done', + ('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: Transition('plan', 'plan', 'planned'), Transition('accept', 'accept', 'accepted'), @@ -96,7 +100,8 @@ def workItemStates(): initialState='new') -fieldNames = ['title', 'description', 'deadline', 'start', 'end', +fieldNames = ['title', 'description', 'deadline', 'priority', 'activity', + 'start', 'end', 'duration', 'effort', 'comment', 'party'] # for use in editingRules @@ -138,7 +143,8 @@ class WorkItemType(object): self.title = title self.description = description 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.delegatedState = delegatedState self.prefillDate = prefillDate @@ -156,9 +162,10 @@ workItemTypes = Jeep(( fields =('deadline',), indicator='work_deadline'), WorkItemType('checkup', u'Check-up', - actions=('plan', 'accept', 'finish', 'cancel', + actions=('plan', 'accept', 'start', 'finish', 'cancel', 'modify', 'delegate', 'close', 'reopen'), - fields =('deadline', 'start-end',), + #fields =('deadline', 'start-end',), + fields =('deadline', 'daterange',), indicator='work_checkup', delegatedState='closed', prefillDate=False), )) @@ -177,7 +184,7 @@ class WorkItem(Stateful, Track): statesDefinition = 'organize.workItemStates' initAttributes = set(['workItemType', 'party', 'title', 'description', - 'deadline', 'start', 'end', + 'deadline', 'priority', 'activity', 'start', 'end', 'duration', 'effort']) def __init__(self, taskId, runId, userName, data): @@ -225,13 +232,16 @@ class WorkItem(Stateful, Track): return list(getParent(self).query(runId=self.runId)) def doAction(self, action, userName, **kw): - if self != self.currentWorkItems[-1]: - raise ValueError("Actions are only allowed on the last item of a run.") + #if self != self.currentWorkItems[-1]: + # raise ValueError("Actions are only allowed on the last item of a run.") if action not in [t.name for t in self.getAvailableTransitions()]: raise ValueError("Action '%s' not allowed in state '%s'" % (action, self.state)) if action in self.specialActions: return self.specialActions[action](self, userName, **kw) + return self.doStandardAction(action, userName, **kw) + + def doStandardAction(self, action, userName, **kw): if self.state == 'new': self.setData(**kw) self.doTransition(action) @@ -244,6 +254,9 @@ class WorkItem(Stateful, Track): elif self.state in ('planned', 'accepted', 'done'): self.state = self.state + '_x' self.reindex('state') + elif self.state in ('finished',) and action == 'cancel': + self.state = self.state + '_x' + self.reindex('state') new.doTransition(action) new.reindex() return new @@ -279,25 +292,57 @@ class WorkItem(Stateful, Track): delegated.data['target'] = new.name 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 = getParent(self).query( + party=userName, state='running') + for wi in running: + if wi.workItemType in '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): xkw = dict(kw) for k in ('deadline', 'start', 'end'): xkw.pop(k, None) # do not change on source item - moved = self.createNew('move', userName, **xkw) - moved.userName = self.userName - moved.state = 'moved' - moved.reindex() + if self.state == 'new': # should this be possible? + moved = self + self.setData(kw) + if self.state in ('done', 'finished', 'running'): + moved = self # is this OK? or better new state ..._y? + else: + moved = self.createNew('move', userName, **xkw) + moved.userName = self.userName task = kw.pop('task', None) new = moved.createNew(None, userName, taskId=task, runId=0, **kw) new.userName = self.userName new.data['source'] = moved.name - new.state = self.state + if self.state == 'new': + new.state = 'planned' + else: + new.state = self.state new.reindex() moved.data['target'] = new.name - if self.state in ('planned', 'accepted', 'delegated', 'moved', - 'done', 'finished'): + moved.state = 'moved' + moved.reindex() + if self.state in ('planned', 'accepted', 'delegated', 'moved'): + #'done', 'finished'): self.state = self.state + '_x' self.reindex('state') + #elif self.state in ('done', 'finished'): + # self.state = self.state + '_y' + # self.reindex('state') return new def close(self, userName, **kw): @@ -315,7 +360,8 @@ class WorkItem(Stateful, Track): item.reindex('state') return new - specialActions = dict(modify=modify, delegate=delegate, move=move, + specialActions = dict(modify=modify, delegate=delegate, + start=doStart, move=move, close=close) def setData(self, ignoreParty=False, **kw): @@ -328,7 +374,7 @@ class WorkItem(Stateful, Track): self.reindex('userName') start = kw.get('start') or kw.get('deadline') # TODO: check OK? if start is not None: - self.timeStamp = start + self.timeStamp = start # TODO: better use end self.reindex('timeStamp') data = self.data for k, v in kw.items(): diff --git a/organize/work.txt b/organize/work.txt index ab1cc45..c276fc1 100644 --- a/organize/work.txt +++ b/organize/work.txt @@ -100,7 +100,7 @@ but may also be specified explicitly. >>> wi03 = wi02.doAction('start', 'jim', start=1229958000) >>> wi03 + {'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}> Stopping and finishing work --------------------------- @@ -113,7 +113,7 @@ as "running" will be replaced by a new one. >>> wi03 + {'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}> >>> wi04 diff --git a/stateful/base.py b/stateful/base.py index 67cee8d..dd9b4f1 100644 --- a/stateful/base.py +++ b/stateful/base.py @@ -50,7 +50,7 @@ class Stateful(object): def getStateObject(self): states = self.getStatesDefinition().states - if self.state not in states: + if self.getState() not in states: self.state = self.getStatesDefinition().initialState return states[self.state] diff --git a/tracking/btree.py b/tracking/btree.py index f817535..07191d7 100644 --- a/tracking/btree.py +++ b/tracking/btree.py @@ -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 # 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. - -$Id$ """ import time @@ -142,11 +140,12 @@ class TrackingStorage(BTreeContainer): def setupIndexes(self): changed = False - for idx in self.indexAttributes: + for idx in self.trackFactory.index_attributes: if idx not in self.indexes: self.indexes[idx] = FieldIndex() changed = True if changed: + self.indexAttributes = self.trackFactory.index_attributes self.reindexTracks() def idFromNum(self, num): @@ -237,6 +236,7 @@ class TrackingStorage(BTreeContainer): self.unindexTrack(trackNum, track) def indexTrack(self, trackNum, track, idx=None): + #self.setupIndexes() if not trackNum: trackNum = int(track.__name__) data = track.indexdata diff --git a/tracking/interfaces.py b/tracking/interfaces.py index ab08571..8e37e14 100644 --- a/tracking/interfaces.py +++ b/tracking/interfaces.py @@ -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 # it under the terms of the GNU General Public License as published by @@ -18,8 +18,6 @@ """ Interface definitions for tracking of user interactions. - -$Id$ """ from zope.interface import Interface, Attribute @@ -50,6 +48,12 @@ class ITrack(Interface): # """ 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): """ A utility for storing user tracks. diff --git a/xedit/browser.py b/xedit/browser.py index 5ea67d4..00acae7 100644 --- a/xedit/browser.py +++ b/xedit/browser.py @@ -58,6 +58,7 @@ class ExternalEditorView(object): r.append('cookie:' + cookie) r.append('') r.append(fromUnicode(data)) + r = [str(item) for item in r] result = '\n'.join(r) self.setHeaders(len(result)) return result