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
---
New features
- basic job management: a job executor view calls job managers specified
by loops root option ``organize.job.managers``
- allow ``__getitem__`` on Loops and ViewManager, this is a prerequisite for
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 via wget without login credentials
views/home) on protected sites; this also allows calling of (public)
job processiong views via wget without login credentials
- 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
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
def params(self):
result = {}

View file

@ -76,6 +76,8 @@ class NodeView(BaseView):
def __init__(self, context, request):
super(NodeView, self).__init__(context, request)
viewAnnotations = request.annotations.setdefault('loops.view', {})
viewAnnotations['nodeView'] = self
viewConfig = getViewConfiguration(context, request)
self.setSkin(viewConfig.get('skinName'))
@ -738,6 +740,8 @@ class NodeTraverser(ItemTraverser):
component.adapts(INode)
def publishTraverse(self, request, name):
viewAnnotations = request.annotations.setdefault('loops.view', {})
viewAnnotations['node'] = self.context
if self.context.nodeType == 'menu':
setViewConfiguration(self.context, request)
if name == '.loops':
@ -761,8 +765,6 @@ class NodeTraverser(ItemTraverser):
target = self.context.target
if target is not None:
# remember self.context in request
viewAnnotations = request.annotations.setdefault('loops.view', {})
viewAnnotations['node'] = self.context
if request.method == 'PUT':
# we have to use the target object directly
return target

View file

@ -133,6 +133,9 @@ of job control.
Tracking Reports
================
Overview (cumulative) statistics
--------------------------------
>>> from loops.organize.tracking.report import TrackingStats
>>> view = TrackingStats(home, TestRequest())
@ -142,6 +145,28 @@ Tracking Reports
>>> result['data']
[{'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
=============

View file

@ -42,6 +42,12 @@
class="loops.organize.tracking.report.TrackingStats"
permission="zope.View" />
<browser:page
name="recent_changes.html"
for="loops.interfaces.IConcept"
class="loops.organize.tracking.report.RecentChanges"
permission="zope.View" />
<zope:adapter
for="loops.organize.tracking.change.IChangeRecord
zope.publisher.interfaces.browser.IBrowserRequest"

View file

@ -1,6 +1,7 @@
<metal:report define-macro="report"
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:report>
@ -29,3 +30,27 @@
</table>
</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.api import getName
from cybertools.util import format
from loops.browser.common import BaseView
from loops.interfaces import IResource
from loops import util
from loops.util import _
report_macros = ViewPageTemplateFile('report.pt')
@ -39,6 +41,7 @@ report_macros = ViewPageTemplateFile('report.pt')
class TrackingStats(BaseView):
template = report_macros
title = _(u'Statistics Report')
@Lazy
def macro(self):
@ -112,11 +115,75 @@ class TrackingStats(BaseView):
the period given.
"""
def getObjectDetails(uid):
""" Listing of last n accesses and changes of the object specified by
the uid given.
def getObjectDetails(uid, period=None):
""" Listing of (last n?) accesses and changes of the object specified by
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):
return d.isoformat()[:7]

View file

@ -85,6 +85,8 @@ def toUnicode(value, encoding='UTF-8'):
def getObjectForUid(uid, intIds=None):
if uid == '*': # wild card
return '*'
if isinstance(uid, basestring) and not uid.isdigit(): # not a valid uid
return None
if intIds is None:
intIds = component.getUtility(IIntIds)
return intIds.getObject(int(uid))