source: main/waeup.kofa/branches/0.2/src/waeup/kofa/students/utils.py @ 11394

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

Resolve configuration conflict.

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