loops/organize/tracking
2016-12-26 10:37:00 +01:00
..
__init__.py move stateful and process to organize; work in progress: organize.tracking 2008-04-09 10:03:52 +00:00
access.py - keep access trail (history) in session 2011-03-13 12:53:42 +00:00
base.py revert previous change (was other error in cyberapps.knowledge) 2016-04-07 09:36:22 +02:00
browser.py merge branch master 2015-06-12 08:35:07 +02:00
change.py always track state changes; allow control of change tracking via 'force' parameter 2014-11-03 10:32:51 +01:00
configure.zcml make sure management views of records/tracks can only accessed by managers 2015-06-01 08:39:46 +02:00
edit_track.pt revert request/URL stuff for management views 2016-06-09 12:20:44 +02:00
README.txt work item list: show description of task as link title; fix docttests: changed handling of field defaults 2015-10-10 10:38:31 +02:00
report.pt fix description field in recent changes report 2016-11-18 12:48:13 +01:00
report.py don't try to get a view for an object the user does not have access to 2015-12-01 20:47:26 +01:00
setup.py work in progress: access tracking - loading access log basically OK 2008-10-31 22:48:38 +00:00
tests.py remove deprecated import from zope.testing 2016-12-26 10:37:00 +01:00

===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================

  ($Id$)

Let's do some basic setup

  >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
  >>> site = placefulSetUp(True)
  >>> from zope import component, interface

and set up a simple loops site with a concept manager and some concepts
(with all the type machinery, what in real life is done via standard
ZCML setup):

  >>> from loops.organize.setup import SetupManager
  >>> component.provideAdapter(SetupManager, name='organize')
  >>> from loops.organize.tracking.setup import SetupManager
  >>> component.provideAdapter(SetupManager, name='organize.tracking')

  >>> from loops.tests.setup import TestSite
  >>> t = TestSite(site)
  >>> concepts, resources, views = t.setup()


Tracking Changes
================

  >>> loopsRoot = concepts.getLoopsRoot()
  >>> records = loopsRoot.getRecordManager()
  >>> changes = records['changes']

User management setup
---------------------

In order to be able to login and store personal data
we have to prepare our environment. We need some basic adapter registrations,
and a pluggable authentication utility with a principal folder.

  >>> from loops.organize.tests import setupObjectsForTesting
  >>> setupData = setupObjectsForTesting(site, concepts)
  >>> johnC = setupData.johnC

Recording changes to objects
----------------------------

  >>> from loops.organize.tracking.change import recordModification
  >>> component.provideHandler(recordModification)

  >>> loopsRoot.options = ['organize.tracking.changes']

  >>> tTask = concepts['task']
  >>> from loops.concept import Concept
  >>> from loops.setup import addAndConfigureObject
  >>> t01 = addAndConfigureObject(concepts, Concept, 't01', conceptType=tTask,
  ...                   title='Develop change tracking')

  >>> len(changes)
  1

  >>> from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
  >>> from zope.event import notify
  >>> resources['d001.txt'].title = 'Change Doc 001'
  >>> notify(ObjectModifiedEvent(resources['d001.txt']))
  >>> len(changes)
  2

Recording assignment changes
----------------------------

  >>> from loops.organize.tracking.change import recordAssignment, recordDeassignment
  >>> component.provideHandler(recordAssignment)
  >>> component.provideHandler(recordDeassignment)

  >>> t01.assignChild(johnC)
  >>> len(changes)
  3


Tracking Object Access
======================

Access records are not directly stored in the ZODB (in order to avoid
conflict errors) but are first stored to a log file.

Even this logging is a two-step process: the data to be logged are first collected
in the request; all collected data are then written triggered by the
EndRequestEvent.

  >>> from loops.organize.tracking.access import logfile_option, record, logAccess
  >>> #from loops.organize.tracking.access import AccessRecordManagerView
  >>> from loops.organize.tracking.access import AccessRecordManager
  >>> from loops.organize.tracking.tests import testDir
  >>> from loops.browser.node import NodeView
  >>> from loops.browser.resource import ResourceView
  >>> from loops import util
  >>> from zope.publisher.browser import TestRequest
  >>> from cybertools.browser.view import BodyRenderedEvent

  >>> loopsRoot.options = [logfile_option + ':test.log']

  >>> request = TestRequest()
  >>> home = views['home']
  >>> record(request, principal='users.john', view='render',
  ...                 node=util.getUidForObject(home),
  ...                 target=util.getUidForObject(resources['d001.txt']),
  ...       )
  >>> logAccess(BodyRenderedEvent(home, request), testDir)

  >>> record(request, principal='users.john', view='render',
  ...                 node=util.getUidForObject(home),
  ...                 target=util.getUidForObject(resources['d002.txt']),
  ...       )
  >>> logAccess(BodyRenderedEvent(home, request), testDir)

The access log can then be read in via an AccessRecordManager object, i.e. a view
that may be called via ``wget`` using a crontab entry or some other kind
of job control.

  >>> access = records['access']
  >>> len(access)
  0

  >>> #rm = AccessRecordManagerView(loopsRoot, TestRequest())
  >>> rm = AccessRecordManager(loopsRoot)
  >>> rm.baseDir = testDir
  >>> rm.loadRecordsFromLog()
  'AccessRecordManager: 2 records loaded.'
  >>> len(access)
  2


Tracking Reports
================

Overview (cumulative) statistics
--------------------------------

  >>> from loops.expert.concept import IQueryConcept
  >>> tQuery = concepts['query'] = addAndConfigureObject(concepts, Concept,
  ...                   'query', conceptType=concepts.getTypeConcept(),
  ...                   typeInterface=IQueryConcept)
  >>> statQuery = addAndConfigureObject(concepts, Concept, 'stats',
  ...                   conceptType=tQuery)

  >>> from loops.organize.tracking.report import TrackingStats
  >>> view = TrackingStats(statQuery, TestRequest())
  >>> result = view.getData()
  >>> result['macro'][4][1][u'define-macro']
  u'overview'
  >>> result['data']
  [{'access': 2, 'new': 0, 'changed': 1, 'period': '...', 'count': 3}]


Changes for a certain period (month)
------------------------------------

  >>> input = dict(period='2009-01', select='access')
  >>> view = TrackingStats(statQuery, TestRequest(form=input))
  >>> result = view.getData()
  >>> result['data']
  [...]

Recent changes
--------------

  >>> from loops.organize.tracking.report import RecentChanges
  >>> view = RecentChanges(statQuery, 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].objectData
  {'version': '', 'description': u'', 'title': 'Change Doc 001', 'url': '', 
   'object': <loops.resource.Resource object at ...>, 'type': u'Text', 
   'canAccess': True}

  >>> data[0].user
  {'url': '', 'object': <loops.concept.Concept ...>, 'title': u'john'}

  >>> data[0].action
  'modify'


Fin de partie
=============

  >>> import os
  >>> for fn in os.listdir(testDir):
  ...     if '.log' in fn:
  ...         os.unlink(os.path.join(testDir, fn))
  >>> placefulTearDown()