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

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

Render course ticket tables correctly.

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