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

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

Ease selection of pdf creator in custom packages.

  • Property svn:keywords set to Id
File size: 12.1 KB
Line 
1## $Id: student_statistics.py 14438 2017-01-23 09:20:52Z 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 not in mode_groups[mode]:
80            continue
81        if level != 0 and stud.current_level != level:
82            continue
83        if getattr(stud, breakdown) is None:
84            continue
85        if breakdown == 'faccode':
86            row_name = getattr(stud, 'faccode')
87        elif breakdown == 'depcode':
88            row_name = '%s/%s' % (
89                getattr(stud, 'faccode'), getattr(stud, 'depcode'))
90        row = paths.index(row_name)
91        col = states.index(stud.state)
92        table[row][col] += 1
93        table[-1][col] += 1
94        table[row][-1] += 1
95        table[-1][-1] += 1
96    # turn lists into tuples
97    table = tuple([tuple(row) for row in table])
98
99    paths = paths + (u'Total',)
100    return (paths, states, table)
101
102from reportlab.lib import colors
103from reportlab.lib.styles import getSampleStyleSheet
104from reportlab.lib.units import cm
105from reportlab.platypus import Paragraph, Table, Spacer
106from waeup.kofa.reports import IReport, IReportGenerator
107from waeup.kofa.reports import Report
108from waeup.kofa.browser.interfaces import IPDFCreator
109
110STYLE = getSampleStyleSheet()
111
112def tbl_data_to_table(row_names, col_names, data):
113    result = []
114    new_col_names = []
115    for name in col_names:
116        new_col_names.append(name.replace(' ', '\n'))
117    head = [''] + list(new_col_names)
118    result = [head]
119    for idx, row_name in enumerate(row_names):
120        row = [row_name] + list(data[idx])
121        result.append(row)
122    return result
123
124TABLE_STYLE = [
125    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
126    ('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8),
127    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8),
128    ('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8),
129    ('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8),
130    ('ALIGN', (1,1), (-1,-1), 'RIGHT'),
131    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
132    ('LINEBELOW', (0,-1), (-1,-1), 0.25, colors.black),
133    ('LINEAFTER', (-1,0), (-1,-1), 0.25, colors.black),
134    ('LINEBEFORE', (-1,0), (-1,-1), 1.0, colors.black),
135    #('LINEABOVE', (0,-1), (-1,-1), 1.0, colors.black),
136    #('LINEABOVE', (0,0), (-1,0), 0.25, colors.black),
137    ]
138
139@implementer(IStudentStatisticsReport)
140class StudentStatisticsReport(Report):
141    data = None
142    session = None
143    mode = None
144    pdfcreator = 'landscape'
145
146    def __init__(self, session, mode, level, breakdown, author='System'):
147        super(StudentStatisticsReport, self).__init__(
148            args=[session, mode, breakdown], kwargs={'author':author})
149        self.studylevelsource = StudyLevelSource().factory
150        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
151        self.session = academic_sessions_vocab.getTerm(session).title
152        self.mode = mode
153        if level == 0:
154            self.level = ''
155        else:
156            self.level = translate(self.studylevelsource.getTitle(None, int(level)),
157                'waeup.kofa', target_language=self.portal_language)
158        self.breakdown = breakdown
159        self.author = author
160        self.creation_dt_string = self.creation_dt.astimezone(
161            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
162        self.data = get_student_stats(session, mode, level, breakdown)
163
164    def create_pdf(self, job_id):
165        creator = getUtility(IPDFCreator, name=self.pdfcreator)
166        table_data = tbl_data_to_table(*self.data)
167        col_widths = [None,] + [1.6*cm] * len(self.data[1]) + [None,]
168        pdf_data = [Paragraph('<b>%s - Report %s</b>'
169                              % (self.creation_dt_string, job_id),
170                              STYLE["Normal"]),
171                    Spacer(1, 12),]
172        pdf_data += [
173            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
174        if not self.level:
175            doc_title = translate(
176                _('${a} Students - ${b}',
177                mapping = {'a':self.mode, 'b':self.session}))
178            pdf = creator.create_pdf(
179                pdf_data, None, doc_title, self.author, doc_title + '-'
180                )
181        else:
182            doc_title = translate(
183                _('${a} Students - ${b} - ${c}',
184                mapping = {'a':self.mode, 'b': self.level, 'c':self.session}))
185            pdf = creator.create_pdf(
186                pdf_data, None, doc_title, self.author, doc_title + '-'
187                )
188        return pdf
189
190@implementer(IReportGenerator)
191class StudentStatisticsReportGenerator(grok.GlobalUtility):
192
193    title = _('Student Statistics')
194    grok.name('student_stats')
195
196    def generate(
197        self, site, session=None, mode=None,
198        level=None, breakdown=None, author=None):
199        result = StudentStatisticsReport(
200            session=session, mode=mode, level=level,
201            breakdown=breakdown, author=author)
202        return result
203
204###############################################################
205## Browser related stuff
206##
207## XXX: move to local browser module
208###############################################################
209from waeup.kofa.browser.layout import KofaPage
210from waeup.kofa.interfaces import academic_sessions_vocab
211from waeup.kofa.reports import get_generators
212from waeup.kofa.browser.breadcrumbs import Breadcrumb
213grok.templatedir('browser_templates')
214class StudentStatisticsReportGeneratorPage(KofaPage):
215
216    grok.context(StudentStatisticsReportGenerator)
217    grok.name('index.html')
218    grok.require('waeup.handleReports')
219
220    label = _('Create student statistics report')
221
222    @property
223    def generator_name(self):
224        for name, gen in get_generators():
225            if gen == self.context:
226                return name
227        return None
228
229    def update(
230        self, CREATE=None, session=None, mode=None, level=None, breakdown=None):
231        self.parent_url = self.url(self.context.__parent__)
232        self._set_session_values()
233        self._set_mode_values()
234        self._set_level_values()
235        self._set_breakdown_values()
236        if CREATE and session:
237            # create a new report job for students by session and level
238            container = self.context.__parent__
239            user_id = self.request.principal.id
240            kw = dict(
241                session=int(session),
242                mode=mode,
243                level=int(level),
244                breakdown=breakdown)
245            self.flash(_('New report is being created in background'))
246            job_id = container.start_report_job(
247                self.generator_name, user_id, kw=kw)
248            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
249            grok.getSite().logger.info(
250                '%s - report %s created: %s (session=%s, mode=%s, level=%s, breakdown=%s)' % (
251                ob_class, job_id, self.context.title,
252                session, mode, level, breakdown))
253            self.redirect(self.parent_url)
254            return
255        return
256
257    def _set_session_values(self):
258        vocab_terms = academic_sessions_vocab.by_value.values()
259        self.sessions = [(x.title, x.token) for x in vocab_terms]
260        return
261
262    def _set_mode_values(self):
263        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
264        self.modes = sorted([(key, key) for key in mode_groups.keys()])
265        return
266
267    def _set_level_values(self):
268        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
269        studylevelsource = StudyLevelSource().factory
270        self.levels = [(u'All levels', 0)]
271        for code in studylevelsource.getValues(None):
272            title = translate(studylevelsource.getTitle(None, code),
273                'waeup.kofa', target_language=portal_language)
274            self.levels.append((title, code))
275        return
276
277    def _set_breakdown_values(self):
278        self.breakdowns = [('Faculties', 'faccode'), ('Departments', 'depcode')]
279        return
280
281class StudentStatisticsReportPDFView(grok.View):
282
283    grok.context(IStudentStatisticsReport)
284    grok.name('pdf')
285    grok.require('waeup.handleReports')
286    prefix = ''
287
288    def _filename(self):
289        return 'StudentStatisticsReport_%s_%s_%s_%s.pdf' % (
290            self.context.session, self.context.mode, self.context.level,
291            self.context.creation_dt_string)
292
293    def render(self):
294        filename = self._filename().replace(
295            '/', '_').replace(' ','_').replace(':', '-')
296        self.response.setHeader(
297            'Content-Type', 'application/pdf')
298        self.response.setHeader(
299            'Content-Disposition:', 'attachment; filename="%s' % filename)
300        pdf_stream = self.context.create_pdf(self.context.__name__)
301        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
302        grok.getSite().logger.info('%s - report %s downloaded: %s' % (
303            ob_class, self.context.__name__, filename))
304        return pdf_stream
305
306class StudentStatsBreadcrumb(Breadcrumb):
307    """A breadcrumb for reports.
308    """
309    grok.context(StudentStatisticsReportGenerator)
310    title = _(u'Student Statistics')
311    target = None
Note: See TracBrowser for help on using the repository browser.