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

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