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

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

Define correct dictionaries. level_dict must include probation levels.

Add buttons for navigating to transcripts.

Add property attribute 'transcript_enabled' which eases defining conditions in custom packages.

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