source: main/waeup.kofa/branches/uli-py3/src/waeup/kofa/students/reports/level_report.py

Last change on this file was 15968, checked in by Henrik Bettermann, 5 years ago

Add waeup.local.ReportsOfficer role.

  • Property svn:keywords set to Id
File size: 17.0 KB
Line 
1## $Id: level_report.py 15968 2020-01-30 22:47:56Z henrik $
2##
3## Copyright (C) 2013 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
19import textwrap
20from zope.i18n import translate
21from zope.catalog.interfaces import ICatalog
22from zope.component import queryUtility, getUtility
23from zope.interface import implementer, Interface, Attribute
24from waeup.kofa.interfaces import (
25    IKofaUtils, GRADUATED,
26    academic_sessions_vocab, registration_states_vocab)
27from waeup.kofa.interfaces import MessageFactory as _
28from waeup.kofa.reports import IReport
29from waeup.kofa.utils.helpers import get_user_account
30from waeup.kofa.browser.pdf import get_signature_tables
31from waeup.kofa.students.vocabularies import StudyLevelSource
32from waeup.kofa.students.reports.student_statistics import (
33    StudentStatisticsReportPDFView)
34
35from reportlab.lib import colors
36from reportlab.lib.styles import getSampleStyleSheet
37from reportlab.lib.units import cm
38from reportlab.platypus import Paragraph, Table, Spacer
39from reportlab.platypus.flowables import Flowable
40from waeup.kofa.reports import IReport, IReportGenerator
41from waeup.kofa.reports import Report
42from waeup.kofa.browser.interfaces import IPDFCreator
43from waeup.kofa.browser.pdf import SMALL_PARA_STYLE
44
45STYLE = getSampleStyleSheet()
46
47class TTR(Flowable): #TableTextRotate
48    '''Rotates a text in a table cell.'''
49
50    def __init__(self, text):
51        Flowable.__init__(self)
52        self.text=text
53
54    def draw(self):
55        canvas = self.canv
56        canvas.rotate(45)
57        canvas.drawString( 0, -1, self.text)
58
59    #def wrap(self,aW,aH):
60    #    canv = self.canv
61    #    return canv._leading, canv.stringWidth(self.text)
62
63def tbl_data_to_table(data):
64    result = []
65    col_names = (
66            'S/N',
67            'Matric No.',
68            translate(_('Student Name')),
69            TTR(translate(_('Total Credits Taken'))),
70            TTR(translate(_('Total Credits Passed'))),
71            TTR(translate(_('GPA'))),
72            translate(_('Courses Failed')),
73            translate(_('Outstanding Courses')),
74            TTR(translate(_('Cum. Credits Taken'))),
75            TTR(translate(_('Cum. Credits Passed'))),
76            TTR(translate(_('CGPA'))),
77            TTR(translate(_('Remark'))))
78    table = [col_names]
79    sn = 1
80    for line in data:
81        # Underline mandatory (core) courses
82        failed_courses = Paragraph(
83            line[5].replace(
84                'm_', '<u>').replace('_m', '</u>'), SMALL_PARA_STYLE)
85        line = line[:5] + (failed_courses,) + line[6:]
86        # Superscript in remarks (needed by AAUE)
87        remark = Paragraph(
88            line[10].replace(
89                's_', '<super>').replace('_s', '</super>'), SMALL_PARA_STYLE)
90        line = line[:10] + (remark,)
91        line = (sn,) + line
92        table.append(line)
93        sn += 1
94    return table
95
96TABLE_STYLE = [
97    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
98    ('VALIGN', (0, 1), (-1,-1), 'TOP'),
99    #('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8), # 1st column
100    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8), # 1st row
101    #('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8), # last row
102    #('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8), # last column
103    ('ALIGN', (3,1), (-1,-1), 'RIGHT'),
104    ('ALIGN', (0,0), (0,-1), 'RIGHT'),
105    ('ALIGN', (6,0), (6,-1), 'LEFT'),
106    ('ALIGN', (7,0), (7,-1), 'LEFT'),
107    ('INNERGRID', (0,1), (-1,-1), 0.25, colors.black),
108    ('BOX', (0,1), (-1,-1), 1, colors.black),
109    ]
110
111class ILevelReport(IReport):
112
113    session = Attribute('Session to report')
114    level = Attribute('Level to report')
115    faccode = Attribute('Faculty to report')
116    depcode = Attribute('Department to report')
117    certcode = Attribute('Certificate to report')
118    creation_dt_string = Attribute('Human readable report creation datetime')
119
120@implementer(ILevelReport)
121class LevelReport(Report):
122    data = None
123    session = None
124    level = None
125    faccode = None
126    depcode = None
127    certcode = None
128    pdfcreator = 'landscape'
129    note = None
130    signatures = None
131
132    @property
133    def title(self):
134        return translate(_('Level Report'))
135
136    @property
137    def right_footer(self):
138        return self.title + ' - %s -' % self.session
139
140    def _excluded(self, level_obj):
141        """Some universities may add further conditions to exclude
142        students from reports. These conditions can be customized in
143        this function.
144        """
145        return False
146
147    def _get_students(self, faccode, depcode, certcode, session, level):
148        """Get students in a certain department, studying a certain programmen
149        who registered courses in a certain session at a certain level.
150
151        Returns a list of student data tuples.
152        """
153        site = grok.getSite()
154        cat = queryUtility(ICatalog, name="students_catalog")
155        if certcode == 'all':
156            certcode = None
157        result = cat.searchResults(
158            depcode = (depcode, depcode), faccode = (faccode, faccode),
159            certcode = (certcode, certcode)
160            )
161        table = []
162        format_float = getUtility(IKofaUtils).format_float
163        for stud in result:
164            if not stud['studycourse'].has_key(str(level)):
165                continue
166            if stud.state == GRADUATED:
167                continue
168            level_obj = stud['studycourse'][str(level)]
169            if level_obj.level_session != session:
170                continue
171            if self._excluded(level_obj):
172                continue
173            passed_params = level_obj.passed_params
174            failed_courses = textwrap.fill(passed_params[4], 35)
175            not_taken_courses = textwrap.fill(passed_params[5], 17)
176            fullname = textwrap.fill(stud.display_fullname, 29)
177            # This is a very special column requested by AAUE, Nigeria.
178            # The 'remark' column remains empty in base package.
179            remark = getattr(level_obj, 'remark', '')
180            end_level = getattr(stud['studycourse'].certificate, 'end_level', None)
181            credits_counted = level_obj.gpa_params[1]
182            credits_passed = passed_params[2]
183            level_gpa = format_float(level_obj.gpa_params[0], 3)
184            cum_credits_taken = level_obj.cumulative_params[1]
185            cum_credits_passed = level_obj.cumulative_params[4]
186            if end_level and level >= end_level:
187                cgpa = format_float(level_obj.cumulative_params[0], 2)
188            else:
189                cgpa = format_float(level_obj.cumulative_params[0], 3)
190            if remark in ('FRNS', 'NER', 'NYV'):
191                cgpa = ''
192                if remark in ('NER', 'NYV'):
193                    # hide all data (requestd by AAUE, Nigeria)
194                    credits_counted = ''
195                    credits_passed = ''
196                    level_gpa = ''
197                    failed_courses = ''
198                    not_taken_courses = ''
199                    cum_credits_taken = ''
200                    cum_credits_passed = ''
201            line = (
202                    stud.matric_number,
203                    fullname,
204                    credits_counted,
205                    credits_passed,
206                    level_gpa,
207                    failed_courses,
208                    not_taken_courses,
209                    cum_credits_taken,
210                    cum_credits_passed,
211                    cgpa,
212                    remark,
213                    )
214            table.append(line)
215        return sorted(table, key=lambda value:value[0])
216
217    def __init__(self, faccode, depcode, certcode, session, level,
218                 author='System'):
219        super(LevelReport, self).__init__(
220            args=[faccode, depcode, certcode, session, level],
221            kwargs={'author':author})
222        site = grok.getSite()
223        self.studylevelsource = StudyLevelSource().factory
224        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
225        self.session = academic_sessions_vocab.getTerm(session).title
226        self.levelcode = level
227        self.sessioncode = session
228        self.faccode = faccode
229        self.depcode = depcode
230        self.certcode = certcode
231        self.factitle = site['faculties'][faccode].longtitle
232        self.deptitle = site['faculties'][faccode][depcode].longtitle
233        if self.certcode == 'all':
234            self.certtitle = translate(
235                _('All Certificates'), 'waeup.kofa',
236                target_language=self.portal_language)
237            self.level = translate(
238                self.studylevelsource.getTitle(None, int(level)),
239                'waeup.kofa', target_language=self.portal_language)
240        else:
241            certificate = site[
242                'faculties'][faccode][depcode].certificates[certcode]
243            self.certtitle = certificate.longtitle
244            self.level = translate(
245                self.studylevelsource.getTitle(certificate, int(level)),
246                'waeup.kofa', target_language=self.portal_language)
247        self.author = author
248        self.creation_dt_string = self.creation_dt.astimezone(
249            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
250        self.data = self._get_students(faccode, depcode, certcode, session, level)
251
252    def create_pdf(self, job_id):
253        creator = getUtility(IPDFCreator, name=self.pdfcreator)
254        table_data = tbl_data_to_table(self.data)
255        #col_widths = [3.5*cm] * len(self.data[0])
256        col_widths = [1*cm, 4*cm, 5*cm, 0.8*cm, 0.8*cm, 1*cm,
257                      6*cm, 3*cm, 0.8*cm, 0.8*cm, 1*cm, 2*cm]
258        pdf_data = [Paragraph('<b>%s - Report %s</b>'
259                              % (self.creation_dt_string, job_id),
260                              STYLE["Normal"]),
261                    Spacer(1, 12),]
262        pdf_data += [Paragraph(
263                    translate(
264                        '${a}<br />${b}<br />${c}<br />'
265                        'Session: ${d}<br />Level: ${e}',
266                        mapping = {'a':self.certtitle,
267                                   'b':self.deptitle,
268                                   'c':self.factitle,
269                                   'd':self.session,
270                                   'e':self.level,
271                                   }),
272                    STYLE["Normal"]),
273                    Spacer(1, 12),]
274        pdf_data += [
275            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
276
277        pdf_data.append(Spacer(1, 40))
278        if self.signatures:
279            signaturetables = get_signature_tables(
280                self.signatures, landscape=True)
281            pdf_data.append(signaturetables[0])
282
283        pdf = creator.create_pdf(
284            pdf_data, None, self.title, self.author,
285            self.right_footer, note = self.note
286            )
287        return pdf
288
289@implementer(IReportGenerator)
290class LevelReportGenerator(grok.GlobalUtility):
291
292    title = _('Level Report')
293    grok.name('level_report')
294
295    def generate(self, site, faccode=None, depcode=None, certcode=None,
296                 session=None, level=None, author=None):
297        result = LevelReport(faccode=faccode, depcode=depcode,
298                             certcode=certcode,
299                             session=session, level=level, author=author)
300        return result
301
302###############################################################
303## Browser related stuff
304##
305## XXX: move to local browser module
306###############################################################
307from waeup.kofa.browser.layout import KofaPage
308from waeup.kofa.interfaces import academic_sessions_vocab
309from waeup.kofa.reports import get_generators
310from waeup.kofa.browser.breadcrumbs import Breadcrumb
311grok.templatedir('browser_templates')
312class LevelReportGeneratorPage(KofaPage):
313
314    grok.context(LevelReportGenerator)
315    grok.name('index.html')
316    grok.require('waeup.handleReports')
317
318    label = _('Create level report')
319
320    @property
321    def generator_name(self):
322        for name, gen in get_generators():
323            if gen == self.context:
324                return name
325        return None
326
327    def update(self, CREATE=None, faccode_depcode_certcode=None,
328               session=None, level=None):
329        self.parent_url = self.url(self.context.__parent__)
330        self._set_session_values()
331        self._set_level_values()
332        try:
333            local_roles = get_user_account(self.request).getLocalRoles()
334        except AttributeError:  # Managers have no user account
335            local_roles = {}
336        lror = local_roles.get('waeup.local.ReportsOfficer', None)
337        self._set_faccode_depcode_certcode_values(lror)
338        if not faccode_depcode_certcode:
339            self.flash(_('No certificate selected.'), type="warning")
340            return
341        if CREATE:
342            # create a new report job for students by session
343            faccode = faccode_depcode_certcode.split('_')[0]
344            depcode = faccode_depcode_certcode.split('_')[1]
345            certcode = faccode_depcode_certcode.split('_')[2]
346            container = self.context.__parent__
347            user_id = self.request.principal.id
348            if level:
349                level = int(level)
350            if session:
351                session = int(session)
352            kw = dict(
353                session=session,
354                level=level,
355                faccode=faccode,
356                depcode=depcode,
357                certcode=certcode)
358            self.flash(_('New report is being created in background'))
359            job_id = container.start_report_job(
360                self.generator_name, user_id, kw=kw)
361            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
362            grok.getSite().logger.info(
363                '%s - report %s created: %s '
364                '(faculty=%s, department=%s, certificate=%s, '
365                'session=%s, level=%s)' % (
366                ob_class, job_id, self.context.title, faccode, depcode,
367                certcode, session, level))
368            self.redirect(self.parent_url)
369            return
370        return
371
372    def _set_session_values(self):
373        vocab_terms = academic_sessions_vocab.by_value.values()
374        self.sessions = [(x.title, x.token) for x in vocab_terms]
375        return
376
377    #def _set_level_values(self):
378    #    vocab_terms = course_levels.by_value.values()
379    #    self.levels = sorted([(x.title, x.token) for x in vocab_terms])
380    #    return
381
382    def _set_level_values(self):
383        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
384        studylevelsource = StudyLevelSource().factory
385        self.levels = []
386        for code in studylevelsource.getValues(None):
387            title = translate(studylevelsource.getTitle(None, code),
388                'waeup.kofa', target_language=portal_language)
389            self.levels.append((title, code))
390        return
391
392    def _set_faccode_depcode_certcode_values(self, lror):
393        faccode_depcode_certcode = []
394        faculties = grok.getSite()['faculties']
395        if lror:
396           for dep in lror:
397                faccode_depcode_certcode.append(
398                    (' All certificates -- %s, %s)'
399                     %(dep.longtitle, dep.__parent__.longtitle),
400                     '%s_%s_all'
401                     %(dep.__parent__.code, dep.code)))
402                for cert in dep.certificates.values():
403                    faccode_depcode_certcode.append(
404                        ('%s -- %s, %s)'
405                         %(cert.longtitle, dep.longtitle, dep.__parent__.longtitle),
406                         '%s_%s_%s'
407                         %(dep.__parent__.code, dep.code, cert.code)))
408        else:
409            for fac in faculties.values():
410                for dep in fac.values():
411                    faccode_depcode_certcode.append(
412                        (' All certificates -- %s, %s)'
413                         %(dep.longtitle, fac.longtitle),
414                         '%s_%s_all'
415                         %(fac.code, dep.code)))
416                    for cert in dep.certificates.values():
417                        faccode_depcode_certcode.append(
418                            ('%s -- %s, %s)'
419                             %(cert.longtitle, dep.longtitle, fac.longtitle),
420                             '%s_%s_%s'
421                             %(fac.code, dep.code, cert.code)))
422        self.faccode_depcode_certcode = sorted(
423            faccode_depcode_certcode, key=lambda value: value[0])
424        return
425
426class LevelReportPDFView(StudentStatisticsReportPDFView):
427
428    grok.context(ILevelReport)
429    grok.name('pdf')
430    grok.require('waeup.handleReports')
431
432    def _filename(self):
433        return 'LevelReport_rno%s_%s.pdf' % (
434            self.context.__name__,
435            self.context.creation_dt_string)
Note: See TracBrowser for help on using the repository browser.