work in progress: loops site synchronization: export (and subsequent import) of changes

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3730 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2010-02-15 11:32:11 +00:00
parent eddf4287ec
commit a761455e35
6 changed files with 158 additions and 42 deletions

26
external/base.py vendored
View file

@ -39,7 +39,7 @@ from cybertools.typology.interfaces import IType
from loops.common import adapted from loops.common import adapted
from loops.external.interfaces import ILoader, IExtractor, ISubExtractor from loops.external.interfaces import ILoader, IExtractor, ISubExtractor
from loops.external.element import elementTypes from loops.external.element import elementTypes
from loops.interfaces import IConceptSchema, IResourceSchema, IResource from loops.interfaces import IConceptSchema, IResourceSchema, IResource, IConcept
from loops.layout.base import LayoutNode from loops.layout.base import LayoutNode
from loops.resource import Document, MediaAsset from loops.resource import Document, MediaAsset
from loops.setup import SetupManager from loops.setup import SetupManager
@ -113,9 +113,10 @@ class Extractor(Base):
self.count += 1 self.count += 1
yield element yield element
def extractConcepts(self): def extractConcepts(self, types=None):
for name, obj in self.concepts.items(): for name, obj in self.concepts.items():
if obj.conceptType != self.typeConcept: if obj.conceptType != self.typeConcept:
if self.checkTypes(obj, types):
self.count += 1 self.count += 1
yield self.getConceptElement(name, obj) yield self.getConceptElement(name, obj)
@ -161,7 +162,8 @@ class Extractor(Base):
yield elem yield elem
def extractChanges(self, changedSince, parents=None, predicates=None, def extractChanges(self, changedSince, parents=None, predicates=None,
includeSubconcepts=False, includeResources=False,): types=None,):
#includeSubconcepts=False, includeResources=False,):
changes = self.getChangeRecords() changes = self.getChangeRecords()
if not changes: if not changes:
return return
@ -173,6 +175,8 @@ class Extractor(Base):
obj = util.getObjectForUid(tr.taskId) obj = util.getObjectForUid(tr.taskId)
action = tr.data.get('action') action = tr.data.get('action')
if action in ('add', 'modify'): if action in ('add', 'modify'):
if not self.checkTypes(obj, types):
continue
if not self.checkParents(obj, parents, predicates): if not self.checkParents(obj, parents, predicates):
continue continue
if obj not in objects: if obj not in objects:
@ -188,6 +192,9 @@ class Extractor(Base):
if (not self.checkParents(obj, parents, predicates) and if (not self.checkParents(obj, parents, predicates) and
not self.checkParents(child, parents, predicates)): not self.checkParents(child, parents, predicates)):
continue continue
if (not self.checkTypes(obj, types) and
not self.checkTypes(child, types)):
continue
if action == 'assign': if action == 'assign':
element = self.getAssignmentElement(obj, child, pred) element = self.getAssignmentElement(obj, child, pred)
else: else:
@ -196,7 +203,7 @@ class Extractor(Base):
yield element yield element
# TODO: include children and resources if corresponding flags are set. # TODO: include children and resources if corresponding flags are set.
def extractForParents(self, parents, predicates=None, def extractForParents(self, parents, predicates=None, types=None,
includeSubconcepts=False, includeResources=False,): includeSubconcepts=False, includeResources=False,):
concepts = set(parents) concepts = set(parents)
for p in parents: for p in parents:
@ -204,6 +211,7 @@ class Extractor(Base):
conceptList = sorted(concepts, key=lambda x: conceptList = sorted(concepts, key=lambda x:
(x.conceptType != self.typeConcept, getName(x))) (x.conceptType != self.typeConcept, getName(x)))
for c in conceptList: for c in conceptList:
if self.checkTypes(c, types):
self.count += 1 self.count += 1
yield self.getConceptElement(getName(c), c) yield self.getConceptElement(getName(c), c)
for c in conceptList: for c in conceptList:
@ -216,6 +224,7 @@ class Extractor(Base):
for c in conceptList: for c in conceptList:
for obj in c.getResources(predicates): for obj in c.getResources(predicates):
if obj not in resources: if obj not in resources:
if self.checkTypes(obj, types):
resources.add(obj) resources.add(obj)
self.count += 1 self.count += 1
yield self.getResourceElement(getName(obj), obj) yield self.getResourceElement(getName(obj), obj)
@ -243,12 +252,21 @@ class Extractor(Base):
def checkParents(self, obj, parents, predicates): def checkParents(self, obj, parents, predicates):
if not parents: if not parents:
return True return True
if (not IResource.providedBy(obj) and not IConcept.providedBy(obj)):
return False
objParents = obj.getParents(predicates) objParents = obj.getParents(predicates)
for p in parents: for p in parents:
if p in objParents: if p in objParents:
return True return True
return False return False
def checkTypes(self, obj, types):
if not types:
return True
if (not IResource.providedBy(obj) and not IConcept.providedBy(obj)):
return False
return obj.getType() in types
def getConceptOrResourceElement(self, name, obj): def getConceptOrResourceElement(self, name, obj):
if IResource.providedBy(obj): if IResource.providedBy(obj):
return self.getResourceElement(name, obj) return self.getResourceElement(name, obj)

