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

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

Show department paths. Department codes are not unique!

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