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:
helmutm 2008-12-06 10:06:58 +00:00
parent 40cee5d212
commit bc538f2fd7
8 changed files with 155 additions and 8 deletions

View file

@ -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)

View file

@ -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 = {}

View file

@ -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

View file

@ -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
============= =============

View file

@ -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"

View file

@ -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>

View file

@ -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]

View file

@ -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))