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

Last change on this file since 9931 was 9927, checked in by uli, 12 years ago

student faccodes of None should not block report generation.

  • Property svn:keywords set to Id
File size: 9.0 KB
RevLine 
[9633]1## $Id: reports.py 9927 2013-02-01 12:38:16Z uli $
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
[9680]21from zope.interface import implementer, Interface, Attribute
[9633]22from waeup.kofa.interfaces import (
[9649]23    IKofaUtils,
24    academic_sessions_vocab, registration_states_vocab)
[9633]25from waeup.kofa.interfaces import MessageFactory as _
26from waeup.kofa.reports import IReport
27
[9653]28class IStudentsReport(IReport):
[9633]29
[9680]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
[9647]34def get_students_by(session, mode):
35    """Get students in a certain session and study mode.
[9633]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()))
[9647]58    fac_codes = fac_codes + (u'Total',)
[9633]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))]
[9649]66    mode_groups = getUtility(IKofaUtils).MODE_GROUPS
[9633]67    for stud in result:
[9649]68        if mode != 'All' and stud.current_mode not in mode_groups[mode]:
[9647]69            continue
[9927]70        if stud.faccode not in fac_codes:
71            # studs can have a faccode ``None``
72            continue
[9633]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 waeup.kofa.reports import IReport, IReportGenerator
88from waeup.kofa.reports import Report
89from waeup.kofa.browser.interfaces import IPDFCreator
90
91STYLE = getSampleStyleSheet()
92
93def tbl_data_to_table(row_names, col_names, data):
94    result = []
95    new_col_names = []
96    for name in col_names:
97        new_col_names.append(name.replace(' ', '\n'))
98    head = [''] + list(new_col_names)
99    result = [head]
100    for idx, row_name in enumerate(row_names):
101        row = [row_name] + list(data[idx])
102        result.append(row)
103    return result
104
105TABLE_STYLE = [
106    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
107    ('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8),
108    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8),
109    ('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8),
110    ('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8),
111    ('ALIGN', (1,1), (-1,-1), 'RIGHT'),
112    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
113    ('LINEBELOW', (0,-1), (-1,-1), 0.25, colors.black),
114    ('LINEAFTER', (-1,0), (-1,-1), 0.25, colors.black),
115    ('LINEBEFORE', (-1,0), (-1,-1), 1.0, colors.black),
116    ('LINEABOVE', (0,-1), (-1,-1), 1.0, colors.black),
117    ('LINEABOVE', (0,0), (-1,0), 0.25, colors.black),
118    ]
119
[9653]120@implementer(IStudentsReport)
121class StudentsReport(Report):
[9633]122    data = None
123    session = None
[9647]124    mode = None
[9633]125
[9647]126    def __init__(self, session, mode, author='System'):
[9653]127        super(StudentsReport, self).__init__(
[9647]128            args=[session, mode], kwargs={'author':author})
129        self.session = academic_sessions_vocab.getTerm(session).title
130        self.mode = mode
[9633]131        self.author = author
[9678]132        self.creation_dt_string = self.creation_dt.astimezone(
[9668]133            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
[9647]134        self.data = get_students_by(session, mode)
[9633]135
136    def create_pdf(self):
137        creator = getUtility(IPDFCreator)
138        table_data = tbl_data_to_table(*self.data)
139        col_widths = [None,] + [1.6*cm] * len(self.data[1]) + [None,]
[9678]140        pdf_data = [Paragraph('<b>%s</b>' % self.creation_dt_string,
[9657]141                              STYLE["Normal"]),
142                    Spacer(1, 12),]
143        pdf_data += [
[9633]144            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
[9657]145        doc_title = '%s Students in Session %s' % (self.mode, self.session)
[9633]146        pdf = creator.create_pdf(
147            pdf_data, None, doc_title, self.author,
[9657]148            'Students in Session %s' % self.session)
[9633]149        return pdf
150
151@implementer(IReportGenerator)
[9653]152class StudentsReportGenerator(grok.GlobalUtility):
[9633]153
[9648]154    title = _('Students')
155    grok.name('students_by')
[9633]156
[9647]157    def generate(self, site, session=None, mode=None, author=None):
[9653]158        result = StudentsReport(session=session, mode=mode, author=author)
[9633]159        return result
160
161###############################################################
162## Browser related stuff
163##
164## XXX: move to local browser module
165###############################################################
166from waeup.kofa.browser.layout import KofaPage
167from waeup.kofa.interfaces import academic_sessions_vocab
168from waeup.kofa.reports import get_generators
169grok.templatedir('browser_templates')
[9653]170class StudentsReportGeneratorPage(KofaPage):
[9633]171
[9653]172    grok.context(StudentsReportGenerator)
[9633]173    grok.name('index.html')
174    grok.require('waeup.manageReports')
175
[9637]176    label = _('Create students report')
177
[9633]178    @property
179    def generator_name(self):
180        for name, gen in get_generators():
181            if gen == self.context:
182                return name
183        return None
184
[9647]185    def update(self, CREATE=None, session=None, mode=None):
[9633]186        self.parent_url = self.url(self.context.__parent__)
187        self._set_session_values()
[9647]188        self._set_mode_values()
[9633]189        if CREATE and session:
190            # create a new report job for students by session
191            container = self.context.__parent__
192            user_id = self.request.principal.id
[9647]193            kw = dict(
194                session=int(session),
195                mode=mode)
[9633]196            self.flash(_('New report is being created in background'))
[9679]197            job_id = container.start_report_job(
[9633]198                self.generator_name, user_id, kw=kw)
[9654]199            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
200            grok.getSite().logger.info(
[9679]201                '%s - report %s created: %s (session=%s, mode=%s)' % (
202                ob_class, job_id, self.context.title, session, mode))
[9633]203            self.redirect(self.parent_url)
204            return
205        return
206
207    def _set_session_values(self):
208        vocab_terms = academic_sessions_vocab.by_value.values()
209        self.sessions = [(x.title, x.token) for x in vocab_terms]
210        return
211
[9647]212    def _set_mode_values(self):
[9649]213        mode_groups = getUtility(IKofaUtils).MODE_GROUPS
214        self.modes = sorted([(key, key) for key in mode_groups.keys()])
[9647]215        return
[9633]216
[9653]217class StudentsReportPDFView(grok.View):
[9633]218
[9653]219    grok.context(IStudentsReport)
[9633]220    grok.name('pdf')
221    grok.require('waeup.Public')
222
223    def render(self):
[9673]224        filename = 'StudentsReport_%s_%s_%s.pdf' % (
225            self.context.session, self.context.mode,
[9678]226            self.context.creation_dt_string)
[9673]227        filename = filename.replace(
228            '/', '_').replace(' ','_').replace(':', '-')
[9633]229        self.response.setHeader(
230            'Content-Type', 'application/pdf')
231        self.response.setHeader(
232            'Content-Disposition:', 'attachment; filename="%s' % filename)
233        pdf_stream = self.context.create_pdf()
[9654]234        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
[9679]235        grok.getSite().logger.info('%s - report %s downloaded: %s' % (
236            ob_class, self.context.__name__, filename))
[9633]237        return pdf_stream
Note: See TracBrowser for help on using the repository browser.