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

Last change on this file since 12593 was 12515, checked in by Henrik Bettermann, 10 years ago

Student statistic can now be broken down by faculties or by departments.

  • Property svn:keywords set to Id
File size: 10.2 KB
Line 
1## $Id: student_statistics.py 12515 2015-01-24 18:30:11Z 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.catalog.interfaces import ICatalog
20from zope.component import queryUtility, getUtility
21from zope.interface import implementer, Interface, Attribute
22from waeup.kofa.interfaces import (
23    IKofaUtils,
24    academic_sessions_vocab, registration_states_vocab)
25from waeup.kofa.interfaces import MessageFactory as _
26from waeup.kofa.reports import IReport
27
28class IStudentStatisticsReport(IReport):
29
30    session = Attribute('Session to report')
31    mode = Attribute('Study modes group to report')
32    creation_dt_string = Attribute('Human readable report creation datetime')
33
34def get_student_stats(session, mode, breakdown):
35    """Get students in a certain session and study mode.
36
37    Returns a table ordered by code (faculty or department, one per row) and
38    registration state (cols). The result is a 3-tuple representing
39    ((<CODES>), (<STATES>), (<NUM_OF_STUDENTS>)). The
40    (<NUM_OF_STUDENTS>) is an n-tuple with each entry containing the
41    number of students found in that faculty/department and with the respective
42    state.
43
44    Sample result:
45
46      >>> ((u'FAC1', u'FAC2'),
47      ...  ('created', 'accepted', 'registered'),
48      ...  ((12, 10, 1), (0, 5, 25)))
49
50    This result means: there are 5 students in FAC2 in state
51    'accepted' while 12 students in 'FAC1' are in state 'created'.
52    """
53    site = grok.getSite()
54    states = tuple([x.value for x in registration_states_vocab])
55    states = states + (u'Total',)
56    if breakdown == 'faccode':
57        codes = tuple(sorted([x for x in site['faculties'].keys()],
58                                 key=lambda x: x.lower()))
59    elif breakdown == 'depcode':
60        faculties = site['faculties']
61        depcodes = []
62        for fac in faculties.values():
63            for dep in fac.values():
64                depcodes.append(dep.code)
65        codes = tuple(sorted([x for x in depcodes],
66                                 key=lambda x: x.lower()))
67    codes = codes + (u'Total',)
68    # XXX: Here we do _one_ query and then examine the single
69    #   students. One could also do multiple queries and just look for
70    #   the result length (not introspecting the delivered students
71    #   further).
72    cat = queryUtility(ICatalog, name="students_catalog")
73    result = cat.searchResults(current_session=(session, session))
74    table = [[0 for x in xrange(len(states))] for y in xrange(len(codes))]
75    mode_groups = getUtility(IKofaUtils).MODE_GROUPS
76    for stud in result:
77        if mode != 'All' and stud.current_mode not in mode_groups[mode]:
78            continue
79        if getattr(stud, breakdown) not in codes:
80            # studs can have a faccode ``None``
81            continue
82        row = codes.index(getattr(stud, breakdown))
83        col = states.index(stud.state)
84        table[row][col] += 1
85        table[-1][col] += 1
86        table[row][-1] += 1
87        table[-1][-1] += 1
88    # turn lists into tuples
89    table = tuple([tuple(row) for row in table])
90    return (codes, states, table)
91
92from reportlab.lib import colors
93from reportlab.lib.styles import getSampleStyleSheet
94from reportlab.lib.units import cm
95from reportlab.platypus import Paragraph, Table, Spacer
96from waeup.kofa.reports import IReport, IReportGenerator
97from waeup.kofa.reports import Report
98from waeup.kofa.browser.interfaces import IPDFCreator
99
100STYLE = getSampleStyleSheet()
101
102def tbl_data_to_table(row_names, col_names, data):
103    result = []
104    new_col_names = []
105    for name in col_names:
106        new_col_names.append(name.replace(' ', '\n'))
107    head = [''] + list(new_col_names)
108    result = [head]
109    for idx, row_name in enumerate(row_names):
110        row = [row_name] + list(data[idx])
111        result.append(row)
112    return result
113
114TABLE_STYLE = [
115    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
116    ('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8),
117    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8),
118    ('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8),
119    ('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8),
120    ('ALIGN', (1,1), (-1,-1), 'RIGHT'),
121    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
122    ('LINEBELOW', (0,-1), (-1,-1), 0.25, colors.black),
123    ('LINEAFTER', (-1,0), (-1,-1), 0.25, colors.black),
124    ('LINEBEFORE', (-1,0), (-1,-1), 1.0, colors.black),
125    #('LINEABOVE', (0,-1), (-1,-1), 1.0, colors.black),
126    #('LINEABOVE', (0,0), (-1,0), 0.25, colors.black),
127    ]
128
129@implementer(IStudentStatisticsReport)
130class StudentStatisticsReport(Report):
131    data = None
132    session = None
133    mode = None
134
135    def __init__(self, session, mode, breakdown, author='System'):
136        super(StudentStatisticsReport, self).__init__(
137            args=[session, mode, breakdown], kwargs={'author':author})
138        self.session = academic_sessions_vocab.getTerm(session).title
139        self.mode = mode
140        self.breakdown = breakdown
141        self.author = author
142        self.creation_dt_string = self.creation_dt.astimezone(
143            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
144        self.data = get_student_stats(session, mode, breakdown)
145
146    def create_pdf(self):
147        creator = getUtility(IPDFCreator, name='landscape')
148        table_data = tbl_data_to_table(*self.data)
149        col_widths = [None,] + [1.6*cm] * len(self.data[1]) + [None,]
150        pdf_data = [Paragraph('<b>%s</b>' % self.creation_dt_string,
151                              STYLE["Normal"]),
152                    Spacer(1, 12),]
153        pdf_data += [
154            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
155        doc_title = '%s Students in Session %s' % (self.mode, self.session)
156        pdf = creator.create_pdf(
157            pdf_data, None, doc_title, self.author,
158            'Students in Session %s' % self.session
159            )
160        return pdf
161
162@implementer(IReportGenerator)
163class StudentStatisticsReportGenerator(grok.GlobalUtility):
164
165    title = _('Student Statistics')
166    grok.name('student_stats')
167
168    def generate(self, site, session=None, mode=None, breakdown=None, author=None):
169        result = StudentStatisticsReport(
170            session=session, mode=mode, breakdown=breakdown, author=author)
171        return result
172
173###############################################################
174## Browser related stuff
175##
176## XXX: move to local browser module
177###############################################################
178from waeup.kofa.browser.layout import KofaPage
179from waeup.kofa.interfaces import academic_sessions_vocab
180from waeup.kofa.reports import get_generators
181from waeup.kofa.browser.breadcrumbs import Breadcrumb
182grok.templatedir('browser_templates')
183class StudentStatisticsReportGeneratorPage(KofaPage):
184
185    grok.context(StudentStatisticsReportGenerator)
186    grok.name('index.html')
187    grok.require('waeup.manageReports')
188
189    label = _('Create student statistics report')
190
191    @property
192    def generator_name(self):
193        for name, gen in get_generators():
194            if gen == self.context:
195                return name
196        return None
197
198    def update(self, CREATE=None, session=None, mode=None, breakdown=None):
199        self.parent_url = self.url(self.context.__parent__)
200        self._set_session_values()
201        self._set_mode_values()
202        self._set_breakdown_values()
203        if CREATE and session:
204            # create a new report job for students by session
205            container = self.context.__parent__
206            user_id = self.request.principal.id
207            kw = dict(
208                session=int(session),
209                mode=mode,
210                breakdown=breakdown)
211            self.flash(_('New report is being created in background'))
212            job_id = container.start_report_job(
213                self.generator_name, user_id, kw=kw)
214            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
215            grok.getSite().logger.info(
216                '%s - report %s created: %s (session=%s, mode=%s, breakdown=%s)' % (
217                ob_class, job_id, self.context.title, session, mode, breakdown))
218            self.redirect(self.parent_url)
219            return
220        return
221
222    def _set_session_values(self):
223        vocab_terms = academic_sessions_vocab.by_value.values()
224        self.sessions = [(x.title, x.token) for x in vocab_terms]
225        return
226
227    def _set_mode_values(self):
228        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
229        self.modes = sorted([(key, key) for key in mode_groups.keys()])
230        return
231
232    def _set_breakdown_values(self):
233        self.breakdowns = [('Faculties', 'faccode'), ('Departments', 'depcode')]
234        return
235
236class StudentStatisticsReportPDFView(grok.View):
237
238    grok.context(IStudentStatisticsReport)
239    grok.name('pdf')
240    grok.require('waeup.Public')
241    prefix = ''
242
243    def _filename(self):
244        return 'StudentStatisticsReport_%s_%s_%s.pdf' % (
245            self.context.session, self.context.mode,
246            self.context.creation_dt_string)
247
248    def render(self):
249        filename = self._filename().replace(
250            '/', '_').replace(' ','_').replace(':', '-')
251        self.response.setHeader(
252            'Content-Type', 'application/pdf')
253        self.response.setHeader(
254            'Content-Disposition:', 'attachment; filename="%s' % filename)
255        pdf_stream = self.context.create_pdf()
256        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
257        grok.getSite().logger.info('%s - report %s downloaded: %s' % (
258            ob_class, self.context.__name__, filename))
259        return pdf_stream
260
261class StudentStatsBreadcrumb(Breadcrumb):
262    """A breadcrumb for reports.
263    """
264    grok.context(StudentStatisticsReportGenerator)
265    title = _(u'Student Statistics')
266    target = None
Note: See TracBrowser for help on using the repository browser.