source: main/waeup.kofa/trunk/src/waeup/kofa/students/reports/student_statistics.py @ 17711

Last change on this file since 17711 was 15698, checked in by Henrik Bettermann, 5 years ago

Translate select box values.

  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1## $Id: student_statistics.py 15698 2019-10-22 10:54:34Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19from zope.i18n import translate
20from zope.catalog.interfaces import ICatalog
21from zope.component import queryUtility, getUtility
22from zope.interface import implementer, Interface, Attribute
23from waeup.kofa.interfaces import (
24    IKofaUtils,
25    academic_sessions_vocab, registration_states_vocab)
26from waeup.kofa.students.vocabularies import StudyLevelSource
27from waeup.kofa.interfaces import MessageFactory as _
28from waeup.kofa.reports import IReport
29
30class IStudentStatisticsReport(IReport):
31
32    session = Attribute('Session to report')
33    level = Attribute('Level to report')
34    mode = Attribute('Study modes group to report')
35    creation_dt_string = Attribute('Human readable report creation datetime')
36
37def get_student_stats(session, mode, level, breakdown):
38    """Get students in a certain session, study mode and current level.
39
40    Returns a table ordered by code (faculty or department, one per row) and
41    registration state (cols). The result is a 3-tuple representing
42    ((<PATHS>), (<STATES>), (<NUM_OF_STUDENTS>)). The
43    (<NUM_OF_STUDENTS>) is an n-tuple with each entry containing the
44    number of students found in that faculty/department and with the respective
45    state.
46
47    Sample result:
48
49      >>> ((u'FAC1', u'FAC2'),
50      ...  ('created', 'accepted', 'registered'),
51      ...  ((12, 10, 1), (0, 5, 25)))
52
53    This result means: there are 5 students in FAC2 in state
54    'accepted' while 12 students in 'FAC1' are in state 'created'.
55    """
56    site = grok.getSite()
57    states = tuple([x.value for x in registration_states_vocab])
58    states = states + (u'Total',)
59    if breakdown == 'faccode':
60        paths = tuple(sorted([x for x in site['faculties'].keys()],
61                                 key=lambda x: x.lower()))
62    elif breakdown == 'depcode':
63        faculties = site['faculties']
64        deppaths = []
65        for fac in faculties.values():
66            for dep in fac.values():
67                deppaths.append('%s/%s' % (fac.code, dep.code))
68        paths = tuple(sorted([x for x in deppaths],
69                                 key=lambda x: x.lower()))
70    # XXX: Here we do _one_ query and then examine the single
71    #   students. One could also do multiple queries and just look for
72    #   the result length (not introspecting the delivered students
73    #   further).
74    cat = queryUtility(ICatalog, name="students_catalog")
75    result = cat.searchResults(current_session=(session, session))
76    table = [[0 for x in xrange(len(states))] for y in xrange(len(paths)+1)]
77    mode_groups = getUtility(IKofaUtils).MODE_GROUPS
78    for stud in result:
79        if mode != 'All' and stud.current_mode \
80            and stud.current_mode not in mode_groups[mode]:
81            continue
82        if level != 0 and stud.current_level != level:
83            continue
84        if getattr(stud, breakdown) is None:
85            continue
86        if breakdown == 'faccode':
87            row_name = getattr(stud, 'faccode')
88        elif breakdown == 'depcode':
89            row_name = '%s/%s' % (
90                getattr(stud, 'faccode'), getattr(stud, 'depcode'))
91        row = paths.index(row_name)
92        col = states.index(stud.state)
93        table[row][col] += 1
94        table[-1][col] += 1
95        table[row][-1] += 1
96        table[-1][-1] += 1
97    # turn lists into tuples
98    table = tuple([tuple(row) for row in table])
99
100    paths = paths + (u'Total',)
101    return (paths, states, table)
102
103from reportlab.lib import colors
104from reportlab.lib.styles import getSampleStyleSheet
105from reportlab.lib.units import cm
106from reportlab.platypus import Paragraph, Table, Spacer
107from waeup.kofa.reports import IReport, IReportGenerator
108from waeup.kofa.reports import Report
109from waeup.kofa.browser.interfaces import IPDFCreator
110
111STYLE = getSampleStyleSheet()
112
113def tbl_data_to_table(row_names, col_names, data):
114    result = []
115    new_col_names = []
116    for name in col_names:
117        new_col_names.append(name.replace(' ', '\n'))
118    head = [''] + list(new_col_names)
119    result = [head]
120    for idx, row_name in enumerate(row_names):
121        row = [row_name] + list(data[idx])
122        result.append(row)
123    return result
124
125TABLE_STYLE = [
126    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
127    ('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8),
128    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8),
129    ('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8),
130    ('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8),
131    ('ALIGN', (1,1), (-1,-1), 'RIGHT'),
132    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
133    ('LINEBELOW', (0,-1), (-1,-1), 0.25, colors.black),
134    ('LINEAFTER', (-1,0), (-1,-1), 0.25, colors.black),
135    ('LINEBEFORE', (-1,0), (-1,-1), 1.0, colors.black),
136    #('LINEABOVE', (0,-1), (-1,-1), 1.0, colors.black),
137    #('LINEABOVE', (0,0), (-1,0), 0.25, colors.black),
138    ]
139
140@implementer(IStudentStatisticsReport)
141class StudentStatisticsReport(Report):
142    data = None
143    session = None
144    mode = None
145    pdfcreator = 'landscape'
146    title = translate(_('Student Statistics'))
147
148    @property
149    def title(self):
150        return translate(_('Student Statistics'))
151
152    def __init__(self, session, mode, level, breakdown, author='System'):
153        super(StudentStatisticsReport, self).__init__(
154            args=[session, mode, breakdown], kwargs={'author':author})
155        self.sessioncode = session
156        self.levelcode = level
157        self.studylevelsource = StudyLevelSource().factory
158        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
159        self.session = academic_sessions_vocab.getTerm(session).title
160        self.mode = mode
161        if level == 0:
162            self.level = 'All levels'
163        else:
164            self.level = translate(
165                self.studylevelsource.getTitle(None, int(level)),
166                'waeup.kofa', target_language=self.portal_language)
167        self.breakdown = breakdown
168        self.author = author
169        self.creation_dt_string = self.creation_dt.astimezone(
170            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
171        self.data = get_student_stats(session, mode, level, breakdown)
172
173    def create_pdf(self, job_id):
174        creator = getUtility(IPDFCreator, name=self.pdfcreator)
175        table_data = tbl_data_to_table(*self.data)
176        col_widths = [None,] + [1.6*cm] * len(self.data[1]) + [None,]
177        pdf_data = [Paragraph('<b>%s - Report %s</b>'
178                              % (self.creation_dt_string, job_id),
179                              STYLE["Normal"]),
180                    Spacer(1, 12),]
181        pdf_data += [Paragraph(
182                    translate(
183                        'Study Mode: ${a}<br />'
184                        'Academic Session: ${b}<br />Level: ${c}<br />',
185                        mapping = {'a':self.mode,
186                                   'b':self.session,
187                                   'c':self.level,
188                                   }),
189                    STYLE["Normal"]),
190                    Spacer(1, 12),]
191        pdf_data += [
192            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
193        right_footer = translate(
194            _('${a} Students - ${b} -',
195            mapping = {'a':self.mode, 'b':self.session}))
196        pdf = creator.create_pdf(
197            pdf_data, None, self.title, self.author, right_footer
198            )
199        return pdf
200
201@implementer(IReportGenerator)
202class StudentStatisticsReportGenerator(grok.GlobalUtility):
203
204    title = _('Student Statistics')
205    grok.name('student_stats')
206
207    def generate(
208        self, site, session=None, mode=None,
209        level=None, breakdown=None, author=None):
210        result = StudentStatisticsReport(
211            session=session, mode=mode, level=level,
212            breakdown=breakdown, author=author)
213        return result
214
215###############################################################
216## Browser related stuff
217##
218## XXX: move to local browser module
219###############################################################
220from waeup.kofa.browser.layout import KofaPage
221from waeup.kofa.interfaces import academic_sessions_vocab
222from waeup.kofa.reports import get_generators
223from waeup.kofa.browser.breadcrumbs import Breadcrumb
224grok.templatedir('browser_templates')
225class StudentStatisticsReportGeneratorPage(KofaPage):
226
227    grok.context(StudentStatisticsReportGenerator)
228    grok.name('index.html')
229    grok.require('waeup.handleReports')
230
231    label = _('Create student statistics report')
232
233    @property
234    def generator_name(self):
235        for name, gen in get_generators():
236            if gen == self.context:
237                return name
238        return None
239
240    def update(
241        self, CREATE=None, session=None, mode=None, level=None, breakdown=None):
242        self.parent_url = self.url(self.context.__parent__)
243        self._set_session_values()
244        self._set_mode_values()
245        self._set_level_values()
246        self._set_breakdown_values()
247        if CREATE and session:
248            # create a new report job for students by session and level
249            container = self.context.__parent__
250            user_id = self.request.principal.id
251            kw = dict(
252                session=int(session),
253                mode=mode,
254                level=int(level),
255                breakdown=breakdown)
256            self.flash(_('New report is being created in background'))
257            job_id = container.start_report_job(
258                self.generator_name, user_id, kw=kw)
259            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
260            grok.getSite().logger.info(
261                '%s - report %s created: %s '
262                '(session=%s, mode=%s, level=%s, breakdown=%s)' % (
263                ob_class, job_id, self.context.title,
264                session, mode, level, breakdown))
265            self.redirect(self.parent_url)
266            return
267        return
268
269    def _set_session_values(self):
270        vocab_terms = academic_sessions_vocab.by_value.values()
271        self.sessions = [(x.title, x.token) for x in vocab_terms]
272        return
273
274    def _set_mode_values(self):
275        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
276        self.modes = sorted([(key, key) for key in mode_groups.keys()])
277        return
278
279    def _set_level_values(self):
280        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
281        studylevelsource = StudyLevelSource().factory
282        self.levels = [(u'All', 0)]
283        for code in studylevelsource.getValues(None):
284            title = translate(studylevelsource.getTitle(None, code),
285                'waeup.kofa', target_language=portal_language)
286            self.levels.append((title, code))
287        return
288
289    def _set_breakdown_values(self):
290        self.breakdowns = [(_('Faculties'), 'faccode'), (_('Departments'), 'depcode')]
291        return
292
293class StudentStatisticsReportPDFView(grok.View):
294
295    grok.context(IStudentStatisticsReport)
296    grok.name('pdf')
297    grok.require('waeup.handleReports')
298
299    def _filename(self):
300        return 'StudentStatisticsReport_rno%s_%s.pdf' % (
301            self.context.__name__,
302            self.context.creation_dt_string)
303
304    def render(self):
305        filename = self._filename().replace(
306            '/', '_').replace(' ','_').replace(':', '-')
307        self.response.setHeader(
308            'Content-Type', 'application/pdf')
309        self.response.setHeader(
310            'Content-Disposition:', 'attachment; filename="%s' % filename)
311        pdf_stream = self.context.create_pdf(self.context.__name__)
312        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
313        grok.getSite().logger.info('%s - report %s downloaded: %s' % (
314            ob_class, self.context.__name__, filename))
315        return pdf_stream
Note: See TracBrowser for help on using the repository browser.