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

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

Show report number (job_id) on report pdf slips.

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