39
external/browser.py vendored
View file

@ -33,6 +33,7 @@ from zope.cachedescriptors.property import Lazy
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName, getPath from zope.traversing.api import getName, getPath
from cybertools.util.date import str2timeStamp
from loops.external.base import Loader, Extractor from loops.external.base import Loader, Extractor
from loops.external.interfaces import IReader, IWriter from loops.external.interfaces import IReader, IWriter
from loops import util from loops import util
@ -73,7 +74,7 @@ class ExportImport(object):
def export(self): def export(self):
form = self.request.form form = self.request.form
parents = predicates = None parents = predicates = types = None
parentIds = form.get('parents') parentIds = form.get('parents')
if parentIds: if parentIds:
parentIds = [id for id in parentIds.splitlines() if id] parentIds = [id for id in parentIds.splitlines() if id]
@ -82,6 +83,9 @@ class ExportImport(object):
predicateIds = form.get('predicates') predicateIds = form.get('predicates')
if predicateIds: if predicateIds:
predicates = ([self.conceptManager[id] for id in predicateIds]) predicates = ([self.conceptManager[id] for id in predicateIds])
typeIds = form.get('types')
if typeIds:
types = ([self.conceptManager[id] for id in typeIds])
changed = form.get('changed') changed = form.get('changed')
includeSubconcepts = form.get('include_subconcepts') includeSubconcepts = form.get('include_subconcepts')
includeResources = form.get('include_resources') includeResources = form.get('include_resources')
@ -89,13 +93,13 @@ class ExportImport(object):
if changed: if changed:
changed = self.parseDate(changed) changed = self.parseDate(changed)
if changed: if changed:
elements = extractor.extractChanges(changed, parents, predicates, elements = extractor.extractChanges(changed, parents,
includeSubconcepts, includeResources) predicates, types)
elif parents: elif parents:
elements = extractor.extractForParents(parents, predicates, elements = extractor.extractForParents(parents, predicates, types,
includeSubconcepts, includeResources) includeSubconcepts, includeResources)
else: else:
elements = extractor.extract() elements = extractor.extract(types)
return self.download(elements) return self.download(elements)
def download(self, elements): def download(self, elements):
@ -107,26 +111,25 @@ class ExportImport(object):
self.setDownloadHeader(self.request, text) self.setDownloadHeader(self.request, text)
return text return text
def parseDate(self, s):
try:
t = time.strptime(s, '%Y-%m-%d %H:%M:%S')
except ValueError:
try:
t = time.strptime(s, '%Y-%m-%d %H:%M')
except ValueError:
t = time.strptime(s, '%Y-%m-%d')
return int(time.mktime(t))
@Lazy @Lazy
def conceptManager(self): def conceptManager(self):
return self.context.getConceptManager() return self.context.getConceptManager()
@Lazy
def typePredicate(self):
return self.conceptManager.getTypePredicate()
@Lazy
def types(self):
ttype = self.conceptManager['type']
return [dict(name=getName(p), title=p.title)
for p in ttype.getChildren([self.typePredicate])]
@Lazy @Lazy
def predicates(self): def predicates(self):
ptype = self.conceptManager['predicate'] ptype = self.conceptManager['predicate']
hasType = self.conceptManager['hasType']
return [dict(name=getName(p), title=p.title) return [dict(name=getName(p), title=p.title)
for p in ptype.getChildren([hasType])] for p in ptype.getChildren([self.typePredicate])]
def upload(self): def upload(self):
data = self.request.get('field.data', None) data = self.request.get('field.data', None)
@ -148,3 +151,5 @@ class ExportImport(object):
response.setHeader('Content-Type', 'text/plain') response.setHeader('Content-Type', 'text/plain')
response.setHeader('Content-Length', len(text)) response.setHeader('Content-Length', len(text))
def parseDate(self, s):
return str2timeStamp(s)

View file

@ -59,9 +59,17 @@
<label for="include_resources">Include Assigned Resources</label> <label for="include_resources">Include Assigned Resources</label>
</td> </td>
</tr> </tr>
<tr>
<td colspan="4">
<label for="types">Restrict Type</label><br />
<select multiple name="types:list" id="types"
size="4">
<option tal:repeat="type view/types"
tal:attributes="value type/name"
tal:content="type/title">type</option></select>
</tr>
</table> </table>
</div> </div>
<div>&nbsp;</div>
<div class="row"> <div class="row">
<div class="controls"> <div class="controls">
<input type="submit" name="loops.export" value="Export" /> <input type="submit" name="loops.export" value="Export" />

