source: main/waeup.aaue/trunk/src/waeup/aaue/students/reports/student_level_statistics.py

Last change on this file was 14626, checked in by Henrik Bettermann, 8 years ago

We need a marker interface for GradLevelReports?.

  • Property svn:keywords set to Id
File size: 11.5 KB
Line 
1## $Id: student_level_statistics.py 14626 2017-03-14 16:58:34Z 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.reports.student_statistics import (
27    StudentStatisticsReportPDFView)
28from waeup.kofa.students.vocabularies import StudyLevelSource
29from waeup.kofa.interfaces import MessageFactory as _
30from waeup.kofa.reports import IReport
31
32class IStudentLevelStatisticsReport(IReport):
33
34    session = Attribute('Session to report')
35    creation_dt_string = Attribute('Human readable report creation datetime')
36    mode = Attribute('Study modes group to report')
37
38def get_student_stats(session, mode, breakdown):
39    """Get students in a certain session and study mode.
40
41    Returns a table ordered by code (faculty or department, one per row) and
42    current level (cols). The result is a 3-tuple representing
43    ((<PATHS>), (<LEVELS>), (<NUM_OF_STUDENTS>)). The
44    (<NUM_OF_STUDENTS>) is an n-tuple with each entry containing the
45    number of students found in that faculty/department and with the respective
46    current level.
47
48    Sample result:
49
50      >>> ((u'FAC1', u'FAC2'),
51      ...  ('100', '200', '300'),
52      ...  ((12, 10, 1), (0, 5, 25)))
53
54    This result means: there are 5 students in FAC2 at level
55    200 while 12 students in FAC1 are at level 100.
56    """
57    site = grok.getSite()
58    levels = (100, 200, 300, 400, 500)
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(2 * len(levels) + 2)]
77             for y in xrange(len(paths)+1)]
78    mode_groups = getUtility(IKofaUtils).MODE_GROUPS
79    for stud in result:
80        if mode != 'All' and stud.current_mode \
81            and stud.current_mode not in mode_groups[mode]:
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        normalized_level = 100*(stud.current_level/100)
92        if stud.current_level and stud.current_level in range(100, 500, 10):
93            if stud.sex == 'f':
94                col = 2 * levels.index(normalized_level) + 1
95                total_col = -1
96            elif stud.sex == 'm':
97                col = 2 * levels.index(normalized_level)
98                total_col = -2
99            else:
100                continue
101        else:
102            continue
103        table[row][col] += 1
104        table[-1][col] += 1
105        table[row][total_col] += 1
106        table[-1][total_col] += 1
107    # turn lists into tuples
108    table = tuple([tuple(row) for row in table])
109    levels = (u'100 male', u'100 female',
110              u'200 male', u'200 female',
111              u'300 male', u'300 female',
112              u'400 male', u'400 female',
113              u'500 male', u'500 female',
114              u'Total male', u'Total female')
115    paths = paths + (u'Total',)
116    return (paths, levels, table)
117
118from reportlab.lib import colors
119from reportlab.lib.styles import getSampleStyleSheet
120from reportlab.lib.units import cm
121from reportlab.platypus import Paragraph, Table, Spacer
122from waeup.kofa.reports import IReport, IReportGenerator
123from waeup.kofa.reports import Report
124from waeup.kofa.browser.interfaces import IPDFCreator
125
126STYLE = getSampleStyleSheet()
127
128def tbl_data_to_table(row_names, col_names, data):
129    result = []
130    head = [''] + list(col_names)
131    result = [head]
132    for idx, row_name in enumerate(row_names):
133        row = [row_name] + list(data[idx])
134        result.append(row)
135    return result
136
137TABLE_STYLE = [
138    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
139    ('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8),
140    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8),
141    ('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8),
142    #('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8),
143    ('ALIGN', (1,1), (-1,-1), 'RIGHT'),
144    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
145    ('LINEBELOW', (0,-1), (-1,-1), 0.25, colors.black),
146
147    ('LINEAFTER', (-1,0), (-1,-1), 1.0, colors.black),
148    ('LINEBEFORE', (-2,0), (-2,-1), 1.0, colors.black),
149    ('LINEBEFORE', (-4,0), (-4,-1), 1.0, colors.black),
150    ('LINEBEFORE', (-6,0), (-6,-1), 1.0, colors.black),
151    ('LINEBEFORE', (-8,0), (-8,-1), 1.0, colors.black),
152    ('LINEBEFORE', (-10,0), (-10,-1), 1.0, colors.black),
153    ('LINEBEFORE', (-12,0), (-12,-1), 1.0, colors.black),
154
155    ('LINEABOVE', (0,-1), (-1,-1), 1.0, colors.black),
156    #('LINEABOVE', (0,0), (-1,0), 0.25, colors.black),
157    ]
158
159@implementer(IStudentLevelStatisticsReport)
160class StudentLevelStatisticsReport(Report):
161    data = None
162    session = None
163    mode = None
164
165    def __init__(self, session, mode, breakdown, author='System'):
166        super(StudentLevelStatisticsReport, self).__init__(
167            args=[session, mode, breakdown], kwargs={'author':author})
168        self.studylevelsource = StudyLevelSource().factory
169        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
170        self.session = academic_sessions_vocab.getTerm(session).title
171        self.breakdown = breakdown
172        self.author = author
173        self.mode = mode
174        self.creation_dt_string = self.creation_dt.astimezone(
175            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
176        self.data = get_student_stats(session, mode, breakdown)
177
178    def create_pdf(self, job_id):
179        creator = getUtility(IPDFCreator, name='landscape')
180        table_data = tbl_data_to_table(*self.data)
181        col_widths = [None,] + [1.8*cm] * (
182            len(self.data[1]) - 1)+ [2.0*cm] + [None,]
183        pdf_data = [Paragraph('<b>%s - Report %s</b>'
184                              % (self.creation_dt_string, job_id),
185                              STYLE["Normal"]),
186                    Spacer(1, 12),]
187        pdf_data += [Paragraph(
188                    translate(
189                        'Study Mode: ${a}<br />'
190                        'Academic Session: ${b}',
191                        mapping = {'a':self.mode,
192                                   'b':self.session,
193                                   }),
194                    STYLE["Normal"]),
195                    Spacer(1, 12),]
196        pdf_data += [
197            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
198        doc_title = translate(_('Student Level Statistics'))
199        pdf = creator.create_pdf(
200            pdf_data, None, doc_title, self.author,
201            doc_title + ' - %s -' % self.session,
202            )
203        return pdf
204
205@implementer(IReportGenerator)
206class StudentLevelStatisticsReportGenerator(grok.GlobalUtility):
207
208    title = _('Student Level Statistics')
209    grok.name('student_level_stats')
210
211    def generate(
212        self, site, session=None, mode=None,
213        breakdown=None, author=None):
214        result = StudentLevelStatisticsReport(
215            session=session, mode=mode,
216            breakdown=breakdown, author=author)
217        return result
218
219###############################################################
220## Browser related stuff
221##
222## XXX: move to local browser module
223###############################################################
224from waeup.kofa.browser.layout import KofaPage
225from waeup.kofa.reports import get_generators
226from waeup.kofa.browser.breadcrumbs import Breadcrumb
227grok.templatedir('browser_templates')
228class StudentLevelStatisticsReportGeneratorPage(KofaPage):
229
230    grok.context(StudentLevelStatisticsReportGenerator)
231    grok.name('index.html')
232    grok.require('waeup.handleReports')
233
234    label = _('Create student level statistics report')
235
236    @property
237    def generator_name(self):
238        for name, gen in get_generators():
239            if gen == self.context:
240                return name
241        return None
242
243    def update(
244        self, CREATE=None, session=None, mode=None, breakdown=None):
245        self.parent_url = self.url(self.context.__parent__)
246        self._set_session_values()
247        self._set_mode_values()
248        self._set_breakdown_values()
249        if CREATE and session:
250            # create a new report job for students by session and level
251            container = self.context.__parent__
252            user_id = self.request.principal.id
253            kw = dict(
254                session=int(session),
255                mode=mode,
256                breakdown=breakdown)
257            self.flash(_('New report is being created in background'))
258            job_id = container.start_report_job(
259                self.generator_name, user_id, kw=kw)
260            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
261            grok.getSite().logger.info(
262                '%s - report %s created: %s '
263                '(session=%s, mode=%s, breakdown=%s)' % (
264                ob_class, job_id, self.context.title,
265                session, mode, breakdown))
266            self.redirect(self.parent_url)
267            return
268        return
269
270    def _set_session_values(self):
271        vocab_terms = academic_sessions_vocab.by_value.values()
272        self.sessions = [(x.title, x.token) for x in vocab_terms]
273        return
274
275    def _set_mode_values(self):
276        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
277        self.modes = sorted([(key, key) for key in mode_groups.keys()])
278        return
279
280    def _set_breakdown_values(self):
281        self.breakdowns = [('Faculties', 'faccode'), ('Departments', 'depcode')]
282        return
283
284class StudentLevelStatisticsReportPDFView(StudentStatisticsReportPDFView):
285
286    grok.context(IStudentLevelStatisticsReport)
287    grok.name('pdf')
288    grok.require('waeup.handleReports')
289
290    def _filename(self):
291        return 'StudentLevelStatisticsReport_rno%s_%s.pdf' % (
292            self.context.__name__,
293            self.context.creation_dt_string)
294
295class StudentLevelStatsBreadcrumb(Breadcrumb):
296    """A breadcrumb for reports.
297    """
298    grok.context(StudentLevelStatisticsReportGenerator)
299    title = _(u'Student Level Statistics')
300    target = None
Note: See TracBrowser for help on using the repository browser.