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

Last change on this file since 14607 was 14607, checked in by Henrik Bettermann, 8 years ago

Ease customization of report title.

  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1## $Id: student_statistics.py 14607 2017-03-07 09:36:09Z 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    def __init__(self, session, mode, level, breakdown, author='System'):
149        super(StudentStatisticsReport, self).__init__(
150            args=[session, mode, breakdown], kwargs={'author':author})
151        self.sessioncode = session
152        self.levelcode = level
153        self.studylevelsource = StudyLevelSource().factory
154        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
155        self.session = academic_sessions_vocab.getTerm(session).title
156        self.mode = mode
157        if level == 0:
158            self.level = 'All levels'
159        else:
160            self.level = translate(
161                self.studylevelsource.getTitle(None, int(level)),
162                'waeup.kofa', target_language=self.portal_language)
163        self.breakdown = breakdown
164        self.author = author
165        self.creation_dt_string = self.creation_dt.astimezone(
166            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
167        self.data = get_student_stats(session, mode, level, breakdown)
168
169    def create_pdf(self, job_id):
170        creator = getUtility(IPDFCreator, name=self.pdfcreator)
171        table_data = tbl_data_to_table(*self.data)
172        col_widths = [None,] + [1.6*cm] * len(self.data[1]) + [None,]
173        pdf_data = [Paragraph('<b>%s - Report %s</b>'
174                              % (self.creation_dt_string, job_id),
175                              STYLE["Normal"]),
176                    Spacer(1, 12),]
177        pdf_data += [Paragraph(
178                    translate(
179                        'Study Mode: ${a}<br />'
180                        'Academic Session: ${b}<br />Level: ${c}<br />',
181                        mapping = {'a':self.mode,
182                                   'b':self.session,
183                                   'c':self.level,
184                                   }),
185                    STYLE["Normal"]),
186                    Spacer(1, 12),]
187        pdf_data += [
188            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
189        doc_title = self.title
190        right_footer = translate(
191            _('${a} Students - ${b} -',
192            mapping = {'a':self.mode, 'b':self.session}))
193        pdf = creator.create_pdf(
194            pdf_data, None, doc_title, self.author, right_footer
195            )
196        return pdf
197
198@implementer(IReportGenerator)
199class StudentStatisticsReportGenerator(grok.GlobalUtility):
200
201    title = _('Student Statistics')
202    grok.name('student_stats')
203
204    def generate(
205        self, site, session=None, mode=None,
206        level=None, breakdown=None, author=None):
207        result = StudentStatisticsReport(
208            session=session, mode=mode, level=level,
209            breakdown=breakdown, author=author)
210        return result
211
212###############################################################
213## Browser related stuff
214##
215## XXX: move to local browser module
216###############################################################
217from waeup.kofa.browser.layout import KofaPage
218from waeup.kofa.interfaces import academic_sessions_vocab
219from waeup.kofa.reports import get_generators
220from waeup.kofa.browser.breadcrumbs import Breadcrumb
221grok.templatedir('browser_templates')
222class StudentStatisticsReportGeneratorPage(KofaPage):
223
224    grok.context(StudentStatisticsReportGenerator)
225    grok.name('index.html')
226    grok.require('waeup.handleReports')
227
228    label = _('Create student statistics report')
229
230    @property
231    def generator_name(self):
232        for name, gen in get_generators():
233            if gen == self.context:
234                return name
235        return None
236
237    def update(
238        self, CREATE=None, session=None, mode=None, level=None, breakdown=None):
239        self.parent_url = self.url(self.context.__parent__)
240        self._set_session_values()
241        self._set_mode_values()
242        self._set_level_values()
243        self._set_breakdown_values()
244        if CREATE and session:
245            # create a new report job for students by session and level
246            container = self.context.__parent__
247            user_id = self.request.principal.id
248            kw = dict(
249                session=int(session),
250                mode=mode,
251                level=int(level),
252                breakdown=breakdown)
253            self.flash(_('New report is being created in background'))
254            job_id = container.start_report_job(
255                self.generator_name, user_id, kw=kw)
256            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
257            grok.getSite().logger.info(
258                '%s - report %s created: %s '
259                '(session=%s, mode=%s, level=%s, breakdown=%s)' % (
260                ob_class, job_id, self.context.title,
261                session, mode, level, breakdown))
262            self.redirect(self.parent_url)
263            return
264        return
265
266    def _set_session_values(self):
267        vocab_terms = academic_sessions_vocab.by_value.values()
268        self.sessions = [(x.title, x.token) for x in vocab_terms]
269        return
270
271    def _set_mode_values(self):
272        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
273        self.modes = sorted([(key, key) for key in mode_groups.keys()])
274        return
275
276    def _set_level_values(self):
277        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
278        studylevelsource = StudyLevelSource().factory
279        self.levels = [(u'All', 0)]
280        for code in studylevelsource.getValues(None):
281            title = translate(studylevelsource.getTitle(None, code),
282                'waeup.kofa', target_language=portal_language)
283            self.levels.append((title, code))
284        return
285
286    def _set_breakdown_values(self):
287        self.breakdowns = [('Faculties', 'faccode'), ('Departments', 'depcode')]
288        return
289
290class StudentStatisticsReportPDFView(grok.View):
291
292    grok.context(IStudentStatisticsReport)
293    grok.name('pdf')
294    grok.require('waeup.handleReports')
295    prefix = ''
296
297    def _filename(self):
298        return 'StudentStatisticsReport_rno%s_%s.pdf' % (
299            self.context.__name__,
300            self.context.creation_dt_string)
301
302    def render(self):
303        filename = self._filename().replace(
304            '/', '_').replace(' ','_').replace(':', '-')
305        self.response.setHeader(
306            'Content-Type', 'application/pdf')
307        self.response.setHeader(
308            'Content-Disposition:', 'attachment; filename="%s' % filename)
309        pdf_stream = self.context.create_pdf(self.context.__name__)
310        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
311        grok.getSite().logger.info('%s - report %s downloaded: %s' % (
312            ob_class, self.context.__name__, filename))
313        return pdf_stream
Note: See TracBrowser for help on using the repository browser.