source: products/quintagroup.analytics/branches/update_mt/quintagroup/analytics/browser/views.py @ 3351

Last change on this file since 3351 was 3351, checked in by enkidu, 12 years ago

changeset from http://codereview.corp.quintagroup.com/142241/show

  • Property svn:eol-style set to native
File size: 23.0 KB
Line 
1from pprint import pprint
2from operator import itemgetter
3from Acquisition import aq_base
4from zope.component import getUtility, getMultiAdapter, queryMultiAdapter
5
6from OFS.interfaces import IPropertyManager
7from Products.Five.browser import BrowserView
8from Products.CMFCore.utils import getToolByName
9from Products.CMFCore.interfaces import IFolderish
10from Products.Archetypes.interfaces import IBaseFolder
11from plone.portlets.interfaces import IPortletManager
12from plone.portlets.interfaces import IPortletAssignmentMapping
13from plone.portlets.interfaces import ILocalPortletAssignmentManager
14
15from plone.memoize import view
16
17try:
18    from plone.portlets.interfaces import IPortletAssignmentSettings
19except ImportError:
20    "Before plon4 we don't have an annotation storage for settings."
21    IPortletAssignmentSettings = lambda assignment:{}
22
23from GChartWrapper import VerticalBarStack
24
25from quintagroup.analytics.config import COLORS, OTHER_TYPES, NO_WF_BIND
26
27class OwnershipByType(BrowserView):
28    MAX = 10
29    def __init__(self, context, request):
30        self.context = context
31        self.request = request
32        self.cat = getToolByName(self.context, 'portal_catalog')
33        self.users = None
34        self.total = None
35        self.types = None
36        self.data = {}
37
38    def getUsers(self):
39        if self.users is None:
40            index = self.cat._catalog.getIndex('Creator')
41            data = {}
42            for k in index._index.keys():
43                haslen = hasattr(index._index[k], '__len__')
44                if haslen:
45                    data[k] = len(index._index[k])
46                else:
47                    data[k] = 1
48            data = data.items()
49            data.sort(lambda a, b: a[1] - b[1])
50            data.reverse()
51            data = data[:self.MAX]
52            self.users = [i[0] for i in data]
53            self.total = [i[1] for i in data]
54        return self.users
55
56    def getTypes(self, all=False):
57        if self.types is None:
58            index = self.cat._catalog.getIndex('portal_type')
59            data = {}
60            for k in index._index.keys():
61                if not k:
62                    continue
63                haslen = hasattr(index._index[k], '__len__')
64                if haslen:
65                    data[k] = len(index._index[k])
66                else:
67                    data[k] = 1
68            data = data.items()
69            data.sort(lambda a, b: a[1] - b[1])
70            data.reverse()
71            self.types = [i[0] for i in data]
72        return all and self.types or self.types[:self.MAX]
73
74    def getContent(self, type_):
75        if type_ not in self.data:
76            data = self.data[type_] = []
77            for user in self.getUsers():
78                res = self.cat(portal_type=type_, Creator=user)
79                l = len(res)
80                if l == 0:
81                    data.append(0)
82                else:
83                    data.append(l)
84        return self.data[type_]
85
86    def getTotal(self):
87        return self.total
88
89    def getChart(self):
90        data = []
91        types = self.getTypes()
92        for type_ in types:
93            data.append(self.getContent(type_))
94        other = [self.getContent(t) for t in self.getTypes(all=True)[self.MAX:]]
95        if other:
96            data.append([sum(l) for l in zip(*other)])
97        max_value = max(self.getTotal())
98        chart = VerticalBarStack(data, encoding='text')
99        types = other and types+OTHER_TYPES or types
100        chart.title('Content ownership by type').legend(*(types))
101        chart.bar('a', 10, 0).legend_pos("b")
102        chart.color(*COLORS)
103        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.users)
104        chart.axes.type("y")
105        chart.axes.range(0,0,max_value)
106        return chart.img()
107
108
109class OwnershipByState(BrowserView):
110    MAX = 10
111    def __init__(self, context, request):
112        self.context = context
113        self.request = request
114        self.cat = getToolByName(self.context, 'portal_catalog')
115        self.users = None
116        self.states = None
117        self.total = None
118        self.data = {}
119
120    def getUsers(self):
121        if self.users is None:
122            index = self.cat._catalog.getIndex('Creator')
123            data = {}
124            for k in index._index.keys():
125                haslen = hasattr(index._index[k], '__len__')
126                if haslen:
127                    data[k] = len(index._index[k])
128                else:
129                    data[k] = 1
130            data = data.items()
131            data.sort(lambda a, b: a[1] - b[1])
132            data.reverse()
133            data = data[:self.MAX]
134            self.users = [i[0] for i in data]
135            self.total = [i[1] for i in data]
136        return self.users
137
138    def getStates(self):
139        if self.states is None:
140            index = self.cat._catalog.getIndex('review_state')
141            data = {}
142            for k in index._index.keys():
143                haslen = hasattr(index._index[k], '__len__')
144                if haslen:
145                    data[k] = len(index._index[k])
146                else:
147                    data[k] = 1
148            data = data.items()
149            data.sort(lambda a, b: a[1] - b[1])
150            data.reverse()
151            self.states = [i[0] for i in data]
152        return self.states
153
154    def getContent(self, type_):
155        if type_ not in self.data:
156            if NO_WF_BIND not in self.data:
157                self.data[NO_WF_BIND] = self.getTotal()
158            data = self.data[type_] = []
159            for user in self.getUsers():
160                res = self.cat(review_state=type_, Creator=user)
161                l = len(res)
162                if l == 0:
163                    data.append(0)
164                else:
165                    data.append(l)
166            if len(data) > 0:
167                self.data[NO_WF_BIND] = map(lambda t,d:t-d, self.data[NO_WF_BIND], data)
168        return self.data[type_]
169
170    def getNoWFContentTitle(self):
171        return NO_WF_BIND
172
173    def getNoWFContent(self):
174        return self.getContent(NO_WF_BIND)
175
176    def getTotal(self):
177        if self.total is None:
178            self.getUsers()
179        return self.total
180
181    def getChart(self):
182        data = []
183        for state in self.getStates():
184            data.append(self.getContent(state))
185        data.append(self.getNoWFContent())
186        max_value = max(self.getTotal())
187        chart = VerticalBarStack(data, encoding='text')
188        chart.title('Content ownership by state').legend(*self.states+[NO_WF_BIND])
189        chart.bar('a', 10, 0).legend_pos("b")
190        chart.color(*COLORS)
191        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.users)
192        chart.axes.type("y")
193        chart.axes.range(0,0,max_value)
194        return chart.img()
195
196
197class TypeByState(BrowserView):
198    MAX = 10
199    def __init__(self, context, request):
200        self.context = context
201        self.request = request
202        self.cat = getToolByName(self.context, 'portal_catalog')
203        self.types = None
204        self.states = None
205        self.total = None
206        self.data = {}
207
208    def getTypes(self):
209        if self.types is None:
210            index = self.cat._catalog.getIndex('portal_type')
211            data = {}
212            for k in index._index.keys():
213                if not k:
214                    continue
215                haslen = hasattr(index._index[k], '__len__')
216                if haslen:
217                    data[k] = len(index._index[k])
218                else:
219                    data[k] = 1
220            data = data.items()
221            data.sort(lambda a, b: a[1] - b[1])
222            data.reverse()
223            self.types = [i[0] for i in data[:self.MAX]]
224            self.total = [i[1] for i in data[:self.MAX]]
225        return self.types
226
227    def getStates(self):
228        if self.states is None:
229            index = self.cat._catalog.getIndex('review_state')
230            data = {}
231            for k in index._index.keys():
232                haslen = hasattr(index._index[k], '__len__')
233                if haslen:
234                    data[k] = len(index._index[k])
235                else:
236                    data[k] = 1
237            data = data.items()
238            data.sort(lambda a, b: a[1] - b[1])
239            data.reverse()
240            self.states = [i[0] for i in data]
241        return self.states
242
243    def getContent(self, state):
244        if state not in self.data:
245            if NO_WF_BIND not in self.data:
246                self.data[NO_WF_BIND] = self.getTotal()
247            data = self.data[state] = []
248            for type_ in self.getTypes():
249                res = self.cat(portal_type=type_, review_state=state)
250                l = len(res)
251                if l == 0:
252                    data.append(0)
253                else:
254                    data.append(l)
255            if len(data) > 0:
256                self.data[NO_WF_BIND] = map(lambda t,d:t-d, self.data[NO_WF_BIND], data)
257        return self.data[state]
258
259    def getTotal(self):
260        if self.total is None:
261            self.getTypes()
262        return self.total
263
264    def getNoWFContentTitle(self):
265        return NO_WF_BIND
266
267    def getNoWFContent(self):
268        return self.getContent(NO_WF_BIND)
269
270    def getChart(self):
271        data = []
272        for state in self.getStates():
273            data.append(self.getContent(state))
274        data.append(self.getContent(NO_WF_BIND))
275        max_value = max(self.getTotal())
276        chart = VerticalBarStack(data, encoding='text')
277        chart.title('Content type by state').legend(*self.states+[NO_WF_BIND])
278        chart.bar('a', 10, 0).legend_pos("b")
279        chart.color(*COLORS)
280        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.types)
281        chart.axes.type("y")
282        chart.axes.range(0,0,max_value)
283        return chart.img()
284
285
286class LegacyPortlets(BrowserView):
287    def __init__(self, context, request):
288        self.context = context
289        self.request = request
290        self.total = None
291        self.DEBUG = False
292        self.expressions = set()
293
294    def _getInfo(self, obj):
295        href = obj.absolute_url()
296        path = '/'.join(obj.getPhysicalPath())
297        info = {
298            'path': path,
299            'href': href,
300            'left_slots': None,
301            'right_slots': None,
302        }
303        if IPropertyManager.providedBy(obj):
304            obj = aq_base(obj)
305            if obj.hasProperty('left_slots'):
306                info['left_slots'] = obj.getProperty('left_slots')
307                self.expressions = self.expressions.union(set(info['left_slots']))
308            if obj.hasProperty('right_slots'):
309                info['right_slots'] = obj.getProperty('right_slots')
310                self.expressions = self.expressions.union(set(info['right_slots']))
311        return info
312
313    def _walk(self, obj, level=-1):
314        yield self._getInfo(obj)
315        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
316            for v in obj.contentValues():
317                for i in self._walk(v, level-1):
318                    yield i
319
320    def getPortlets(self):
321        level = self.request.form.get('level', 1)
322        try:
323            level = level and int(level) or 1
324        except ValueError:
325            level = 1
326        infos = []
327        for i in self._walk(self.context, level):
328            if self.DEBUG or i['left_slots'] is not None or i['right_slots'] is not None:
329                infos.append(i)
330        self.total = len(infos)
331        return infos
332
333    def getTotal(self):
334        return self.total
335
336    def getAllPortletExpressions(self):
337        exprs = []
338        for name in self.expressions:
339            name = name.split('|')[0]
340            if not '/macros/' in name:
341                name = name.split('/')[-1]
342            name = name.strip()
343            if name not in exprs:
344                exprs.append(name)
345        exprs.sort()
346        return exprs
347
348class PropertiesStats(BrowserView):
349    def __init__(self, context, request):
350        self.context = context
351        self.request = request
352        self.total = None
353        self.DEBUG = False
354        self.expressions = set()
355        self.proplist = []
356        self.propname = self.request.form.get('propname') or ""
357
358    def _getInfo(self, obj):
359
360        href = obj.absolute_url()
361        path = '/'.join(obj.getPhysicalPath())
362        info = {
363            'path': path,
364            'href': href,
365            'slots': None,
366        }
367        if IPropertyManager.providedBy(obj):
368            obj = aq_base(obj)
369            self.proplist.extend([i for i in obj.propertyIds() if i not in self.proplist])
370            if obj.hasProperty(self.propname):
371                info['slots'] = obj.getProperty(self.propname)
372                if isinstance(info['slots'], int):
373                    info['slots'] = str(info['slots'])
374                if not isinstance(info['slots'], basestring):
375                    self.expressions = self.expressions.union(set(info['slots']))
376                else:
377                    self.expressions = self.expressions.union(set([info['slots']]))
378        return info
379
380    def _walk(self, obj, level=-1):
381        yield self._getInfo(obj)
382        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
383            for v in obj.contentValues():
384                for i in self._walk(v, level-1):
385                    yield i
386
387    def getPropsList(self):
388        level = self.request.form.get('level', 1)
389        try:
390            level = level and int(level) or 1
391        except ValueError:
392            level = 1
393        infos = []
394        for i in self._walk(self.context, level):
395            if self.DEBUG or i['slots'] is not None:
396                infos.append(i)
397        self.total = len(infos)
398        return infos
399
400    def getTotal(self):
401        return self.total
402
403    def getAllPortletExpressions(self):
404        exprs = []
405        for name in self.expressions:
406            name = name.strip()
407            if name not in exprs:
408                exprs.append(name)
409        #exprs.sort()
410        return exprs
411
412class PortletsStats(BrowserView):
413    def __init__(self, context, request):
414        self.context = context
415        self.request = request
416        self.total = None
417        self.DEBUG = False
418        self.expressions = set()
419        self.proplist = []
420        self.propname = self.request.form.get('propname') or ""
421
422    def getAssignmentMappingUrl(self, context, manager):
423        baseUrl = str(getMultiAdapter((context, self.request), name='absolute_url'))
424        return '%s/++contextportlets++%s' % (baseUrl, manager.__name__)
425
426    def getAssignmentsForManager(self, context, manager):
427        assignments = getMultiAdapter((context, manager), IPortletAssignmentMapping)
428        return assignments.values()
429
430    def getPortletsMapping(self, context):
431        leftcolumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=context)
432        rightcolumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=context)
433        leftmapping = getMultiAdapter((context, leftcolumn,), IPortletAssignmentMapping)
434        rightmapping = getMultiAdapter((context, rightcolumn,), IPortletAssignmentMapping)
435        return (leftmapping, rightmapping)
436
437    def getLocalPortletsManager(self, context):
438        leftcolumn = getUtility(IPortletManager, name='plone.leftcolumn', context=context)
439        rightcolumn = getUtility(IPortletManager, name='plone.rightcolumn', context=context)
440        leftmanager = getMultiAdapter((context, leftcolumn,), ILocalPortletAssignmentManager)
441        rightmanager = getMultiAdapter((context, rightcolumn,), ILocalPortletAssignmentManager)
442        return (leftmanager, rightmanager)
443
444    def getPortletsManager(self, context):
445        left = getUtility(IPortletManager, name='plone.leftcolumn', context=context)
446        right = getUtility(IPortletManager, name='plone.rightcolumn', context=context)
447        return (left, right)
448
449    def portlets_for_assignments(self, assignments, manager, base_url):
450        data = []
451        for idx in range(len(assignments)):
452            name = assignments[idx].__name__
453
454            editview = queryMultiAdapter(
455                (assignments[idx], self.request), name='edit', default=None)
456
457            if editview is None:
458                editviewName = ''
459            else:
460                editviewName = '%s/%s/edit' % (base_url, name)
461
462            settings = IPortletAssignmentSettings(assignments[idx])
463
464            data.append({
465                'title'      : assignments[idx].title,
466                'editview'   : editviewName,
467                'visible'    : settings.get('visible', True),
468                })
469        return data
470
471    def getPortlets(self, context, mapping, manager):
472        #import pdb; pdb.set_trace()
473        return mapping.keys()
474
475    def _getInfo(self, obj):
476        href = obj.absolute_url()
477        path = '/'.join(obj.getPhysicalPath())
478        info = {
479            'path': path,
480            'href': href,
481            'left_slots': None,
482            'right_slots': None,
483        }
484        left, right = self.getPortletsManager(obj)
485        #leftmapping, rightmapping = self.getPortletsMapping(obj)
486        #leftmanager, rightmanager = self.getLocalPortletsManager(obj)
487        #info['left_slots'] = self.getPortlets(obj, leftmapping, leftmanager)
488        #info['right_slots'] = self.getPortlets(obj, rightmapping, rightmanager)
489        lass = self.getAssignmentsForManager(obj, left)
490        rass = self.getAssignmentsForManager(obj, right)
491        lurl = self.getAssignmentMappingUrl(obj, left)
492        rurl = self.getAssignmentMappingUrl(obj, right)
493        plass = self.portlets_for_assignments(lass, left, lurl)
494        prass = self.portlets_for_assignments(rass, right, rurl)
495        #print obj, plass, prass
496        info['left_slots'] = plass #[i['title'] for i in plass]
497        info['right_slots'] = prass #[i['title'] for i in prass]
498        return info
499
500    def _walk(self, obj, level=-1):
501        try:
502            yield self._getInfo(obj)
503        except:
504            pass
505        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
506            for v in obj.contentValues():
507                for i in self._walk(v, level-1):
508                    yield i
509
510    def getPropsList(self):
511        level = self.request.form.get('level', 1)
512        try:
513            level = level and int(level) or 1
514        except ValueError:
515            level = 1
516        infos = []
517        for i in self._walk(self.context, level):
518            if self.DEBUG or i['left_slots'] is not None or i['right_slots'] is not None:
519                infos.append(i)
520        self.total = len(infos)
521        return infos
522
523    def getTotal(self):
524        return self.total
525
526    def getAllPortletExpressions(self):
527        exprs = []
528        for name in self.expressions:
529            name = name.strip()
530            if name not in exprs:
531                exprs.append(name)
532        #exprs.sort()
533        return exprs
534
535def human_format(num):
536    magnitude = 0
537    while num >= 1024:
538        magnitude += 1
539        num /= 1024.0
540    # add more suffixes if you need them
541    return '%.0f %sB' % (num, ['', 'K', 'M', 'G', 'T', 'P'][magnitude])
542
543def getSize(brain):
544    return float(brain.getObjSize.split()[0])*(
545                 brain.getObjSize.endswith('kB') and 1024 \
546                 or brain.getObjSize.endswith('MB') and 1024*1024 \
547                 or 1)
548
549def matrix_tranform(a):
550    b = [0 for i in range(len(a)-1)]
551    return [b[:i]+[a[i]]+b[i:] for i in range(len(a))]
552
553class SizeByPath(BrowserView):
554    def __init__(self, context, request):
555        self.context = context
556        self.request = request
557        self.cat = getToolByName(self.context, 'portal_catalog')
558        self.purl = getToolByName(self.context, 'portal_url')
559        self.total = None
560        self.DEBUG = True
561        bpath = self.request.form.get('basepath')
562        self.basepath = bpath and "/"+bpath.strip("/") or ""
563        # set default value for type by size stats
564        if not hasattr(self.request, 'type_name'):
565            self.request['type_name'] = "File"
566
567    def _brainsByPath(self, path):
568        return self.cat(path=path, Language="all")
569
570    def getValidPath(self):
571        portal = self.purl.getPortalObject()
572        return "/%s%s" % (portal.getId(), self.basepath)
573
574    def getSizeInfoByPath(self):
575        """API for chart builder"""
576        path = self.getValidPath()
577        return [{'size': getSize(b),
578                 'type': b.portal_type,
579                 'path': b.getPath()} \
580                for b in self._brainsByPath(path)]
581
582    def _walk(self, obj, path):
583        result = {}
584        for b in self._brainsByPath(path):
585            bpath = b.getPath()[len(path):]
586            p1 = len(bpath)>1 and bpath.split('/')[1] or ''
587            if not p1:
588                continue
589            data = result.setdefault(p1, {'size': 0, 'brain': None})
590            data['size'] += getSize(b)
591            if b.getPath() == "%s/%s" % (path, p1):
592                data['brain'] = b
593            result[p1] = data
594
595        self.total = human_format(sum([d['size'] for d in result.values()]))
596
597        sortedres = [(d['size'], d['brain']) for k,d in result.items() if d['brain']]
598        sortedres.sort(key=lambda i:i[0], reverse=True)
599        return sortedres
600
601    def getInfoForTableItem(self, size, brain):
602        oid = brain.getId or brain.id
603        analyse_url = ""
604        if brain.getObject().isPrincipiaFolderish:
605            analyse_url =  "%s/@@size_stats?basepath=%s/%s&submit=Search" % (
606                            self.purl(), self.basepath, oid)
607        return {'id': oid,
608                'path': "%s/%s" % (self.basepath, oid),
609                'href': "%s%s/%s" % (self.purl(), self.basepath, oid),
610                'human_size': human_format(size),
611                'analyse_url': analyse_url,}
612
613    def getSizeStats(self):
614        if self.request.get("submit", None) is None:
615            return []
616
617        infos = []
618        path = self.getValidPath()
619        for size, brain in self._walk(self.context, path):
620            if self.DEBUG or size > 1:
621                infos.append(self.getInfoForTableItem(size, brain))
622       
623        return infos
624
625    @view.memoize
626    def get_data(self, serh_type="File"):
627        if serh_type != "all":
628            objects_by_types_info = [i for i in self.getSizeInfoByPath() if i['type']==serh_type]
629        else:
630            objects_by_types_info = self.getSizeInfoByPath()
631        return sorted([ {'h_size': human_format(i['size']), 
632                          'size' : i['size'],
633                          'path' : i['path'],
634                          'id'   : i['path'].split('/')[-1]} 
635                        for i in objects_by_types_info ],
636                      key=itemgetter('size'),
637                      reverse=True)
638
639    def getChart(self):
640        type_name = self.request['type_name']
641        info_obj = self.get_data(type_name)[:10]
642        if not info_obj:
643            return "<h2>Not found objetcs of '%s' type</h2>" % type_name
644        path, data = zip(*map(itemgetter('path','size'), info_obj))
645        max_value = data[0]
646        #We need transpozed dataset, matrix_tranform does trabspozition
647        chart = VerticalBarStack(matrix_tranform(data))
648        chart.title('Objects of "%s" type by size'%type_name)
649        chart.bar('a', 10, 0).legend_pos("b")#.legend(*path)
650        chart.color(*COLORS)
651        chart.size(800, 375).scale(0,max_value)
652        chart.axes('xy').axes.type("y").axes.range(0,0,max_value)
653        return chart.img()
654       
Note: See TracBrowser for help on using the repository browser.