source: products/quintagroup.analytics/branches/treemap/quintagroup/analytics/browser/views.py @ 3372

Last change on this file since 3372 was 3372, checked in by potar, 8 years ago

Added new branch

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