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

Last change on this file since 10689 was 10688, checked in by Henrik Bettermann, 12 years ago

Print current mode on pdf slips.

  • Property svn:keywords set to Id
File size: 34.6 KB
Line 
1## $Id: utils.py 10688 2013-11-04 06:46:51Z 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##
18"""General helper functions and utilities for the student section.
19"""
20import grok
21from time import time
22from reportlab.lib import colors
23from reportlab.lib.units import cm
24from reportlab.lib.pagesizes import A4
25from reportlab.lib.styles import getSampleStyleSheet
26from reportlab.platypus import Paragraph, Image, Table, Spacer
27from zope.schema.interfaces import ConstraintNotSatisfied
28from zope.component import getUtility, createObject
29from zope.formlib.form import setUpEditWidgets
30from zope.i18n import translate
31from waeup.kofa.interfaces import (
32    IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED,
33    academic_sessions_vocab)
34from waeup.kofa.interfaces import MessageFactory as _
35from waeup.kofa.students.interfaces import IStudentsUtils
36from waeup.kofa.browser.pdf import (
37    ENTRY1_STYLE, format_html, NOTE_STYLE, HEADING_STYLE,
38    get_signature_tables)
39from waeup.kofa.browser.interfaces import IPDFCreator
40from waeup.kofa.utils.helpers import to_timezone
41
42SLIP_STYLE = [
43    ('VALIGN',(0,0),(-1,-1),'TOP'),
44    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
45    ]
46
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),
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
55    ]
56
57FONT_SIZE = 10
58FONT_COLOR = 'black'
59
60def trans(text, lang):
61    # shortcut
62    return translate(text, 'waeup.kofa', target_language=lang)
63
64def formatted_text(text, color=FONT_COLOR, lang='en'):
65    """Turn `text`, `color` and `size` into an HTML snippet.
66
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
73    Finally, a br tag is added if widgets contain div tags
74    which are not supported by reportlab.
75
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)
83    if text == 'None':
84        text = ''
85    # Mainly for boolean values we need our customized
86    # localisation of the zope domain
87    text = translate(text, 'zope', target_language=lang)
88    text = text.replace('</div>', '<br /></div>')
89    tag1 = u'<font color="%s">' % (color)
90    return tag1 + u'%s</font>' % text
91
92def generate_student_id():
93    students = grok.getSite()['students']
94    new_id = students.unique_student_id
95    return new_id
96
97def set_up_widgets(view, ignore_request=False):
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
105def render_student_data(studentview, omit_fields=(), lang='en'):
106    """Render student table for an existing frame.
107    """
108    width, height = A4
109    set_up_widgets(studentview, ignore_request=True)
110    data_left = []
111    data_right = []
112    style = getSampleStyleSheet()
113    img = getUtility(IExtFileStore).getFileByContext(
114        studentview.context, attr='passport.jpg')
115    if img is None:
116        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
117        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
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)])
121
122    f_label = trans(_('Name:'), lang)
123    f_label = Paragraph(f_label, ENTRY1_STYLE)
124    f_text = formatted_text(studentview.context.display_fullname)
125    f_text = Paragraph(f_text, ENTRY1_STYLE)
126    data_right.append([f_label,f_text])
127
128    for widget in studentview.widgets:
129        if 'name' in widget.name:
130            continue
131        f_label = translate(
132            widget.label.strip(), 'waeup.kofa',
133            target_language=lang)
134        f_label = Paragraph('%s:' % f_label, ENTRY1_STYLE)
135        f_text = formatted_text(widget(), lang=lang)
136        f_text = Paragraph(f_text, ENTRY1_STYLE)
137        data_right.append([f_label,f_text])
138
139    if getattr(studentview.context, 'certcode', None):
140        if not 'certificate' in omit_fields:
141            f_label = trans(_('Study Course:'), lang)
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:
148            f_label = trans(_('Department:'), lang)
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:
157            f_label = trans(_('Faculty:'), lang)
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 '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])
174        if not 'entry_session' in omit_fields:
175            f_label = trans(_('Entry Session:'), lang)
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])
182        if not 'date_of_birth' in omit_fields:
183            f_label = trans(_('Date of Birth:'), lang)
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])
193
194    table_left = Table(data_left,style=SLIP_STYLE)
195    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
196    table = Table([[table_left, table_right],],style=SLIP_STYLE)
197    return table
198
199def render_table_data(tableheader, tabledata, lang='en'):
200    """Render children table for an existing frame.
201    """
202    data = []
203    #data.append([Spacer(1, 12)])
204    line = []
205    style = getSampleStyleSheet()
206    for element in tableheader:
207        field = '<strong>%s</strong>' % formatted_text(element[0], lang=lang)
208        field = Paragraph(field, style["Normal"])
209        line.append(field)
210    data.append(line)
211    for ticket in tabledata:
212        line = []
213        for element in tableheader:
214              field = formatted_text(getattr(ticket,element[1],u' '))
215              field = Paragraph(field, style["Normal"])
216              line.append(field)
217        data.append(line)
218    table = Table(data,colWidths=[
219        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
220    return table
221
222def render_transcript_data(view, tableheader, levels_data, lang='en'):
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']
229        tickets = level['tickets_1'] + level['tickets_2'] + level['tickets_3']
230        headerline = []
231        tabledata = []
232        subheader = '%s %s, %s %s' % (
233            trans(_('Session'), lang),
234            view.session_dict[level_obj.level_session],
235            trans(_('Level'), lang),
236            view.level_dict[level_obj.level])
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"])
241            headerline.append(field)
242        tabledata.append(headerline)
243        for ticket in tickets:
244            ticketline = []
245            for element in tableheader:
246                  field = formatted_text(getattr(ticket,element[1],u' '))
247                  field = Paragraph(field, style["Normal"])
248                  ticketline.append(field)
249            tabledata.append(ticketline)
250        table = Table(tabledata,colWidths=[
251            element[2]*cm for element in tableheader], style=CONTENT_STYLE)
252        data.append(table)
253        sgpa = '%s: %s' % (trans('Sessional GPA (rectified)', lang), level['sgpa'])
254        data.append(Paragraph(sgpa, style["Normal"]))
255    return data
256
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 = []
265
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)
271        data.append(Paragraph(sc_translation, HEADING_STYLE))
272        # Insert list of scanned documents
273        table_data = []
274        for viewlet in fm.viewlets:
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])
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
295class StudentsUtils(grok.GlobalUtility):
296    """A collection of methods subject to customization.
297    """
298    grok.implements(IStudentsUtils)
299
300    def getReturningData(self, student):
301        """ Define what happens after school fee payment
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        """
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):
312        """ Define what happens after school fee payment
313        depending on the student's senate verdict.
314
315        This method folllows the same algorithm as getReturningData but
316        it also sets the new values.
317        """
318        new_session, new_level = self.getReturningData(student)
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
325        student['studycourse'].current_session = new_session
326        verdict = student['studycourse'].current_verdict
327        student['studycourse'].current_verdict = '0'
328        student['studycourse'].previous_verdict = verdict
329        return
330
331    def _getSessionConfiguration(self, session):
332        try:
333            return grok.getSite()['configuration'][str(session)]
334        except KeyError:
335            return None
336
337    def setPaymentDetails(self, category, student,
338            previous_session, previous_level):
339        """Create Payment object and set the payment data of a student for
340        the payment category specified.
341
342        """
343        p_item = u''
344        amount = 0.0
345        if previous_session:
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
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
363        academic_session = self._getSessionConfiguration(p_session)
364        if academic_session == None:
365            return _(u'Session configuration object is not available.'), None
366        # Determine fee.
367        if category == 'schoolfee':
368            try:
369                certificate = student['studycourse'].certificate
370                p_item = certificate.code
371            except (AttributeError, TypeError):
372                return _('Study course data are incomplete.'), None
373            if previous_session:
374                # Students can pay for previous sessions in all
375                # workflow states.  Fresh students are excluded by the
376                # update method of the PreviousPaymentAddFormPage.
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:
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.
389                    p_session, p_level = self.getReturningData(student)
390                    academic_session = self._getSessionConfiguration(p_session)
391                    if academic_session == None:
392                        return _(
393                            u'Session configuration object is not available.'
394                            ), None
395                    amount = getattr(certificate, 'school_fee_2', 0.0)
396                elif student.is_postgrad and student.state == PAID:
397                    # Returning postgraduate students also pay for the
398                    # next session but their level always remains the
399                    # same.
400                    p_session += 1
401                    academic_session = self._getSessionConfiguration(p_session)
402                    if academic_session == None:
403                        return _(
404                            u'Session configuration object is not available.'
405                            ), None
406                    amount = getattr(certificate, 'school_fee_2', 0.0)
407        elif category == 'clearance':
408            try:
409                p_item = student['studycourse'].certificate.code
410            except (AttributeError, TypeError):
411                return _('Study course data are incomplete.'), None
412            amount = academic_session.clearance_fee
413        elif category == 'bed_allocation':
414            p_item = self.getAccommodationDetails(student)['bt']
415            amount = academic_session.booking_fee
416        elif category == 'hostel_maintenance':
417            amount = 0.0
418            bedticket = student['accommodation'].get(
419                str(student.current_session), None)
420            if bedticket:
421                p_item = bedticket.bed_coordinates
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
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)
432        elif category == 'transcript':
433            amount = academic_session.transcript_fee
434        if amount in (0.0, None):
435            return _('Amount could not be determined.'), None
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:
442                  return _('This type of payment has already been made.'), None
443        payment = createObject(u'waeup.StudentOnlinePayment')
444        timestamp = ("%d" % int(time()*10000))[1:]
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
450        payment.p_current = p_current
451        payment.amount_auth = amount
452        return None, payment
453
454    def setBalanceDetails(self, category, student,
455            balance_session, balance_level, balance_amount):
456        """Create Payment object and set the payment data of a student for.
457
458        """
459        p_item = u'Balance'
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
467        if amount in (0.0, None) or amount < 0:
468            return _('Amount must be greater than 0.'), None
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
479        payment.p_category = category
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
487    def getAccommodationDetails(self, student):
488        """Determine the accommodation data of a student.
489        """
490        d = {}
491        d['error'] = u''
492        hostels = grok.getSite()['hostels']
493        d['booking_session'] = hostels.accommodation_session
494        d['allowed_states'] = hostels.accommodation_states
495        d['startdate'] = hostels.startdate
496        d['enddate'] = hostels.enddate
497        d['expired'] = hostels.expired
498        # Determine bed type
499        studycourse = student['studycourse']
500        certificate = getattr(studycourse,'certificate',None)
501        entry_session = studycourse.entry_session
502        current_level = studycourse.current_level
503        if None in (entry_session, current_level, certificate):
504            return d
505        end_level = certificate.end_level
506        if current_level == 10:
507            bt = 'pr'
508        elif entry_session == grok.getSite()['hostels'].accommodation_session:
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
521
522    def selectBed(self, available_beds):
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        """
528        return available_beds[0]
529
530    def _admissionText(self, student, portal_language):
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
538    def renderPDFAdmissionLetter(self, view, student=None, omit_fields=(),
539                                 pre_text=None, post_text=None,):
540        """Render pdf admission letter.
541        """
542        if student is None:
543            return
544        style = getSampleStyleSheet()
545        creator = self.getPDFCreator(student)
546        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
547        data = []
548        doc_title = view.label
549        author = '%s (%s)' % (view.request.principal.title,
550                              view.request.principal.id)
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]
558        if getattr(student, 'student_id', None) is not None:
559            footer_text = "%s - %s - " % (student.student_id, footer_text)
560
561        # Insert text before student data
562        if pre_text is None:
563            html = format_html(self._admissionText(student, portal_language))
564        else:
565            html = format_html(pre_text)
566        data.append(Paragraph(html, NOTE_STYLE))
567        data.append(Spacer(1, 20))
568
569        # Student data
570        data.append(render_student_data(view, omit_fields, lang=portal_language))
571
572        # Insert text after student data
573        data.append(Spacer(1, 20))
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])
577            text = trans(_(
578                'Your Kofa student record was created on ${a}.',
579                mapping = {'a': creation_date}),
580                portal_language)
581        else:
582            text = post_text
583        html = format_html(text)
584        data.append(Paragraph(html, NOTE_STYLE))
585
586        # Create pdf stream
587        view.response.setHeader(
588            'Content-Type', 'application/pdf')
589        pdf_stream = creator.create_pdf(
590            data, None, doc_title, author=author, footer=footer_text,
591            note=None)
592        return pdf_stream
593
594    def getPDFCreator(self, context):
595        """Get a pdf creator suitable for `context`.
596
597        The default implementation always returns the default creator.
598        """
599        return getUtility(IPDFCreator)
600
601    def renderPDF(self, view, filename='slip.pdf', student=None,
602                  studentview=None,
603                  tableheader=[], tabledata=[],
604                  note=None, signatures=None, sigs_in_footer=(),
605                  show_scans=True, topMargin=1.5,
606                  omit_fields=()):
607        """Render pdf slips for various pages.
608        """
609        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
610        # XXX: tell what the different parameters mean
611        style = getSampleStyleSheet()
612        creator = self.getPDFCreator(student)
613        data = []
614        doc_title = view.label
615        author = '%s (%s)' % (view.request.principal.title,
616                              view.request.principal.id)
617        footer_text = view.label.split('\n')
618        if len(footer_text) > 2:
619            # We can add a department in first line
620            footer_text = footer_text[1]
621        else:
622            # Only the first line is used for the footer
623            footer_text = footer_text[0]
624        if getattr(student, 'student_id', None) is not None:
625            footer_text = "%s - %s - " % (student.student_id, footer_text)
626
627        # Insert student data table
628        if student is not None:
629            bd_translation = trans(_('Base Data'), portal_language)
630            data.append(Paragraph(bd_translation, HEADING_STYLE))
631            data.append(render_student_data(
632                studentview, omit_fields, lang=portal_language))
633
634        # Insert widgets
635        if view.form_fields:
636            data.append(Paragraph(view.title, HEADING_STYLE))
637            separators = getattr(self, 'SEPARATORS_DICT', {})
638            table = creator.getWidgetsTable(
639                view.form_fields, view.context, None, lang=portal_language,
640                separators=separators)
641            data.append(table)
642
643        # Insert scanned docs
644        if show_scans:
645            data.extend(docs_as_flowables(view, portal_language))
646
647        # Insert history
648        if filename.startswith('clearance'):
649            hist_translation = trans(_('Workflow History'), portal_language)
650            data.append(Paragraph(hist_translation, HEADING_STYLE))
651            data.extend(creator.fromStringList(student.history.messages))
652
653        # Insert content tables (optionally on second page)
654        if hasattr(view, 'tabletitle'):
655            for i in range(len(view.tabletitle)):
656                if tabledata[i] and tableheader[i]:
657                    #data.append(PageBreak())
658                    #data.append(Spacer(1, 20))
659                    data.append(Paragraph(view.tabletitle[i], HEADING_STYLE))
660                    data.append(Spacer(1, 8))
661                    contenttable = render_table_data(tableheader[i],tabledata[i])
662                    data.append(contenttable)
663
664        # Insert signatures
665        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
666        # do not have a test for the following lines.
667        if signatures and not sigs_in_footer:
668            data.append(Spacer(1, 20))
669            # Render one signature table per signature to
670            # get date and signature in line.
671            for signature in signatures:
672                signaturetables = get_signature_tables(signature)
673                data.append(signaturetables[0])
674
675        view.response.setHeader(
676            'Content-Type', 'application/pdf')
677        try:
678            pdf_stream = creator.create_pdf(
679                data, None, doc_title, author=author, footer=footer_text,
680                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
681        except IOError:
682            view.flash('Error in image file.')
683            return view.redirect(view.url(view.context))
684        return pdf_stream
685
686    gpa_boundaries = ((1, 'Fail'),
687                      (1.5, 'Pass'),
688                      (2.4, '3rd Class'),
689                      (3.5, '2nd Class Lower'),
690                      (4.5, '2nd Class Upper'),
691                      (5, '1st Class'))
692
693    def getClassFromCGPA(self, gpa):
694        if gpa < self.gpa_boundaries[0][0]:
695            return 0, self.gpa_boundaries[0][1]
696        if gpa < self.gpa_boundaries[1][0]:
697            return 1, self.gpa_boundaries[1][1]
698        if gpa < self.gpa_boundaries[2][0]:
699            return 2, self.gpa_boundaries[2][1]
700        if gpa < self.gpa_boundaries[3][0]:
701            return 3, self.gpa_boundaries[3][1]
702        if gpa < self.gpa_boundaries[4][0]:
703            return 4, self.gpa_boundaries[4][1]
704        if gpa <= self.gpa_boundaries[5][0]:
705            return 5, self.gpa_boundaries[5][1]
706        return 'N/A'
707
708    def renderPDFTranscript(self, view, filename='transcript.pdf',
709                  student=None,
710                  studentview=None,
711                  note=None, signatures=None, sigs_in_footer=(),
712                  show_scans=True, topMargin=1.5,
713                  omit_fields=(),
714                  tableheader=None):
715        """Render pdf slips for transcript.
716        """
717        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
718        # XXX: tell what the different parameters mean
719        style = getSampleStyleSheet()
720        creator = self.getPDFCreator(student)
721        data = []
722        doc_title = view.label
723        author = '%s (%s)' % (view.request.principal.title,
724                              view.request.principal.id)
725        footer_text = view.label.split('\n')
726        if len(footer_text) > 2:
727            # We can add a department in first line
728            footer_text = footer_text[1]
729        else:
730            # Only the first line is used for the footer
731            footer_text = footer_text[0]
732        if getattr(student, 'student_id', None) is not None:
733            footer_text = "%s - %s - " % (student.student_id, footer_text)
734
735        # Insert student data table
736        if student is not None:
737            #bd_translation = trans(_('Base Data'), portal_language)
738            #data.append(Paragraph(bd_translation, HEADING_STYLE))
739            data.append(render_student_data(
740                studentview, omit_fields, lang=portal_language))
741
742
743        transcript_data = view.context.getTranscriptData()
744        levels_data = transcript_data[0]
745        gpa = transcript_data[1]
746
747        contextdata = []
748        f_label = trans(_('Course of Study:'), portal_language)
749        f_label = Paragraph(f_label, ENTRY1_STYLE)
750        f_text = formatted_text(view.context.certificate.longtitle)
751        f_text = Paragraph(f_text, ENTRY1_STYLE)
752        contextdata.append([f_label,f_text])
753
754        f_label = trans(_('Faculty:'), portal_language)
755        f_label = Paragraph(f_label, ENTRY1_STYLE)
756        f_text = formatted_text(
757            view.context.certificate.__parent__.__parent__.__parent__.longtitle)
758        f_text = Paragraph(f_text, ENTRY1_STYLE)
759        contextdata.append([f_label,f_text])
760
761        f_label = trans(_('Department:'), portal_language)
762        f_label = Paragraph(f_label, ENTRY1_STYLE)
763        f_text = formatted_text(
764            view.context.certificate.__parent__.__parent__.longtitle)
765        f_text = Paragraph(f_text, ENTRY1_STYLE)
766        contextdata.append([f_label,f_text])
767
768        f_label = trans(_('Entry Session:'), portal_language)
769        f_label = Paragraph(f_label, ENTRY1_STYLE)
770        f_text = formatted_text(
771            view.session_dict.get(view.context.entry_session))
772        f_text = Paragraph(f_text, ENTRY1_STYLE)
773        contextdata.append([f_label,f_text])
774
775        f_label = trans(_('Entry Mode:'), portal_language)
776        f_label = Paragraph(f_label, ENTRY1_STYLE)
777        f_text = formatted_text(view.studymode_dict.get(
778            view.context.entry_mode))
779        f_text = Paragraph(f_text, ENTRY1_STYLE)
780        contextdata.append([f_label,f_text])
781
782        f_label = trans(_('Cumulative GPA:'), portal_language)
783        f_label = Paragraph(f_label, ENTRY1_STYLE)
784        f_text = formatted_text('%s (%s)' % (gpa, self.getClassFromCGPA(gpa)[1]))
785        f_text = Paragraph(f_text, ENTRY1_STYLE)
786        contextdata.append([f_label,f_text])
787
788        contexttable = Table(contextdata,style=SLIP_STYLE)
789        data.append(contexttable)
790
791        transcripttables = render_transcript_data(
792            view, tableheader, levels_data, lang=portal_language)
793        data.extend(transcripttables)
794
795        # Insert signatures
796        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
797        # do not have a test for the following lines.
798        if signatures and not sigs_in_footer:
799            data.append(Spacer(1, 20))
800            # Render one signature table per signature to
801            # get date and signature in line.
802            for signature in signatures:
803                signaturetables = get_signature_tables(signature)
804                data.append(signaturetables[0])
805
806        view.response.setHeader(
807            'Content-Type', 'application/pdf')
808        try:
809            pdf_stream = creator.create_pdf(
810                data, None, doc_title, author=author, footer=footer_text,
811                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
812        except IOError:
813            view.flash(_('Error in image file.'))
814            return view.redirect(view.url(view.context))
815        return pdf_stream
816
817    def maxCredits(self, studylevel):
818        """Return maximum credits.
819
820        In some universities maximum credits is not constant, it
821        depends on the student's study level.
822        """
823        return 50
824
825    def maxCreditsExceeded(self, studylevel, course):
826        max_credits = self.maxCredits(studylevel)
827        if max_credits and \
828            studylevel.total_credits + course.credits > max_credits:
829            return max_credits
830        return 0
831
832    def getBedCoordinates(self, bedticket):
833        """Return bed coordinates.
834
835        This method can be used to customize the display_coordinates
836        property method.
837        """
838        return bedticket.bed_coordinates
839
840    VERDICTS_DICT = {
841        '0': _('(not yet)'),
842        'A': 'Successful student',
843        'B': 'Student with carryover courses',
844        'C': 'Student on probation',
845        }
846
847    SEPARATORS_DICT = {
848        }
849
850    SKIP_UPLOAD_VIEWLETS = ()
851
852    #: A prefix used when generating new student ids. Each student id will
853    #: start with this string. The default is 'K' for ``Kofa``.
854    STUDENT_ID_PREFIX = u'K'
Note: See TracBrowser for help on using the repository browser.