provide 'recent changes' view
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3031 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
40cee5d212
commit
bc538f2fd7
8 changed files with 155 additions and 8 deletions
11
CHANGES.txt
11
CHANGES.txt
|
@ -6,10 +6,17 @@ $Id$
|
||||||
0.9
|
0.9
|
||||||
---
|
---
|
||||||
|
|
||||||
|
New features
|
||||||
|
|
||||||
- basic job management: a job executor view calls job managers specified
|
- basic job management: a job executor view calls job managers specified
|
||||||
by loops root option ``organize.job.managers``
|
by loops root option ``organize.job.managers``
|
||||||
- allow ``__getitem__`` on Loops and ViewManager, this is a prerequisite for
|
- allow ``__getitem__`` on Loops and ViewManager, this is a prerequisite for
|
||||||
using virtual hosts over more than one path element (e.g. leading to
|
using virtual hosts over more than one path element (e.g. leading to
|
||||||
views/home) on protected sites; this also allows calling of job processiong
|
views/home) on protected sites; this also allows calling of (public)
|
||||||
views via wget without login credentials
|
job processiong views via wget without login credentials
|
||||||
- add definition of loops package version (see loops/version.py)
|
- add definition of loops package version (see loops/version.py)
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
|
||||||
|
- use select version on file, image and media asset links
|
||||||
|
(via ``?version=this`` in the URL)
|
||||||
|
|
|
@ -160,6 +160,19 @@ class BaseView(GenericView, I18NView):
|
||||||
# allow for having a separate object the view acts upon
|
# allow for having a separate object the view acts upon
|
||||||
return self.context
|
return self.context
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def viewAnnotations(self):
|
||||||
|
return self.request.annotations.get('loops.view', {})
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def node(self):
|
||||||
|
return self.viewAnnotations.get('node')
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def nodeView(self):
|
||||||
|
ann = self.request.annotations.get('loops.view', {})
|
||||||
|
return self.viewAnnotations.get('nodeView')
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def params(self):
|
def params(self):
|
||||||
result = {}
|
result = {}
|
||||||
|
|
|
@ -76,6 +76,8 @@ class NodeView(BaseView):
|
||||||
|
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
super(NodeView, self).__init__(context, request)
|
super(NodeView, self).__init__(context, request)
|
||||||
|
viewAnnotations = request.annotations.setdefault('loops.view', {})
|
||||||
|
viewAnnotations['nodeView'] = self
|
||||||
viewConfig = getViewConfiguration(context, request)
|
viewConfig = getViewConfiguration(context, request)
|
||||||
self.setSkin(viewConfig.get('skinName'))
|
self.setSkin(viewConfig.get('skinName'))
|
||||||
|
|
||||||
|
@ -738,6 +740,8 @@ class NodeTraverser(ItemTraverser):
|
||||||
component.adapts(INode)
|
component.adapts(INode)
|
||||||
|
|
||||||
def publishTraverse(self, request, name):
|
def publishTraverse(self, request, name):
|
||||||
|
viewAnnotations = request.annotations.setdefault('loops.view', {})
|
||||||
|
viewAnnotations['node'] = self.context
|
||||||
if self.context.nodeType == 'menu':
|
if self.context.nodeType == 'menu':
|
||||||
setViewConfiguration(self.context, request)
|
setViewConfiguration(self.context, request)
|
||||||
if name == '.loops':
|
if name == '.loops':
|
||||||
|
@ -761,8 +765,6 @@ class NodeTraverser(ItemTraverser):
|
||||||
target = self.context.target
|
target = self.context.target
|
||||||
if target is not None:
|
if target is not None:
|
||||||
# remember self.context in request
|
# remember self.context in request
|
||||||
viewAnnotations = request.annotations.setdefault('loops.view', {})
|
|
||||||
viewAnnotations['node'] = self.context
|
|
||||||
if request.method == 'PUT':
|
if request.method == 'PUT':
|
||||||
# we have to use the target object directly
|
# we have to use the target object directly
|
||||||
return target
|
return target
|
||||||
|
|
|
@ -133,6 +133,9 @@ of job control.
|
||||||
Tracking Reports
|
Tracking Reports
|
||||||
================
|
================
|
||||||
|
|
||||||
|
Overview (cumulative) statistics
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
>>> from loops.organize.tracking.report import TrackingStats
|
>>> from loops.organize.tracking.report import TrackingStats
|
||||||
|
|
||||||
>>> view = TrackingStats(home, TestRequest())
|
>>> view = TrackingStats(home, TestRequest())
|
||||||
|
@ -142,6 +145,28 @@ Tracking Reports
|
||||||
>>> result['data']
|
>>> result['data']
|
||||||
[{'access': 2, 'new': 0, 'changed': 1, 'period': '...', 'count': 3}]
|
[{'access': 2, 'new': 0, 'changed': 1, 'period': '...', 'count': 3}]
|
||||||
|
|
||||||
|
Recent changes
|
||||||
|
--------------
|
||||||
|
|
||||||
|
>>> from loops.organize.tracking.report import RecentChanges
|
||||||
|
>>> view = RecentChanges(home, TestRequest())
|
||||||
|
>>> result = view.getData()
|
||||||
|
>>> result['macro'][4][1][u'define-macro']
|
||||||
|
u'recent_changes'
|
||||||
|
|
||||||
|
>>> data = result['data']
|
||||||
|
>>> data
|
||||||
|
[<ChangeRecord ['27', 2, '33', '...']: {'action': 'modify'}>]
|
||||||
|
|
||||||
|
>>> data[0].timeStamp
|
||||||
|
u'... ...:...'
|
||||||
|
>>> data[0].object
|
||||||
|
{'url': '', 'object': <loops.resource.Resource ...>, 'title': 'Change Doc 001'}
|
||||||
|
>>> data[0].user
|
||||||
|
{'url': '', 'object': <loops.concept.Concept ...>, 'title': u'john'}
|
||||||
|
>>> data[0].action
|
||||||
|
'modify'
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -42,6 +42,12 @@
|
||||||
class="loops.organize.tracking.report.TrackingStats"
|
class="loops.organize.tracking.report.TrackingStats"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
name="recent_changes.html"
|
||||||
|
for="loops.interfaces.IConcept"
|
||||||
|
class="loops.organize.tracking.report.RecentChanges"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
for="loops.organize.tracking.change.IChangeRecord
|
for="loops.organize.tracking.change.IChangeRecord
|
||||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<metal:report define-macro="report"
|
<metal:report define-macro="report"
|
||||||
tal:define="info item/getData">
|
tal:define="info item/getData">
|
||||||
<h2 i18n:translate="">Statistics Report</h2>
|
<h2 i18n:translate=""
|
||||||
|
tal:content="item/title|default">Statistics Report</h2><br />
|
||||||
<metal:listing use-macro="info/macro" />
|
<metal:listing use-macro="info/macro" />
|
||||||
</metal:report>
|
</metal:report>
|
||||||
|
|
||||||
|
@ -29,3 +30,27 @@
|
||||||
</table>
|
</table>
|
||||||
</metal:overview>
|
</metal:overview>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:recent define-macro="recent_changes">
|
||||||
|
<table class="listing">
|
||||||
|
<tr>
|
||||||
|
<th i18n:translate="">Title</th>
|
||||||
|
<th i18n:translate="">User</th>
|
||||||
|
<th i18n:translate="">Date/Time</th>
|
||||||
|
<th i18n:translate="">Change</th>
|
||||||
|
</tr>
|
||||||
|
<tr tal:repeat="row info/data"
|
||||||
|
tal:attributes="class python: repeat['row'].odd() and 'even' or 'odd'">
|
||||||
|
<td tal:define="url row/object/url">
|
||||||
|
<a tal:attributes="href url"
|
||||||
|
tal:omit-tag="not:url"
|
||||||
|
tal:content="row/object/title" /></td>
|
||||||
|
<td tal:define="url row/user/url">
|
||||||
|
<a tal:attributes="href url"
|
||||||
|
tal:omit-tag="not:url"
|
||||||
|
tal:content="row/user/title" /></td>
|
||||||
|
<td tal:content="row/timeStamp"></td>
|
||||||
|
<td tal:content="row/action"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</metal:recent>
|
||||||
|
|
|
@ -28,9 +28,11 @@ from zope.cachedescriptors.property import Lazy
|
||||||
from zope.traversing.browser import absoluteURL
|
from zope.traversing.browser import absoluteURL
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName
|
||||||
|
|
||||||
|
from cybertools.util import format
|
||||||
from loops.browser.common import BaseView
|
from loops.browser.common import BaseView
|
||||||
from loops.interfaces import IResource
|
from loops.interfaces import IResource
|
||||||
from loops import util
|
from loops import util
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
report_macros = ViewPageTemplateFile('report.pt')
|
report_macros = ViewPageTemplateFile('report.pt')
|
||||||
|
@ -39,6 +41,7 @@ report_macros = ViewPageTemplateFile('report.pt')
|
||||||
class TrackingStats(BaseView):
|
class TrackingStats(BaseView):
|
||||||
|
|
||||||
template = report_macros
|
template = report_macros
|
||||||
|
title = _(u'Statistics Report')
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
|
@ -112,11 +115,75 @@ class TrackingStats(BaseView):
|
||||||
the period given.
|
the period given.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def getObjectDetails(uid):
|
def getObjectDetails(uid, period=None):
|
||||||
""" Listing of last n accesses and changes of the object specified by
|
""" Listing of (last n?) accesses and changes of the object specified by
|
||||||
the uid given.
|
the uid given, optionally limited to the period (month) given.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class RecentChanges(TrackingStats):
|
||||||
|
|
||||||
|
title = _(u'Recent Changes')
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
length = self.request.form.get('length', 15)
|
||||||
|
new = {}
|
||||||
|
changed = {}
|
||||||
|
result = []
|
||||||
|
for track in self.changeRecords:
|
||||||
|
if track.data['action'] == 'add' and track.taskId not in new:
|
||||||
|
new[track.taskId] = track
|
||||||
|
result.append(track)
|
||||||
|
continue
|
||||||
|
if track.data['action'] == 'modify' and track.taskId not in changed:
|
||||||
|
sameNew = new.get(track.taskId)
|
||||||
|
if sameNew and sameNew.timeStamp > track.timeStamp - 60:
|
||||||
|
# change immediate after creation
|
||||||
|
continue
|
||||||
|
changed[track.taskId] = track
|
||||||
|
result.append(track)
|
||||||
|
continue
|
||||||
|
return dict(data=[TrackDetails(self, tr) for tr in result[:length]],
|
||||||
|
macro=self.macros['recent_changes'])
|
||||||
|
|
||||||
|
|
||||||
|
class TrackDetails(object):
|
||||||
|
|
||||||
|
timeStampFormat = 'short'
|
||||||
|
|
||||||
|
def __init__(self, view, track):
|
||||||
|
self.view = view
|
||||||
|
self.track = track
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def object(self):
|
||||||
|
obj = util.getObjectForUid(self.track.taskId)
|
||||||
|
node = self.view.nodeView
|
||||||
|
url = node is not None and node.getUrlForTarget(obj) or ''
|
||||||
|
return dict(object=obj, title=obj.title, url=url)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def user(self):
|
||||||
|
obj = util.getObjectForUid(self.track.userName)
|
||||||
|
if obj is None:
|
||||||
|
return dict(object=None, title=userName, url='')
|
||||||
|
node = self.view.nodeView
|
||||||
|
url = node is not None and node.getUrlForTarget(obj) or ''
|
||||||
|
return dict(object=obj, title=obj.title, url=url)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def action(self):
|
||||||
|
return self.track.data['action']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def timeStamp(self):
|
||||||
|
value = datetime.fromtimestamp(self.track.timeStamp)
|
||||||
|
return format.formatDate(value, 'dateTime', self.timeStampFormat,
|
||||||
|
self.view.languageInfo.language)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self.track)
|
||||||
|
|
||||||
|
|
||||||
def formatAsMonth(d):
|
def formatAsMonth(d):
|
||||||
return d.isoformat()[:7]
|
return d.isoformat()[:7]
|
||||||
|
|
2
util.py
2
util.py
|
@ -85,6 +85,8 @@ def toUnicode(value, encoding='UTF-8'):
|
||||||
def getObjectForUid(uid, intIds=None):
|
def getObjectForUid(uid, intIds=None):
|
||||||
if uid == '*': # wild card
|
if uid == '*': # wild card
|
||||||
return '*'
|
return '*'
|
||||||
|
if isinstance(uid, basestring) and not uid.isdigit(): # not a valid uid
|
||||||
|
return None
|
||||||
if intIds is None:
|
if intIds is None:
|
||||||
intIds = component.getUtility(IIntIds)
|
intIds = component.getUtility(IIntIds)
|
||||||
return intIds.getObject(int(uid))
|
return intIds.getObject(int(uid))
|
||||||
|
|
Loading…
Add table
Reference in a new issue