419 lines
17 KiB
Python
419 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import json
|
|
import csv
|
|
from cStringIO import StringIO
|
|
|
|
from Products.Five import BrowserView
|
|
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
|
from zope.interface import Interface
|
|
|
|
from zope import component
|
|
from zc.relation.interfaces import ICatalog
|
|
from zope.app.intid.interfaces import IIntIds
|
|
from Products.CMFCore.utils import getToolByName
|
|
from plone.registry.interfaces import IRegistry
|
|
|
|
from tabellio.config.interfaces import ITabellioSettings
|
|
import tabellio.config.utils
|
|
|
|
from misc import Cached
|
|
|
|
try:
|
|
from plone.app.caching.operations.utils import setCacheHeaders
|
|
except ImportError:
|
|
setCacheHeaders = None
|
|
|
|
import cairoplot
|
|
|
|
class View(BrowserView, Cached):
|
|
def birthline(self):
|
|
if not self.context.birthdate:
|
|
return
|
|
if self.context.sex == 'M':
|
|
word = u'Né'
|
|
else:
|
|
word = u'Née'
|
|
if self.context.birthplace:
|
|
return u'%s à %s, le %s' % (word, self.context.birthplace,
|
|
self.context.birthdate.strftime('%d/%m/%Y'))
|
|
else:
|
|
return u'%s le %s' % (word, self.context.birthdate.strftime('%d/%m/%Y'))
|
|
|
|
def degrees(self):
|
|
degrees = self.context.degrees
|
|
return '<ul>' + '\n'.join(u'<li>%s</li>' % x for x in degrees.splitlines() if x.strip()) + '</ul>'
|
|
|
|
def mandates(self):
|
|
mandates = self.context.mandates
|
|
return '<ul>' + '\n'.join(u'<li>%s</li>' % x for x in mandates.splitlines() if x.strip()) + '</ul>'
|
|
|
|
def profession(self):
|
|
profession = self.context.profession
|
|
return '<ul>' + '\n'.join(u'<li>%s</li>' % x for x in profession.splitlines() if x.strip()) + '</ul>'
|
|
|
|
def get_commissions_and_roles(self):
|
|
intids = component.getUtility(IIntIds)
|
|
catalog = component.getUtility(ICatalog)
|
|
try:
|
|
author_intid = intids.getId(self.context)
|
|
except KeyError:
|
|
intids.register(self.context)
|
|
author_intid = intids.getId(self.context)
|
|
|
|
president = [x.from_object for x in catalog.findRelations({
|
|
'to_id': author_intid,
|
|
'from_attribute': 'president'})]
|
|
vicepresident = [x.from_object for x in catalog.findRelations({
|
|
'to_id': author_intid,
|
|
'from_attribute': 'vicepresidents'})]
|
|
member = [x.from_object for x in catalog.findRelations({
|
|
'to_id': author_intid,
|
|
'from_attribute': 'members'})]
|
|
substitute = [x.from_object for x in catalog.findRelations({
|
|
'to_id': author_intid,
|
|
'from_attribute': 'substitutes'})]
|
|
roles = []
|
|
if self.context.sex == 'M':
|
|
mapping = {'P': u'Président', 'VP': u'Vice-président', 'M': u'Membre', 'S': u'Suppléant'}
|
|
else:
|
|
mapping = {'P': u'Présidente', 'VP': u'Vice-présidente', 'M': u'Membre', 'S': u'Suppléante'}
|
|
|
|
# do not duplicate presidents and vice presidents as members
|
|
for commission in president + vicepresident:
|
|
if commission in member:
|
|
member.remove(commission)
|
|
|
|
for commission in president + vicepresident + member + substitute:
|
|
if commission.title.lower().startswith(u'commission'):
|
|
prefix = 'de la'
|
|
else:
|
|
prefix = 'du'
|
|
if commission in president:
|
|
title = mapping['P']
|
|
elif commission in vicepresident:
|
|
title = mapping['VP']
|
|
elif commission in member:
|
|
title = mapping['M']
|
|
elif commission in substitute:
|
|
title = mapping['S']
|
|
roles.append((title, prefix, commission))
|
|
return roles
|
|
|
|
_activity_as_author = None
|
|
def get_activity_as_author(self):
|
|
if self._activity_as_author is not None:
|
|
return self._activity_as_author
|
|
intids = component.getUtility(IIntIds)
|
|
catalog = component.getUtility(ICatalog)
|
|
try:
|
|
author_intid = intids.getId(self.context)
|
|
except KeyError:
|
|
intids.register(self.context)
|
|
author_intid = intids.getId(self.context)
|
|
activity = [x.from_object for x in catalog.findRelations({
|
|
'to_id': intids.getId(self.context),
|
|
'from_attribute': 'authors'})]
|
|
activity = [x for x in activity if hasattr(x, 'date')]
|
|
self._activity_as_author = activity
|
|
return activity
|
|
|
|
_activity_as_participant = None
|
|
def get_activity_as_participant(self):
|
|
if self._activity_as_participant is not None:
|
|
return self._activity_as_participant
|
|
intids = component.getUtility(IIntIds)
|
|
catalog = component.getUtility(ICatalog)
|
|
try:
|
|
author_intid = intids.getId(self.context)
|
|
except KeyError:
|
|
intids.register(self.context)
|
|
author_intid = intids.get(self.context)
|
|
activity = [x.from_object for x in catalog.findRelations({
|
|
'to_id': intids.getId(self.context),
|
|
'from_attribute': 'participants'})]
|
|
activity = [x for x in activity if hasattr(x, 'date')]
|
|
self._activity_as_participant = activity
|
|
return activity
|
|
|
|
_activity_as_reporter = None
|
|
def get_activity_as_reporter(self):
|
|
if self._activity_as_reporter is not None:
|
|
return self._activity_as_reporter
|
|
intids = component.getUtility(IIntIds)
|
|
catalog = component.getUtility(ICatalog)
|
|
try:
|
|
reporter_intid = intids.getId(self.context)
|
|
except KeyError:
|
|
intids.register(self.context)
|
|
reporter_intid = initds.get(self.context)
|
|
reports = [x.from_object for x in catalog.findRelations({
|
|
'to_id': reporter_intid,
|
|
'from_attribute': 'reporters'})]
|
|
reports = [x for x in reports if hasattr(x, 'date')]
|
|
self._activity_as_reporter = reports
|
|
|
|
return reports
|
|
|
|
_activity_as_speaker = None
|
|
def get_activity_as_speaker(self):
|
|
if self._activity_as_speaker is not None:
|
|
return self._activity_as_speaker
|
|
intids = component.getUtility(IIntIds)
|
|
catalog = component.getUtility(ICatalog)
|
|
try:
|
|
speaker_intid = intids.getId(self.context)
|
|
except KeyError:
|
|
intids.register(self.context)
|
|
speaker_intid = intids.get(self.context)
|
|
speeches = [x.from_object for x in catalog.findRelations({
|
|
'to_id': speaker_intid,
|
|
'from_attribute': 'speakers'})]
|
|
speeches = [x for x in speeches if hasattr(x, 'date')]
|
|
self._activity_as_speaker = speeches
|
|
|
|
return speeches
|
|
|
|
|
|
def sessions_with_activity(self):
|
|
sessions_dict = {}
|
|
for activity in self.get_activity_as_author():
|
|
sessions_dict[activity.session] = True
|
|
for activity in self.get_activity_as_participant():
|
|
sessions_dict[activity.session] = True
|
|
for activity in self.get_activity_as_speaker():
|
|
sessions_dict[activity.session] = True
|
|
for activity in self.get_activity_as_reporter():
|
|
sessions_dict[activity.session] = True
|
|
sessions = []
|
|
for session in tabellio.config.utils.get_sessions():
|
|
if session in sessions_dict:
|
|
sessions.append(session)
|
|
|
|
return sessions
|
|
|
|
def activity(self, session=None):
|
|
activity = [(x, 'author') for x in self.get_activity_as_author()] + \
|
|
[(x, 'participant') for x in self.get_activity_as_participant()] + \
|
|
[(x, 'reporter') for x in self.get_activity_as_reporter()] + \
|
|
[(x, 'speaker') for x in self.get_activity_as_speaker()]
|
|
if session:
|
|
activity = [x for x in activity if x[0].session == session]
|
|
|
|
def cmp_date(x, y):
|
|
x, y = x[0], y[0]
|
|
if x.date is None and y.date is None: return 0
|
|
if x.date is None: return -1
|
|
if y.date is None: return 1
|
|
return cmp(x.date, y.date)
|
|
|
|
activity.sort(cmp=cmp_date, reverse=True)
|
|
return activity
|
|
|
|
def contact_form_url(self):
|
|
brains = self.context.portal_catalog(portal_type='tabellio.contact.form')
|
|
if not brains:
|
|
return None
|
|
return brains[0].getURL()
|
|
|
|
|
|
class IFolderView(Interface):
|
|
pass
|
|
|
|
class FolderView(BrowserView):
|
|
deputy_listform = ViewPageTemplateFile('deputy_listform.pt')
|
|
|
|
def renderDeputyListForm(self):
|
|
return self.deputy_listform()
|
|
|
|
def sortedDeputies(self):
|
|
from plone.i18n.normalizer.fr import normalizer
|
|
def cmp_deputy(x, y):
|
|
# | replacement is an hack to get spaces to sort after letters
|
|
return cmp(normalizer.normalize('%s %s' % (x.lastname.replace(' ', '|'), x.firstname.replace(' ', '|'))).lower(),
|
|
normalizer.normalize('%s %s' % (y.lastname.replace(' ', '|'), y.firstname.replace(' ', '|'))).lower())
|
|
return sorted([x for x in self.context.values() if x.Type() == 'Deputy' and x.active], cmp=cmp_deputy)
|
|
|
|
def getPolGroups(self):
|
|
polgroups_path = tabellio.config.utils.get_polgroups_path()
|
|
current = getToolByName(self.context, 'portal_url').getPortalObject()
|
|
for part in polgroups_path.split('/'):
|
|
if not part:
|
|
continue
|
|
current = getattr(current, part)
|
|
return [x for x in current.objectValues() if x.portal_type == 'themis.datatypes.polgroup' and x.active]
|
|
|
|
def getQueryName(self):
|
|
return self.request.form.get('deputy.widgets.name')
|
|
|
|
def getSelectedPolgroup(self):
|
|
try:
|
|
return self.request.form.get('deputy.widgets.polgroup')[0]
|
|
except (IndexError, TypeError):
|
|
return None
|
|
|
|
def renderPolgroupTitle(self, item):
|
|
return item.Title().replace('-', ' -')
|
|
|
|
class IDeputiesAndOthersFolderView(Interface):
|
|
pass
|
|
|
|
class DeputiesAndOthersFolderView(FolderView, Cached):
|
|
pass
|
|
|
|
class IPfbDeputiesFolderView(Interface):
|
|
pass
|
|
|
|
class PfbDeputiesFolderView(FolderView, Cached):
|
|
pass
|
|
|
|
class GenderStatsView(BrowserView):
|
|
def __call__(self):
|
|
self.request.response.setHeader('Content-type', 'image/png')
|
|
if setCacheHeaders:
|
|
setCacheHeaders(self, self.request, self.request.response,
|
|
maxage=300, smaxage=300)
|
|
data = {u'Hommes': len([x for x in self.context.values() if
|
|
x.Type() == 'Deputy' and x.sex == 'M' and x.active]),
|
|
u'Femmes': len([x for x in self.context.values() if
|
|
x.Type() == 'Deputy' and x.sex == 'F' and x.active])}
|
|
|
|
settings = component.getUtility(IRegistry).forInterface(ITabellioSettings, False)
|
|
if settings.gender_colors:
|
|
series_colors = [ hexa_to_float(settings.gender_colors.split(',')[0]),
|
|
hexa_to_float(settings.gender_colors.split(',')[1])]
|
|
else:
|
|
series_colors = [ (0.77,0.92,0.94), (0.54,0.56,0.69) ]
|
|
surface = cairoplot.cairo.ImageSurface(cairoplot.cairo.FORMAT_ARGB32, 600, 320)
|
|
plot = cairoplot.DonutPlot(surface, data, 600, 320,
|
|
background=None,
|
|
gradient=False, shadow=True,
|
|
inner_radius=0.5, colors=series_colors)
|
|
plot.radius = 150
|
|
plot.center = (200, 160)
|
|
plot.render_shadow()
|
|
plot.render_plot()
|
|
|
|
for number, group in enumerate(plot.series):
|
|
if number == 0:
|
|
baseline = 320/2-30
|
|
else:
|
|
baseline = 320/2+30
|
|
percent = int(round(100.*sum(group.to_list())/plot.total))
|
|
plot.context.set_source_rgba(*plot.series_colors[number][:4])
|
|
plot.context.rectangle(410, baseline-25, 40, 40)
|
|
plot.context.fill()
|
|
plot.context.set_source_rgba(*plot.label_color)
|
|
plot.context.set_font_size( 1.2 * plot.font_size )
|
|
plot.context.move_to(415, baseline)
|
|
plot.context.show_text('%s %%' % percent)
|
|
plot.context.move_to(460, baseline)
|
|
plot.context.show_text(group.name)
|
|
|
|
plot.context.show_page()
|
|
fd = StringIO()
|
|
surface.write_to_png(fd)
|
|
surface.finish()
|
|
return fd.getvalue()
|
|
|
|
def hexa_to_float(code):
|
|
return (int(code[1:3], 16)/255., int(code[3:5], 16)/255., int(code[5:7], 16)/255.)
|
|
|
|
class AgeStatsView(BrowserView):
|
|
def __call__(self):
|
|
self.request.response.setHeader('Content-type', 'image/png')
|
|
if setCacheHeaders:
|
|
setCacheHeaders(self, self.request, self.request.response,
|
|
maxage=300, smaxage=300)
|
|
ageranges = [x.agerange for x in self.context.values() if
|
|
x.Type() == 'Deputy' and x.active]
|
|
x_labels = ('m30', 'd30-40', 'd40-50', 'd50-60', 'd60-70', 'p70')
|
|
data = []
|
|
for age in x_labels:
|
|
data.append(len([x for x in ageranges if x == age]))
|
|
x_labels = ('-30', '30-40', '40-50', '50-60', '60-70', '+70')
|
|
|
|
settings = component.getUtility(IRegistry).forInterface(ITabellioSettings, False)
|
|
if settings.ageranges_color:
|
|
series_colors = [ list(hexa_to_float(settings.ageranges_color) + ('solid',)) ] * len(x_labels)
|
|
else:
|
|
series_colors = [ [0.54,0.56,0.69, 'solid'] ] * len(x_labels)
|
|
increment = 5
|
|
y_bounds = [0, ((max(data)/increment)+1)*increment]
|
|
y_labels = [str(x) for x in range(0, y_bounds[1]+increment, increment)]
|
|
surface = cairoplot.cairo.ImageSurface(cairoplot.cairo.FORMAT_ARGB32, 500, 320)
|
|
plot = cairoplot.VerticalBarPlot(surface, data, 500, 320,
|
|
display_values=True,
|
|
grid=True,
|
|
series_colors=series_colors,
|
|
border=20,
|
|
x_labels=x_labels,
|
|
y_labels=y_labels,
|
|
y_bounds=y_bounds)
|
|
plot.pad = 15
|
|
plot.render()
|
|
plot.context.show_page()
|
|
fd = StringIO()
|
|
surface.write_to_png(fd)
|
|
surface.finish()
|
|
return fd.getvalue()
|
|
|
|
|
|
class JsonView(BrowserView):
|
|
def __call__(self):
|
|
l = []
|
|
for object in self.context.objectValues():
|
|
if object.portal_type != 'themis.datatypes.deputy':
|
|
continue
|
|
l.append(object.id)
|
|
self.request.response.setHeader('Content-type', 'application/json')
|
|
return json.dumps(l)
|
|
|
|
class CsvView(BrowserView):
|
|
def __call__(self):
|
|
output = StringIO()
|
|
csv_output = csv.writer(output)
|
|
|
|
def address_as_csv(address):
|
|
if address is None:
|
|
return [None]*8
|
|
row = []
|
|
for attr in ('title', 'street', 'zipcode', 'city',
|
|
'phone1', 'phone2', 'fax', 'email'):
|
|
if getattr(address, attr):
|
|
row.append(getattr(address, attr).encode('utf-8'))
|
|
else:
|
|
row.append(None)
|
|
return row
|
|
|
|
for object in self.context.objectValues():
|
|
if object.portal_type != 'themis.datatypes.deputy':
|
|
continue
|
|
if not object.active:
|
|
continue
|
|
row = []
|
|
for attr in ('firstname', 'lastname', 'sex', 'district',
|
|
'website', 'birthplace'):
|
|
if getattr(object, attr):
|
|
row.append(getattr(object, attr).encode('utf-8'))
|
|
else:
|
|
row.append(None)
|
|
if object.birthdate:
|
|
row.append(object.birthdate.strftime('%Y-%m-%d'))
|
|
else:
|
|
row.append(None)
|
|
if object.polgroup:
|
|
polgroup_name = object.polgroup.to_object.title
|
|
if type(polgroup_name) is unicode:
|
|
polgroup_name = polgroup_name.encode('utf-8')
|
|
row.append(polgroup_name)
|
|
else:
|
|
row.append(None)
|
|
row.extend(address_as_csv(object.private_address))
|
|
row.extend(address_as_csv(object.work_address))
|
|
row.extend(address_as_csv(object.work_address_2))
|
|
|
|
csv_output.writerow(row)
|
|
self.request.response.setHeader('Content-type', 'text/csv')
|
|
return output.getvalue()
|