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

Last change on this file since 10557 was 10555, checked in by Henrik Bettermann, 11 years ago

There are many states. Let's use landscape format.

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