source: main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py @ 10704

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

post_text can be the note.

  • Property svn:keywords set to Id
File size: 34.5 KB
RevLine 
[7191]1## $Id: utils.py 10702 2013-11-06 07:57:08Z henrik $
2##
3## Copyright (C) 2011 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##
[7358]18"""General helper functions and utilities for the student section.
[6651]19"""
[7150]20import grok
[8595]21from time import time
[7318]22from reportlab.lib import colors
[7019]23from reportlab.lib.units import cm
24from reportlab.lib.pagesizes import A4
[9015]25from reportlab.lib.styles import getSampleStyleSheet
26from reportlab.platypus import Paragraph, Image, Table, Spacer
[9922]27from zope.schema.interfaces import ConstraintNotSatisfied
[9015]28from zope.component import getUtility, createObject
[7019]29from zope.formlib.form import setUpEditWidgets
[9015]30from zope.i18n import translate
[8596]31from waeup.kofa.interfaces import (
[9762]32    IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED,
33    academic_sessions_vocab)
[7811]34from waeup.kofa.interfaces import MessageFactory as _
35from waeup.kofa.students.interfaces import IStudentsUtils
[9910]36from waeup.kofa.browser.pdf import (
[9965]37    ENTRY1_STYLE, format_html, NOTE_STYLE, HEADING_STYLE,
38    get_signature_tables)
[9910]39from waeup.kofa.browser.interfaces import IPDFCreator
[10256]40from waeup.kofa.utils.helpers import to_timezone
[6651]41
[7318]42SLIP_STYLE = [
43    ('VALIGN',(0,0),(-1,-1),'TOP'),
44    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
45    ]
[7019]46
[7318]47CONTENT_STYLE = [
48    ('VALIGN',(0,0),(-1,-1),'TOP'),
49    #('FONT', (0,0), (-1,-1), 'Helvetica', 8),
50    #('TEXTCOLOR',(0,0),(-1,0),colors.white),
[9906]51    #('BACKGROUND',(0,0),(-1,0),colors.black),
52    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
53    ('BOX', (0,0), (-1,-1), 1, colors.black),
54
[7318]55    ]
[7304]56
[7318]57FONT_SIZE = 10
58FONT_COLOR = 'black'
59
[8112]60def trans(text, lang):
61    # shortcut
62    return translate(text, 'waeup.kofa', target_language=lang)
63
[10261]64def formatted_text(text, color=FONT_COLOR, lang='en'):
[7511]65    """Turn `text`, `color` and `size` into an HTML snippet.
[7318]66
[7511]67    The snippet is suitable for use with reportlab and generating PDFs.
68    Wraps the `text` into a ``<font>`` tag with passed attributes.
69
70    Also non-strings are converted. Raw strings are expected to be
71    utf-8 encoded (usually the case for widgets etc.).
72
[7804]73    Finally, a br tag is added if widgets contain div tags
74    which are not supported by reportlab.
75
[7511]76    The returned snippet is unicode type.
77    """
78    if not isinstance(text, unicode):
79        if isinstance(text, basestring):
80            text = text.decode('utf-8')
81        else:
82            text = unicode(text)
[9717]83    if text == 'None':
84        text = ''
[8141]85    # Mainly for boolean values we need our customized
86    # localisation of the zope domain
[10261]87    text = translate(text, 'zope', target_language=lang)
[7804]88    text = text.replace('</div>', '<br /></div>')
[9910]89    tag1 = u'<font color="%s">' % (color)
[7511]90    return tag1 + u'%s</font>' % text
91
[8481]92def generate_student_id():
[8410]93    students = grok.getSite()['students']
94    new_id = students.unique_student_id
95    return new_id
[6742]96
[7186]97def set_up_widgets(view, ignore_request=False):
[7019]98    view.adapters = {}
99    view.widgets = setUpEditWidgets(
100        view.form_fields, view.prefix, view.context, view.request,
101        adapters=view.adapters, for_display=True,
102        ignore_request=ignore_request
103        )
104
[10261]105def render_student_data(studentview, omit_fields=(), lang='en'):
[7318]106    """Render student table for an existing frame.
107    """
108    width, height = A4
[7186]109    set_up_widgets(studentview, ignore_request=True)
[7318]110    data_left = []
111    data_right = []
[7019]112    style = getSampleStyleSheet()
[7280]113    img = getUtility(IExtFileStore).getFileByContext(
114        studentview.context, attr='passport.jpg')
115    if img is None:
[7811]116        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
[7280]117        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]118    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
119    data_left.append([doc_img])
120    #data.append([Spacer(1, 12)])
[9141]121
[10261]122    f_label = trans(_('Name:'), lang)
[9910]123    f_label = Paragraph(f_label, ENTRY1_STYLE)
[9911]124    f_text = formatted_text(studentview.context.display_fullname)
[9910]125    f_text = Paragraph(f_text, ENTRY1_STYLE)
[9141]126    data_right.append([f_label,f_text])
127
[7019]128    for widget in studentview.widgets:
[9141]129        if 'name' in widget.name:
[7019]130            continue
[9911]131        f_label = translate(
[7811]132            widget.label.strip(), 'waeup.kofa',
[10261]133            target_language=lang)
[9911]134        f_label = Paragraph('%s:' % f_label, ENTRY1_STYLE)
[10261]135        f_text = formatted_text(widget(), lang=lang)
[9910]136        f_text = Paragraph(f_text, ENTRY1_STYLE)
[7318]137        data_right.append([f_label,f_text])
[9141]138
[9452]139    if getattr(studentview.context, 'certcode', None):
[10250]140        if not 'certificate' in omit_fields:
[10261]141            f_label = trans(_('Study Course:'), lang)
[10250]142            f_label = Paragraph(f_label, ENTRY1_STYLE)
143            f_text = formatted_text(
[10650]144                studentview.context['studycourse'].certificate.longtitle)
[10250]145            f_text = Paragraph(f_text, ENTRY1_STYLE)
146            data_right.append([f_label,f_text])
147        if not 'department' in omit_fields:
[10261]148            f_label = trans(_('Department:'), lang)
[10250]149            f_label = Paragraph(f_label, ENTRY1_STYLE)
150            f_text = formatted_text(
151                studentview.context[
[10650]152                'studycourse'].certificate.__parent__.__parent__.longtitle,
[10250]153                )
154            f_text = Paragraph(f_text, ENTRY1_STYLE)
155            data_right.append([f_label,f_text])
156        if not 'faculty' in omit_fields:
[10261]157            f_label = trans(_('Faculty:'), lang)
[10250]158            f_label = Paragraph(f_label, ENTRY1_STYLE)
159            f_text = formatted_text(
160                studentview.context[
[10650]161                'studycourse'].certificate.__parent__.__parent__.__parent__.longtitle,
[10250]162                )
163            f_text = Paragraph(f_text, ENTRY1_STYLE)
164            data_right.append([f_label,f_text])
[10688]165        if not 'current_mode' in omit_fields:
166            studymodes_dict = getUtility(IKofaUtils).STUDY_MODES_DICT
167            sm = studymodes_dict[studentview.context[
168                'studycourse'].certificate.study_mode]
169            f_label = trans(_('Study Mode:'), lang)
170            f_label = Paragraph(f_label, ENTRY1_STYLE)
171            f_text = formatted_text(sm)
172            f_text = Paragraph(f_text, ENTRY1_STYLE)
173            data_right.append([f_label,f_text])
[10250]174        if not 'entry_session' in omit_fields:
[10261]175            f_label = trans(_('Entry Session:'), lang)
[10250]176            f_label = Paragraph(f_label, ENTRY1_STYLE)
177            entry_session = studentview.context['studycourse'].entry_session
178            entry_session = academic_sessions_vocab.getTerm(entry_session).title
179            f_text = formatted_text(entry_session)
180            f_text = Paragraph(f_text, ENTRY1_STYLE)
181            data_right.append([f_label,f_text])
[10256]182        if not 'date_of_birth' in omit_fields:
[10261]183            f_label = trans(_('Date of Birth:'), lang)
[10256]184            f_label = Paragraph(f_label, ENTRY1_STYLE)
185            date_of_birth = studentview.context.date_of_birth
186            tz = getUtility(IKofaUtils).tzinfo
187            date_of_birth = to_timezone(date_of_birth, tz)
188            if date_of_birth is not None:
189                date_of_birth = date_of_birth.strftime("%d/%m/%Y")
190            f_text = formatted_text(date_of_birth)
191            f_text = Paragraph(f_text, ENTRY1_STYLE)
192            data_right.append([f_label,f_text])
[9141]193
[7318]194    table_left = Table(data_left,style=SLIP_STYLE)
[8112]195    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
[7318]196    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]197    return table
198
[10261]199def render_table_data(tableheader, tabledata, lang='en'):
[7318]200    """Render children table for an existing frame.
201    """
[7304]202    data = []
[7318]203    #data.append([Spacer(1, 12)])
[7304]204    line = []
205    style = getSampleStyleSheet()
206    for element in tableheader:
[10261]207        field = '<strong>%s</strong>' % formatted_text(element[0], lang=lang)
[7310]208        field = Paragraph(field, style["Normal"])
[7304]209        line.append(field)
210    data.append(line)
211    for ticket in tabledata:
212        line = []
213        for element in tableheader:
[7511]214              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]215              field = Paragraph(field, style["Normal"])
[7304]216              line.append(field)
217        data.append(line)
[7310]218    table = Table(data,colWidths=[
[7318]219        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]220    return table
221
[10261]222def render_transcript_data(view, tableheader, levels_data, lang='en'):
[10250]223    """Render children table for an existing frame.
224    """
225    data = []
226    style = getSampleStyleSheet()
227    for level in levels_data:
228        level_obj = level['level']
[10251]229        tickets = level['tickets_1'] + level['tickets_2'] + level['tickets_3']
230        headerline = []
231        tabledata = []
[10261]232        subheader = '%s %s, %s %s' % (
233            trans(_('Session'), lang),
[10250]234            view.session_dict[level_obj.level_session],
[10261]235            trans(_('Level'), lang),
[10266]236            view.level_dict[level_obj.level])
[10250]237        data.append(Paragraph(subheader, HEADING_STYLE))
238        for element in tableheader:
239            field = '<strong>%s</strong>' % formatted_text(element[0])
240            field = Paragraph(field, style["Normal"])
[10251]241            headerline.append(field)
242        tabledata.append(headerline)
[10250]243        for ticket in tickets:
[10251]244            ticketline = []
[10250]245            for element in tableheader:
246                  field = formatted_text(getattr(ticket,element[1],u' '))
247                  field = Paragraph(field, style["Normal"])
[10251]248                  ticketline.append(field)
249            tabledata.append(ticketline)
[10250]250        table = Table(tabledata,colWidths=[
251            element[2]*cm for element in tableheader], style=CONTENT_STYLE)
252        data.append(table)
[10479]253        sgpa = '%s: %s' % (trans('Sessional GPA (rectified)', lang), level['sgpa'])
[10261]254        data.append(Paragraph(sgpa, style["Normal"]))
[10250]255    return data
256
[8112]257def docs_as_flowables(view, lang='en'):
258    """Create reportlab flowables out of scanned docs.
259    """
260    # XXX: fix circular import problem
261    from waeup.kofa.students.viewlets import FileManager
262    from waeup.kofa.browser import DEFAULT_IMAGE_PATH
263    style = getSampleStyleSheet()
264    data = []
[7318]265
[8112]266    # Collect viewlets
267    fm = FileManager(view.context, view.request, view)
268    fm.update()
269    if fm.viewlets:
270        sc_translation = trans(_('Scanned Documents'), lang)
[9910]271        data.append(Paragraph(sc_translation, HEADING_STYLE))
[8112]272        # Insert list of scanned documents
273        table_data = []
274        for viewlet in fm.viewlets:
[10020]275            if viewlet.file_exists:
276                # Show viewlet only if file exists
277                f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE)
278                img_path = getattr(getUtility(IExtFileStore).getFileByContext(
279                    view.context, attr=viewlet.download_name), 'name', None)
280                #f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE)
281                if img_path is None:
282                    pass
283                elif not img_path[-4:] in ('.jpg', '.JPG'):
284                    # reportlab requires jpg images, I think.
285                    f_text = Paragraph('%s (not displayable)' % (
286                        viewlet.title,), ENTRY1_STYLE)
287                else:
288                    f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound')
289                table_data.append([f_label, f_text])
[8112]290        if table_data:
291            # safety belt; empty tables lead to problems.
292            data.append(Table(table_data, style=SLIP_STYLE))
293    return data
294
[7150]295class StudentsUtils(grok.GlobalUtility):
296    """A collection of methods subject to customization.
297    """
298    grok.implements(IStudentsUtils)
[7019]299
[8268]300    def getReturningData(self, student):
[9005]301        """ Define what happens after school fee payment
[7841]302        depending on the student's senate verdict.
303
304        In the base configuration current level is always increased
305        by 100 no matter which verdict has been assigned.
306        """
[8268]307        new_level = student['studycourse'].current_level + 100
308        new_session = student['studycourse'].current_session + 1
309        return new_session, new_level
310
311    def setReturningData(self, student):
[9005]312        """ Define what happens after school fee payment
313        depending on the student's senate verdict.
[8268]314
[9005]315        This method folllows the same algorithm as getReturningData but
316        it also sets the new values.
[8268]317        """
318        new_session, new_level = self.getReturningData(student)
[9922]319        try:
320            student['studycourse'].current_level = new_level
321        except ConstraintNotSatisfied:
322            # Do not change level if level exceeds the
323            # certificate's end_level.
324            pass
[8268]325        student['studycourse'].current_session = new_session
[7615]326        verdict = student['studycourse'].current_verdict
[8820]327        student['studycourse'].current_verdict = '0'
[7615]328        student['studycourse'].previous_verdict = verdict
329        return
330
[9519]331    def _getSessionConfiguration(self, session):
332        try:
333            return grok.getSite()['configuration'][str(session)]
334        except KeyError:
335            return None
336
[9148]337    def setPaymentDetails(self, category, student,
[9151]338            previous_session, previous_level):
[8595]339        """Create Payment object and set the payment data of a student for
340        the payment category specified.
341
[7841]342        """
[8595]343        p_item = u''
344        amount = 0.0
[9148]345        if previous_session:
[9517]346            if previous_session < student['studycourse'].entry_session:
347                return _('The previous session must not fall below '
348                         'your entry session.'), None
349            if category == 'schoolfee':
350                # School fee is always paid for the following session
351                if previous_session > student['studycourse'].current_session:
352                    return _('This is not a previous session.'), None
353            else:
354                if previous_session > student['studycourse'].current_session - 1:
355                    return _('This is not a previous session.'), None
[9148]356            p_session = previous_session
357            p_level = previous_level
358            p_current = False
359        else:
360            p_session = student['studycourse'].current_session
361            p_level = student['studycourse'].current_level
362            p_current = True
[9519]363        academic_session = self._getSessionConfiguration(p_session)
364        if academic_session == None:
[8595]365            return _(u'Session configuration object is not available.'), None
[9521]366        # Determine fee.
[7150]367        if category == 'schoolfee':
[8595]368            try:
[8596]369                certificate = student['studycourse'].certificate
370                p_item = certificate.code
[8595]371            except (AttributeError, TypeError):
372                return _('Study course data are incomplete.'), None
[9148]373            if previous_session:
[9916]374                # Students can pay for previous sessions in all
375                # workflow states.  Fresh students are excluded by the
376                # update method of the PreviousPaymentAddFormPage.
[9148]377                if previous_level == 100:
378                    amount = getattr(certificate, 'school_fee_1', 0.0)
379                else:
380                    amount = getattr(certificate, 'school_fee_2', 0.0)
381            else:
382                if student.state == CLEARED:
383                    amount = getattr(certificate, 'school_fee_1', 0.0)
384                elif student.state == RETURNING:
[9916]385                    # In case of returning school fee payment the
386                    # payment session and level contain the values of
387                    # the session the student has paid for. Payment
388                    # session is always next session.
[9148]389                    p_session, p_level = self.getReturningData(student)
[9519]390                    academic_session = self._getSessionConfiguration(p_session)
391                    if academic_session == None:
[9916]392                        return _(
393                            u'Session configuration object is not available.'
394                            ), None
[9148]395                    amount = getattr(certificate, 'school_fee_2', 0.0)
396                elif student.is_postgrad and student.state == PAID:
[9916]397                    # Returning postgraduate students also pay for the
398                    # next session but their level always remains the
399                    # same.
[9148]400                    p_session += 1
[9519]401                    academic_session = self._getSessionConfiguration(p_session)
402                    if academic_session == None:
[9916]403                        return _(
404                            u'Session configuration object is not available.'
405                            ), None
[9148]406                    amount = getattr(certificate, 'school_fee_2', 0.0)
[7150]407        elif category == 'clearance':
[9178]408            try:
409                p_item = student['studycourse'].certificate.code
410            except (AttributeError, TypeError):
411                return _('Study course data are incomplete.'), None
[8595]412            amount = academic_session.clearance_fee
[7150]413        elif category == 'bed_allocation':
[8595]414            p_item = self.getAccommodationDetails(student)['bt']
415            amount = academic_session.booking_fee
[9423]416        elif category == 'hostel_maintenance':
[10681]417            amount = 0.0
[9429]418            bedticket = student['accommodation'].get(
419                str(student.current_session), None)
420            if bedticket:
421                p_item = bedticket.bed_coordinates
[10681]422                if bedticket.bed.__parent__.maint_fee > 0:
423                    amount = bedticket.bed.__parent__.maint_fee
424                else:
425                    # fallback
426                    amount = academic_session.maint_fee
[9429]427            else:
428                # Should not happen because this is already checked
429                # in the browser module, but anyway ...
430                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
431                p_item = trans(_('no bed allocated'), portal_language)
[10449]432        elif category == 'transcript':
433            amount = academic_session.transcript_fee
[8595]434        if amount in (0.0, None):
[9517]435            return _('Amount could not be determined.'), None
[8595]436        for key in student['payments'].keys():
437            ticket = student['payments'][key]
438            if ticket.p_state == 'paid' and\
439               ticket.p_category == category and \
440               ticket.p_item == p_item and \
441               ticket.p_session == p_session:
[9517]442                  return _('This type of payment has already been made.'), None
[8708]443        payment = createObject(u'waeup.StudentOnlinePayment')
[8951]444        timestamp = ("%d" % int(time()*10000))[1:]
[8595]445        payment.p_id = "p%s" % timestamp
446        payment.p_category = category
447        payment.p_item = p_item
448        payment.p_session = p_session
449        payment.p_level = p_level
[9148]450        payment.p_current = p_current
[8595]451        payment.amount_auth = amount
452        return None, payment
[7019]453
[9868]454    def setBalanceDetails(self, category, student,
[9864]455            balance_session, balance_level, balance_amount):
456        """Create Payment object and set the payment data of a student for.
457
458        """
[9868]459        p_item = u'Balance'
[9864]460        p_session = balance_session
461        p_level = balance_level
462        p_current = False
463        amount = balance_amount
464        academic_session = self._getSessionConfiguration(p_session)
465        if academic_session == None:
466            return _(u'Session configuration object is not available.'), None
[9874]467        if amount in (0.0, None) or amount < 0:
468            return _('Amount must be greater than 0.'), None
[9864]469        for key in student['payments'].keys():
470            ticket = student['payments'][key]
471            if ticket.p_state == 'paid' and\
472               ticket.p_category == 'balance' and \
473               ticket.p_item == p_item and \
474               ticket.p_session == p_session:
475                  return _('This type of payment has already been made.'), None
476        payment = createObject(u'waeup.StudentOnlinePayment')
477        timestamp = ("%d" % int(time()*10000))[1:]
478        payment.p_id = "p%s" % timestamp
[9868]479        payment.p_category = category
[9864]480        payment.p_item = p_item
481        payment.p_session = p_session
482        payment.p_level = p_level
483        payment.p_current = p_current
484        payment.amount_auth = amount
485        return None, payment
486
[7186]487    def getAccommodationDetails(self, student):
[9219]488        """Determine the accommodation data of a student.
[7841]489        """
[7150]490        d = {}
491        d['error'] = u''
[8685]492        hostels = grok.getSite()['hostels']
493        d['booking_session'] = hostels.accommodation_session
494        d['allowed_states'] = hostels.accommodation_states
[8688]495        d['startdate'] = hostels.startdate
496        d['enddate'] = hostels.enddate
497        d['expired'] = hostels.expired
[7150]498        # Determine bed type
499        studycourse = student['studycourse']
[7369]500        certificate = getattr(studycourse,'certificate',None)
[7150]501        entry_session = studycourse.entry_session
502        current_level = studycourse.current_level
[9187]503        if None in (entry_session, current_level, certificate):
504            return d
[7369]505        end_level = certificate.end_level
[9148]506        if current_level == 10:
507            bt = 'pr'
508        elif entry_session == grok.getSite()['hostels'].accommodation_session:
[7150]509            bt = 'fr'
510        elif current_level >= end_level:
511            bt = 'fi'
512        else:
513            bt = 're'
514        if student.sex == 'f':
515            sex = 'female'
516        else:
517            sex = 'male'
518        special_handling = 'regular'
519        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
520        return d
[7019]521
[7186]522    def selectBed(self, available_beds):
[7841]523        """Select a bed from a list of available beds.
524
525        In the base configuration we select the first bed found,
526        but can also randomize the selection if we like.
527        """
[7150]528        return available_beds[0]
529
[9981]530    def _admissionText(self, student, portal_language):
[9979]531        inst_name = grok.getSite()['configuration'].name
532        text = trans(_(
533            'This is to inform you that you have been provisionally'
534            ' admitted into ${a} as follows:', mapping = {'a': inst_name}),
535            portal_language)
536        return text
537
[10686]538    def renderPDFAdmissionLetter(self, view, student=None, omit_fields=(),
539                                 pre_text=None, post_text=None,):
[9191]540        """Render pdf admission letter.
541        """
542        if student is None:
543            return
544        style = getSampleStyleSheet()
[9949]545        creator = self.getPDFCreator(student)
[9979]546        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[9191]547        data = []
548        doc_title = view.label
549        author = '%s (%s)' % (view.request.principal.title,
550                              view.request.principal.id)
[9944]551        footer_text = view.label.split('\n')
552        if len(footer_text) > 1:
553            # We can add a department in first line
554            footer_text = footer_text[1]
555        else:
556            # Only the first line is used for the footer
557            footer_text = footer_text[0]
[9191]558        if getattr(student, 'student_id', None) is not None:
559            footer_text = "%s - %s - " % (student.student_id, footer_text)
560
[10702]561        # Text before student data
[10686]562        if pre_text is None:
563            html = format_html(self._admissionText(student, portal_language))
564        else:
565            html = format_html(pre_text)
[9191]566        data.append(Paragraph(html, NOTE_STYLE))
567        data.append(Spacer(1, 20))
568
569        # Student data
[10261]570        data.append(render_student_data(view, omit_fields, lang=portal_language))
[9191]571
[10702]572        # Text after student data
[9191]573        data.append(Spacer(1, 20))
[10686]574        if post_text is None:
575            datelist = student.history.messages[0].split()[0].split('-')
576            creation_date = u'%s/%s/%s' % (datelist[2], datelist[1], datelist[0])
[10702]577            post_text = trans(_(
[10686]578                'Your Kofa student record was created on ${a}.',
579                mapping = {'a': creation_date}),
580                portal_language)
[10702]581        #html = format_html(post_text)
582        #data.append(Paragraph(html, NOTE_STYLE))
[9191]583
584        # Create pdf stream
585        view.response.setHeader(
586            'Content-Type', 'application/pdf')
587        pdf_stream = creator.create_pdf(
588            data, None, doc_title, author=author, footer=footer_text,
[10702]589            note=post_text)
[9191]590        return pdf_stream
591
[9949]592    def getPDFCreator(self, context):
593        """Get a pdf creator suitable for `context`.
594
595        The default implementation always returns the default creator.
596        """
597        return getUtility(IPDFCreator)
598
[8257]599    def renderPDF(self, view, filename='slip.pdf', student=None,
[9906]600                  studentview=None,
[10439]601                  tableheader=[], tabledata=[],
[9555]602                  note=None, signatures=None, sigs_in_footer=(),
[10250]603                  show_scans=True, topMargin=1.5,
604                  omit_fields=()):
[7841]605        """Render pdf slips for various pages.
606        """
[10261]607        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[9916]608        # XXX: tell what the different parameters mean
[8112]609        style = getSampleStyleSheet()
[9949]610        creator = self.getPDFCreator(student)
[8112]611        data = []
612        doc_title = view.label
613        author = '%s (%s)' % (view.request.principal.title,
614                              view.request.principal.id)
[9913]615        footer_text = view.label.split('\n')
616        if len(footer_text) > 2:
617            # We can add a department in first line
618            footer_text = footer_text[1]
619        else:
[9917]620            # Only the first line is used for the footer
[9913]621            footer_text = footer_text[0]
[7714]622        if getattr(student, 'student_id', None) is not None:
[7310]623            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]624
[7318]625        # Insert student data table
[7310]626        if student is not None:
[8112]627            bd_translation = trans(_('Base Data'), portal_language)
[9910]628            data.append(Paragraph(bd_translation, HEADING_STYLE))
[10261]629            data.append(render_student_data(
630                studentview, omit_fields, lang=portal_language))
[7304]631
[7318]632        # Insert widgets
[9191]633        if view.form_fields:
[9910]634            data.append(Paragraph(view.title, HEADING_STYLE))
[9191]635            separators = getattr(self, 'SEPARATORS_DICT', {})
636            table = creator.getWidgetsTable(
637                view.form_fields, view.context, None, lang=portal_language,
638                separators=separators)
639            data.append(table)
[7318]640
[8112]641        # Insert scanned docs
[9550]642        if show_scans:
643            data.extend(docs_as_flowables(view, portal_language))
[7318]644
[9452]645        # Insert history
[9910]646        if filename.startswith('clearance'):
[9452]647            hist_translation = trans(_('Workflow History'), portal_language)
[9910]648            data.append(Paragraph(hist_translation, HEADING_STYLE))
[9452]649            data.extend(creator.fromStringList(student.history.messages))
650
[10438]651        # Insert content tables (optionally on second page)
[10439]652        if hasattr(view, 'tabletitle'):
653            for i in range(len(view.tabletitle)):
654                if tabledata[i] and tableheader[i]:
655                    #data.append(PageBreak())
656                    #data.append(Spacer(1, 20))
657                    data.append(Paragraph(view.tabletitle[i], HEADING_STYLE))
658                    data.append(Spacer(1, 8))
659                    contenttable = render_table_data(tableheader[i],tabledata[i])
660                    data.append(contenttable)
[7318]661
[9010]662        # Insert signatures
[9965]663        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
664        # do not have a test for the following lines.
[9555]665        if signatures and not sigs_in_footer:
[9010]666            data.append(Spacer(1, 20))
[9966]667            # Render one signature table per signature to
668            # get date and signature in line.
669            for signature in signatures:
670                signaturetables = get_signature_tables(signature)
671                data.append(signaturetables[0])
[9010]672
[7150]673        view.response.setHeader(
674            'Content-Type', 'application/pdf')
[8112]675        try:
676            pdf_stream = creator.create_pdf(
[8257]677                data, None, doc_title, author=author, footer=footer_text,
[9948]678                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
[8112]679        except IOError:
680            view.flash('Error in image file.')
681            return view.redirect(view.url(view.context))
682        return pdf_stream
[7620]683
[10578]684    gpa_boundaries = ((1, 'Fail'),
685                      (1.5, 'Pass'),
686                      (2.4, '3rd Class'),
687                      (3.5, '2nd Class Lower'),
688                      (4.5, '2nd Class Upper'),
689                      (5, '1st Class'))
[10576]690
[10445]691    def getClassFromCGPA(self, gpa):
[10578]692        if gpa < self.gpa_boundaries[0][0]:
693            return 0, self.gpa_boundaries[0][1]
694        if gpa < self.gpa_boundaries[1][0]:
695            return 1, self.gpa_boundaries[1][1]
696        if gpa < self.gpa_boundaries[2][0]:
697            return 2, self.gpa_boundaries[2][1]
698        if gpa < self.gpa_boundaries[3][0]:
699            return 3, self.gpa_boundaries[3][1]
700        if gpa < self.gpa_boundaries[4][0]:
701            return 4, self.gpa_boundaries[4][1]
702        if gpa <= self.gpa_boundaries[5][0]:
703            return 5, self.gpa_boundaries[5][1]
704        return 'N/A'
[10445]705
[10250]706    def renderPDFTranscript(self, view, filename='transcript.pdf',
707                  student=None,
708                  studentview=None,
709                  note=None, signatures=None, sigs_in_footer=(),
710                  show_scans=True, topMargin=1.5,
711                  omit_fields=(),
712                  tableheader=None):
713        """Render pdf slips for transcript.
714        """
[10261]715        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[10250]716        # XXX: tell what the different parameters mean
717        style = getSampleStyleSheet()
718        creator = self.getPDFCreator(student)
719        data = []
720        doc_title = view.label
721        author = '%s (%s)' % (view.request.principal.title,
722                              view.request.principal.id)
723        footer_text = view.label.split('\n')
724        if len(footer_text) > 2:
725            # We can add a department in first line
726            footer_text = footer_text[1]
727        else:
728            # Only the first line is used for the footer
729            footer_text = footer_text[0]
730        if getattr(student, 'student_id', None) is not None:
731            footer_text = "%s - %s - " % (student.student_id, footer_text)
732
733        # Insert student data table
734        if student is not None:
735            #bd_translation = trans(_('Base Data'), portal_language)
736            #data.append(Paragraph(bd_translation, HEADING_STYLE))
[10261]737            data.append(render_student_data(
738                studentview, omit_fields, lang=portal_language))
[10250]739
740
741        transcript_data = view.context.getTranscriptData()
742        levels_data = transcript_data[0]
743        gpa = transcript_data[1]
744
745        contextdata = []
[10261]746        f_label = trans(_('Course of Study:'), portal_language)
[10250]747        f_label = Paragraph(f_label, ENTRY1_STYLE)
[10650]748        f_text = formatted_text(view.context.certificate.longtitle)
[10250]749        f_text = Paragraph(f_text, ENTRY1_STYLE)
750        contextdata.append([f_label,f_text])
751
[10261]752        f_label = trans(_('Faculty:'), portal_language)
[10250]753        f_label = Paragraph(f_label, ENTRY1_STYLE)
754        f_text = formatted_text(
[10650]755            view.context.certificate.__parent__.__parent__.__parent__.longtitle)
[10250]756        f_text = Paragraph(f_text, ENTRY1_STYLE)
757        contextdata.append([f_label,f_text])
758
[10261]759        f_label = trans(_('Department:'), portal_language)
[10250]760        f_label = Paragraph(f_label, ENTRY1_STYLE)
761        f_text = formatted_text(
[10650]762            view.context.certificate.__parent__.__parent__.longtitle)
[10250]763        f_text = Paragraph(f_text, ENTRY1_STYLE)
764        contextdata.append([f_label,f_text])
765
[10261]766        f_label = trans(_('Entry Session:'), portal_language)
[10250]767        f_label = Paragraph(f_label, ENTRY1_STYLE)
[10256]768        f_text = formatted_text(
769            view.session_dict.get(view.context.entry_session))
[10250]770        f_text = Paragraph(f_text, ENTRY1_STYLE)
771        contextdata.append([f_label,f_text])
772
[10261]773        f_label = trans(_('Entry Mode:'), portal_language)
[10250]774        f_label = Paragraph(f_label, ENTRY1_STYLE)
[10256]775        f_text = formatted_text(view.studymode_dict.get(
776            view.context.entry_mode))
[10250]777        f_text = Paragraph(f_text, ENTRY1_STYLE)
778        contextdata.append([f_label,f_text])
779
[10262]780        f_label = trans(_('Cumulative GPA:'), portal_language)
[10250]781        f_label = Paragraph(f_label, ENTRY1_STYLE)
[10576]782        f_text = formatted_text('%s (%s)' % (gpa, self.getClassFromCGPA(gpa)[1]))
[10250]783        f_text = Paragraph(f_text, ENTRY1_STYLE)
784        contextdata.append([f_label,f_text])
785
786        contexttable = Table(contextdata,style=SLIP_STYLE)
787        data.append(contexttable)
788
789        transcripttables = render_transcript_data(
[10261]790            view, tableheader, levels_data, lang=portal_language)
[10250]791        data.extend(transcripttables)
792
793        # Insert signatures
794        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
795        # do not have a test for the following lines.
796        if signatures and not sigs_in_footer:
797            data.append(Spacer(1, 20))
798            # Render one signature table per signature to
799            # get date and signature in line.
800            for signature in signatures:
801                signaturetables = get_signature_tables(signature)
802                data.append(signaturetables[0])
803
804        view.response.setHeader(
805            'Content-Type', 'application/pdf')
806        try:
807            pdf_stream = creator.create_pdf(
808                data, None, doc_title, author=author, footer=footer_text,
809                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
810        except IOError:
[10261]811            view.flash(_('Error in image file.'))
[10250]812            return view.redirect(view.url(view.context))
813        return pdf_stream
814
[9830]815    def maxCredits(self, studylevel):
816        """Return maximum credits.
[9532]817
[9830]818        In some universities maximum credits is not constant, it
819        depends on the student's study level.
820        """
821        return 50
822
[9532]823    def maxCreditsExceeded(self, studylevel, course):
[9830]824        max_credits = self.maxCredits(studylevel)
825        if max_credits and \
826            studylevel.total_credits + course.credits > max_credits:
827            return max_credits
[9532]828        return 0
829
[9987]830    def getBedCoordinates(self, bedticket):
831        """Return bed coordinates.
832
833        This method can be used to customize the display_coordinates
834        property method.
835        """
836        return bedticket.bed_coordinates
837
[7841]838    VERDICTS_DICT = {
[8820]839        '0': _('(not yet)'),
[7841]840        'A': 'Successful student',
841        'B': 'Student with carryover courses',
842        'C': 'Student on probation',
843        }
[8099]844
845    SEPARATORS_DICT = {
846        }
[8410]847
[10021]848    SKIP_UPLOAD_VIEWLETS = ()
849
[8410]850    #: A prefix used when generating new student ids. Each student id will
851    #: start with this string. The default is 'K' for ``Kofa``.
852    STUDENT_ID_PREFIX = u'K'
Note: See TracBrowser for help on using the repository browser.