View file

@ -22,7 +22,6 @@ view class(es) for import/export.
$Id$ $Id$
""" """
from cStringIO import StringIO
import os import os
import time import time
from zope import component from zope import component
@ -31,14 +30,18 @@ from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.traversing.api import getName, getPath from zope.traversing.api import getName, getPath
from loops.organize.tracking.report import RecentChanges from cybertools.browser.form import FormController
from cybertools.util.date import str2timeStamp, formatTimeStamp
from loops.browser.concept import ConceptView
from loops.external.base import Extractor
from loops.external.interfaces import IWriter
from loops import util from loops import util
control_macros = ViewPageTemplateFile('control.pt') control_macros = ViewPageTemplateFile('control.pt')
class SyncChanges(RecentChanges): class SyncChanges(ConceptView):
""" View for controlling the transfer of changes from a loops site """ View for controlling the transfer of changes from a loops site
to another one. to another one.
""" """
@ -47,3 +50,54 @@ class SyncChanges(RecentChanges):
def macro(self): def macro(self):
return control_macros.macros['main'] return control_macros.macros['main']
@Lazy
def changed(self):
return (self.request.get('changed_since') or
formatTimeStamp(self.lastSyncTimeStamp))
@Lazy
def lastSyncTime(self):
if self.lastSyncTimeStamp is None:
return u'-'
return formatTimeStamp(self.lastSyncTimeStamp)
@Lazy
def lastSyncTimeStamp(self):
return None
class ChangesSave(FormController):
@Lazy
def baseDirectory(self):
return util.getVarDirectory(self.request)
@Lazy
def sitePath(self):
return getPath(self.view.virtualTargetObject)[1:].replace('/', '_')
@Lazy
def exportDirectory(self):
return os.path.join(self.baseDirectory, 'export', self.sitePath)
@Lazy
def targetView(self):
return self.view.virtualTarget
def update(self):
typeIds = self.targetView.options('types')
types = [self.view.conceptManager[t] for t in typeIds]
since = self.request.get('changed_since')
changed = since and str2timeStamp(since) or self.targetView.lastSyncTimeStamp
extractor = Extractor(self.view.loopsRoot, self.exportDirectory)
elements = extractor.extractChanges(changed, types)
writer = component.getUtility(IWriter)
f = open(os.path.join(self.exportDirectory, '_changes.dmp'), 'w')
writer.write(elements, f)
f.close()
return True
class ChangesSync(ChangesSave):
pass

View file

@ -13,4 +13,16 @@
class="loops.system.sync.browser.SyncChanges" class="loops.system.sync.browser.SyncChanges"
permission="zope.ManageContent" /> permission="zope.ManageContent" />
<zope:adapter name="changes_save"
for="loops.browser.node.NodeView
zope.publisher.interfaces.browser.IBrowserRequest"
factory="loops.system.sync.browser.ChangesSave"
permission="zope.ManageContent" />
<zope:adapter name="changes_sync"
for="loops.browser.node.NodeView
zope.publisher.interfaces.browser.IBrowserRequest"
factory="loops.system.sync.browser.ChangesSync"
permission="zope.ManageContent" />
</configure> </configure>

View file

@ -1,10 +1,29 @@
<!-- $Id$ --> <!-- $Id$ -->
<metal:main define-macro="main" <metal:main define-macro="main">
tal:define="info item/getData"> <form method="post"
tal:define="action request/form.action|string:changes_save">
<metal:title use-macro="item/conceptMacros/concepttitle" /> <metal:title use-macro="item/conceptMacros/concepttitle" />
<br /> <br />
<metal:listing use-macro="info/macro" /> <label for="changed_since" i18n:translate="">Select changes since:</label>
<input type="submit" value="Synchronize Changes" /> <input type="text" name="changed_since" id="changed_since" size="14"
tal:attributes="value item/changed" />&nbsp;
(<span i18n:translate="">Last synchronization:</span>
<span tal:content="item/lastSyncTime" />)
<br />&nbsp;
<div>
<label i18n:translate="">Action:</label>
<input type="radio" name="form.action" value="changes_save"
id="action_save"
tal:attributes="checked python:action == 'changes_save'" />
<label for="action_save" i18n:translate="">Save changes only</label>
<input type="radio" name="form.action" value="changes_sync"
id="action_sync"
tal:attributes="checked python:action == 'changes_sync'" />
<label for="action_sync" i18n:translate="">Synchronize changes</label>
</div>
<br />
<input type="submit" name="submit" value="Submit" i18n:translate="" />
</form>
</metal:main> </metal:main>