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

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

Test if the transcript pdf slip can be opened.

Add date_of_birth to base data.

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