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

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

Show also level courses on slips.

  • Property svn:keywords set to Id
File size: 28.3 KB
RevLine 
[7191]1## $Id: utils.py 9957 2013-02-15 13:31:58Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[7358]18"""General helper functions and utilities for the student section.
[6651]19"""
[7150]20import grok
[8595]21from time import time
[7318]22from reportlab.lib import colors
[7019]23from reportlab.lib.units import cm
24from reportlab.lib.pagesizes import A4
[9015]25from reportlab.lib.styles import getSampleStyleSheet
26from reportlab.platypus import Paragraph, Image, Table, Spacer
[9922]27from zope.schema.interfaces import ConstraintNotSatisfied
[9015]28from zope.component import getUtility, createObject
[7019]29from zope.formlib.form import setUpEditWidgets
[9015]30from zope.i18n import translate
[8596]31from waeup.kofa.interfaces import (
[9762]32    IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED,
33    academic_sessions_vocab)
[7811]34from waeup.kofa.interfaces import MessageFactory as _
35from waeup.kofa.students.interfaces import IStudentsUtils
[9910]36from waeup.kofa.browser.pdf import (
37    ENTRY1_STYLE, format_html, NOTE_STYLE, HEADING_STYLE)
38from waeup.kofa.browser.interfaces import IPDFCreator
[6651]39
[7318]40SLIP_STYLE = [
41    ('VALIGN',(0,0),(-1,-1),'TOP'),
42    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
43    ]
[7019]44
[7318]45CONTENT_STYLE = [
46    ('VALIGN',(0,0),(-1,-1),'TOP'),
47    #('FONT', (0,0), (-1,-1), 'Helvetica', 8),
48    #('TEXTCOLOR',(0,0),(-1,0),colors.white),
[9906]49    #('BACKGROUND',(0,0),(-1,0),colors.black),
50    ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
51    ('BOX', (0,0), (-1,-1), 1, colors.black),
52
[7318]53    ]
[7304]54
[7318]55FONT_SIZE = 10
56FONT_COLOR = 'black'
57
[8112]58def trans(text, lang):
59    # shortcut
60    return translate(text, 'waeup.kofa', target_language=lang)
61
[9911]62def formatted_text(text, color=FONT_COLOR):
[7511]63    """Turn `text`, `color` and `size` into an HTML snippet.
[7318]64
[7511]65    The snippet is suitable for use with reportlab and generating PDFs.
66    Wraps the `text` into a ``<font>`` tag with passed attributes.
67
68    Also non-strings are converted. Raw strings are expected to be
69    utf-8 encoded (usually the case for widgets etc.).
70
[7804]71    Finally, a br tag is added if widgets contain div tags
72    which are not supported by reportlab.
73
[7511]74    The returned snippet is unicode type.
75    """
[8142]76    try:
77        # In unit tests IKofaUtils has not been registered
78        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
79    except:
80        portal_language = 'en'
[7511]81    if not isinstance(text, unicode):
82        if isinstance(text, basestring):
83            text = text.decode('utf-8')
84        else:
85            text = unicode(text)
[9717]86    if text == 'None':
87        text = ''
[8141]88    # Mainly for boolean values we need our customized
89    # localisation of the zope domain
90    text = translate(text, 'zope', target_language=portal_language)
[7804]91    text = text.replace('</div>', '<br /></div>')
[9910]92    tag1 = u'<font color="%s">' % (color)
[7511]93    return tag1 + u'%s</font>' % text
94
[8481]95def generate_student_id():
[8410]96    students = grok.getSite()['students']
97    new_id = students.unique_student_id
98    return new_id
[6742]99
[7186]100def set_up_widgets(view, ignore_request=False):
[7019]101    view.adapters = {}
102    view.widgets = setUpEditWidgets(
103        view.form_fields, view.prefix, view.context, view.request,
104        adapters=view.adapters, for_display=True,
105        ignore_request=ignore_request
106        )
107
[7310]108def render_student_data(studentview):
[7318]109    """Render student table for an existing frame.
110    """
111    width, height = A4
[7186]112    set_up_widgets(studentview, ignore_request=True)
[7318]113    data_left = []
114    data_right = []
[7019]115    style = getSampleStyleSheet()
[7280]116    img = getUtility(IExtFileStore).getFileByContext(
117        studentview.context, attr='passport.jpg')
118    if img is None:
[7811]119        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
[7280]120        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]121    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
122    data_left.append([doc_img])
123    #data.append([Spacer(1, 12)])
[7819]124    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[9141]125
[9911]126    f_label = _('Name:')
[9910]127    f_label = Paragraph(f_label, ENTRY1_STYLE)
[9911]128    f_text = formatted_text(studentview.context.display_fullname)
[9910]129    f_text = Paragraph(f_text, ENTRY1_STYLE)
[9141]130    data_right.append([f_label,f_text])
131
[7019]132    for widget in studentview.widgets:
[9141]133        if 'name' in widget.name:
[7019]134            continue
[9911]135        f_label = translate(
[7811]136            widget.label.strip(), 'waeup.kofa',
[7714]137            target_language=portal_language)
[9911]138        f_label = Paragraph('%s:' % f_label, ENTRY1_STYLE)
139        f_text = formatted_text(widget())
[9910]140        f_text = Paragraph(f_text, ENTRY1_STYLE)
[7318]141        data_right.append([f_label,f_text])
[9141]142
[9452]143    if getattr(studentview.context, 'certcode', None):
[9911]144        f_label = _('Study Course:')
[9910]145        f_label = Paragraph(f_label, ENTRY1_STYLE)
[9191]146        f_text = formatted_text(
[9911]147            studentview.context['studycourse'].certificate.longtitle())
[9910]148        f_text = Paragraph(f_text, ENTRY1_STYLE)
[9141]149        data_right.append([f_label,f_text])
150
[9911]151        f_label = _('Department:')
[9910]152        f_label = Paragraph(f_label, ENTRY1_STYLE)
[9191]153        f_text = formatted_text(
154            studentview.context[
155            'studycourse'].certificate.__parent__.__parent__.longtitle(),
[9911]156            )
[9910]157        f_text = Paragraph(f_text, ENTRY1_STYLE)
[9191]158        data_right.append([f_label,f_text])
159
[9911]160        f_label = _('Faculty:')
[9910]161        f_label = Paragraph(f_label, ENTRY1_STYLE)
[9191]162        f_text = formatted_text(
163            studentview.context[
164            'studycourse'].certificate.__parent__.__parent__.__parent__.longtitle(),
[9911]165            )
[9910]166        f_text = Paragraph(f_text, ENTRY1_STYLE)
[9191]167        data_right.append([f_label,f_text])
168
[9911]169        f_label = _('Entry Session: ')
[9910]170        f_label = Paragraph(f_label, ENTRY1_STYLE)
[9762]171        entry_session = studentview.context['studycourse'].entry_session
172        entry_session = academic_sessions_vocab.getTerm(entry_session).title
[9911]173        f_text = formatted_text(entry_session)
[9910]174        f_text = Paragraph(f_text, ENTRY1_STYLE)
[9762]175        data_right.append([f_label,f_text])
176
[7318]177    table_left = Table(data_left,style=SLIP_STYLE)
[8112]178    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
[7318]179    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]180    return table
181
[7304]182def render_table_data(tableheader,tabledata):
[7318]183    """Render children table for an existing frame.
184    """
[7304]185    data = []
[7318]186    #data.append([Spacer(1, 12)])
[7304]187    line = []
188    style = getSampleStyleSheet()
189    for element in tableheader:
[9906]190        field = '<strong>%s</strong>' % formatted_text(element[0])
[7310]191        field = Paragraph(field, style["Normal"])
[7304]192        line.append(field)
193    data.append(line)
194    for ticket in tabledata:
195        line = []
196        for element in tableheader:
[7511]197              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]198              field = Paragraph(field, style["Normal"])
[7304]199              line.append(field)
200        data.append(line)
[7310]201    table = Table(data,colWidths=[
[7318]202        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]203    return table
204
[9014]205def get_signature_table(signatures, lang='en'):
[9916]206    """Return a list of one or more reportlab tables with signature fields.
207
208    Each signature will get a date field.
209
210    If more than three signatures have to be rendered, instead of a
211    list with one table a list of tables with one signature field each
212    will be returned.
[9014]213    """
[9010]214    style = getSampleStyleSheet()
[9014]215    space_width = 0.4  # width in cm of space between signatures
216    table_width = 16.0 # supposed width of signature table in cms
217    # width of signature cells in cm...
218    sig_col_width = table_width - ((len(signatures) - 1) * space_width)
219    sig_col_width = sig_col_width / len(signatures)
[9010]220    data = []
[9014]221    col_widths = [] # widths of columns
222
223    sig_style = [
224        ('VALIGN',(0,-1),(-1,-1),'TOP'),
[9910]225        #('FONT', (0,0), (-1,-1), 'Helvetica-BoldOblique', 12),
[9014]226        ('BOTTOMPADDING', (0,0), (-1,0), 36),
227        ('TOPPADDING', (0,-1), (-1,-1), 0),
228        ]
229
[9916]230    col_widths = [sig_col_width*cm, space_width*cm] * len(signatures)
231    if len(signatures) == 1 or len(signatures) > 3:
232        col_widths = [table_width*0.66*cm, table_width*0.34*cm]
233
[9014]234    row = []
[9916]235    if len(signatures) < 4:
236        # draw several signature fields in a row
237        for num, signature in enumerate(signatures):
238            # draw a line above each signature cell (not: empty cells
239            # in between)
240            sig_style.append(
241                ('LINEABOVE', (num*2,-1), (num*2, -1), 1, colors.black))
242            row.append(Paragraph(trans(_('Date:'), lang), ENTRY1_STYLE))
243            row.append('') # space col
244        data.append(row[:-1])
245        data.extend(([''],)*2) # insert 2 empty rows...
246        row = []
247        for signature in signatures:
248            row.append(Paragraph(trans(signature, lang), ENTRY1_STYLE))
249            row.append('')
250        data.append(row[:-1])
251    else:
252        # Draw each signature field one under another (vertically)
253        for num, signature in enumerate(signatures):
[9949]254            line = len(data)
[9916]255            sig_style.extend((
256                ('TOPPADDING', (0, line), (-1, line), 32),
257                ('BOTTOMPADDING', (0, line), (-1, line), 2),
258                ('LINEABOVE', (0, line+1), (0, line+1), 1, colors.black),
259                ('SPAN', (0, line+1), (1, line+1)),
260                ))
261            data.append(['', ''])
262            row.append(Paragraph(trans(signature, lang), ENTRY1_STYLE))
263            data.append(row)
264            row = []
[9014]265    table = Table(data, style=sig_style, repeatRows=len(data),
266                  colWidths=col_widths)
[9010]267    return table
268
[8112]269def docs_as_flowables(view, lang='en'):
270    """Create reportlab flowables out of scanned docs.
271    """
272    # XXX: fix circular import problem
273    from waeup.kofa.students.viewlets import FileManager
274    from waeup.kofa.browser import DEFAULT_IMAGE_PATH
275    style = getSampleStyleSheet()
276    data = []
[7318]277
[8112]278    # Collect viewlets
279    fm = FileManager(view.context, view.request, view)
280    fm.update()
281    if fm.viewlets:
282        sc_translation = trans(_('Scanned Documents'), lang)
[9910]283        data.append(Paragraph(sc_translation, HEADING_STYLE))
[8112]284        # Insert list of scanned documents
285        table_data = []
286        for viewlet in fm.viewlets:
287            f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE)
288            img_path = getattr(getUtility(IExtFileStore).getFileByContext(
289                view.context, attr=viewlet.download_name), 'name', None)
[8120]290            f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE)
[8112]291            if img_path is None:
292                pass
[9016]293            elif not img_path[-4:] in ('.jpg', '.JPG'):
[8112]294                # reportlab requires jpg images, I think.
[9016]295                f_text = Paragraph('%s (not displayable)' % (
[8112]296                    viewlet.title,), ENTRY1_STYLE)
297            else:
[8117]298                f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound')
[8112]299            table_data.append([f_label, f_text])
300        if table_data:
301            # safety belt; empty tables lead to problems.
302            data.append(Table(table_data, style=SLIP_STYLE))
303    return data
304
[7150]305class StudentsUtils(grok.GlobalUtility):
306    """A collection of methods subject to customization.
307    """
308    grok.implements(IStudentsUtils)
[7019]309
[8268]310    def getReturningData(self, student):
[9005]311        """ Define what happens after school fee payment
[7841]312        depending on the student's senate verdict.
313
314        In the base configuration current level is always increased
315        by 100 no matter which verdict has been assigned.
316        """
[8268]317        new_level = student['studycourse'].current_level + 100
318        new_session = student['studycourse'].current_session + 1
319        return new_session, new_level
320
321    def setReturningData(self, student):
[9005]322        """ Define what happens after school fee payment
323        depending on the student's senate verdict.
[8268]324
[9005]325        This method folllows the same algorithm as getReturningData but
326        it also sets the new values.
[8268]327        """
328        new_session, new_level = self.getReturningData(student)
[9922]329        try:
330            student['studycourse'].current_level = new_level
331        except ConstraintNotSatisfied:
332            # Do not change level if level exceeds the
333            # certificate's end_level.
334            pass
[8268]335        student['studycourse'].current_session = new_session
[7615]336        verdict = student['studycourse'].current_verdict
[8820]337        student['studycourse'].current_verdict = '0'
[7615]338        student['studycourse'].previous_verdict = verdict
339        return
340
[9519]341    def _getSessionConfiguration(self, session):
342        try:
343            return grok.getSite()['configuration'][str(session)]
344        except KeyError:
345            return None
346
[9148]347    def setPaymentDetails(self, category, student,
[9151]348            previous_session, previous_level):
[8595]349        """Create Payment object and set the payment data of a student for
350        the payment category specified.
351
[7841]352        """
[8595]353        p_item = u''
354        amount = 0.0
[9148]355        if previous_session:
[9517]356            if previous_session < student['studycourse'].entry_session:
357                return _('The previous session must not fall below '
358                         'your entry session.'), None
359            if category == 'schoolfee':
360                # School fee is always paid for the following session
361                if previous_session > student['studycourse'].current_session:
362                    return _('This is not a previous session.'), None
363            else:
364                if previous_session > student['studycourse'].current_session - 1:
365                    return _('This is not a previous session.'), None
[9148]366            p_session = previous_session
367            p_level = previous_level
368            p_current = False
369        else:
370            p_session = student['studycourse'].current_session
371            p_level = student['studycourse'].current_level
372            p_current = True
[9519]373        academic_session = self._getSessionConfiguration(p_session)
374        if academic_session == None:
[8595]375            return _(u'Session configuration object is not available.'), None
[9521]376        # Determine fee.
[7150]377        if category == 'schoolfee':
[8595]378            try:
[8596]379                certificate = student['studycourse'].certificate
380                p_item = certificate.code
[8595]381            except (AttributeError, TypeError):
382                return _('Study course data are incomplete.'), None
[9148]383            if previous_session:
[9916]384                # Students can pay for previous sessions in all
385                # workflow states.  Fresh students are excluded by the
386                # update method of the PreviousPaymentAddFormPage.
[9148]387                if previous_level == 100:
388                    amount = getattr(certificate, 'school_fee_1', 0.0)
389                else:
390                    amount = getattr(certificate, 'school_fee_2', 0.0)
391            else:
392                if student.state == CLEARED:
393                    amount = getattr(certificate, 'school_fee_1', 0.0)
394                elif student.state == RETURNING:
[9916]395                    # In case of returning school fee payment the
396                    # payment session and level contain the values of
397                    # the session the student has paid for. Payment
398                    # session is always next session.
[9148]399                    p_session, p_level = self.getReturningData(student)
[9519]400                    academic_session = self._getSessionConfiguration(p_session)
401                    if academic_session == None:
[9916]402                        return _(
403                            u'Session configuration object is not available.'
404                            ), None
[9148]405                    amount = getattr(certificate, 'school_fee_2', 0.0)
406                elif student.is_postgrad and student.state == PAID:
[9916]407                    # Returning postgraduate students also pay for the
408                    # next session but their level always remains the
409                    # same.
[9148]410                    p_session += 1
[9519]411                    academic_session = self._getSessionConfiguration(p_session)
412                    if academic_session == None:
[9916]413                        return _(
414                            u'Session configuration object is not available.'
415                            ), None
[9148]416                    amount = getattr(certificate, 'school_fee_2', 0.0)
[7150]417        elif category == 'clearance':
[9178]418            try:
419                p_item = student['studycourse'].certificate.code
420            except (AttributeError, TypeError):
421                return _('Study course data are incomplete.'), None
[8595]422            amount = academic_session.clearance_fee
[7150]423        elif category == 'bed_allocation':
[8595]424            p_item = self.getAccommodationDetails(student)['bt']
425            amount = academic_session.booking_fee
[9423]426        elif category == 'hostel_maintenance':
427            amount = academic_session.maint_fee
[9429]428            bedticket = student['accommodation'].get(
429                str(student.current_session), None)
430            if bedticket:
431                p_item = bedticket.bed_coordinates
432            else:
433                # Should not happen because this is already checked
434                # in the browser module, but anyway ...
435                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
436                p_item = trans(_('no bed allocated'), portal_language)
[8595]437        if amount in (0.0, None):
[9517]438            return _('Amount could not be determined.'), None
[8595]439        for key in student['payments'].keys():
440            ticket = student['payments'][key]
441            if ticket.p_state == 'paid' and\
442               ticket.p_category == category and \
443               ticket.p_item == p_item and \
444               ticket.p_session == p_session:
[9517]445                  return _('This type of payment has already been made.'), None
[8708]446        payment = createObject(u'waeup.StudentOnlinePayment')
[8951]447        timestamp = ("%d" % int(time()*10000))[1:]
[8595]448        payment.p_id = "p%s" % timestamp
449        payment.p_category = category
450        payment.p_item = p_item
451        payment.p_session = p_session
452        payment.p_level = p_level
[9148]453        payment.p_current = p_current
[8595]454        payment.amount_auth = amount
455        return None, payment
[7019]456
[9868]457    def setBalanceDetails(self, category, student,
[9864]458            balance_session, balance_level, balance_amount):
459        """Create Payment object and set the payment data of a student for.
460
461        """
[9868]462        p_item = u'Balance'
[9864]463        p_session = balance_session
464        p_level = balance_level
465        p_current = False
466        amount = balance_amount
467        academic_session = self._getSessionConfiguration(p_session)
468        if academic_session == None:
469            return _(u'Session configuration object is not available.'), None
[9874]470        if amount in (0.0, None) or amount < 0:
471            return _('Amount must be greater than 0.'), None
[9864]472        for key in student['payments'].keys():
473            ticket = student['payments'][key]
474            if ticket.p_state == 'paid' and\
475               ticket.p_category == 'balance' and \
476               ticket.p_item == p_item and \
477               ticket.p_session == p_session:
478                  return _('This type of payment has already been made.'), None
479        payment = createObject(u'waeup.StudentOnlinePayment')
480        timestamp = ("%d" % int(time()*10000))[1:]
481        payment.p_id = "p%s" % timestamp
[9868]482        payment.p_category = category
[9864]483        payment.p_item = p_item
484        payment.p_session = p_session
485        payment.p_level = p_level
486        payment.p_current = p_current
487        payment.amount_auth = amount
488        return None, payment
489
[7186]490    def getAccommodationDetails(self, student):
[9219]491        """Determine the accommodation data of a student.
[7841]492        """
[7150]493        d = {}
494        d['error'] = u''
[8685]495        hostels = grok.getSite()['hostels']
496        d['booking_session'] = hostels.accommodation_session
497        d['allowed_states'] = hostels.accommodation_states
[8688]498        d['startdate'] = hostels.startdate
499        d['enddate'] = hostels.enddate
500        d['expired'] = hostels.expired
[7150]501        # Determine bed type
502        studycourse = student['studycourse']
[7369]503        certificate = getattr(studycourse,'certificate',None)
[7150]504        entry_session = studycourse.entry_session
505        current_level = studycourse.current_level
[9187]506        if None in (entry_session, current_level, certificate):
507            return d
[7369]508        end_level = certificate.end_level
[9148]509        if current_level == 10:
510            bt = 'pr'
511        elif entry_session == grok.getSite()['hostels'].accommodation_session:
[7150]512            bt = 'fr'
513        elif current_level >= end_level:
514            bt = 'fi'
515        else:
516            bt = 're'
517        if student.sex == 'f':
518            sex = 'female'
519        else:
520            sex = 'male'
521        special_handling = 'regular'
522        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
523        return d
[7019]524
[7186]525    def selectBed(self, available_beds):
[7841]526        """Select a bed from a list of available beds.
527
528        In the base configuration we select the first bed found,
529        but can also randomize the selection if we like.
530        """
[7150]531        return available_beds[0]
532
[9191]533    def renderPDFAdmissionLetter(self, view, student=None):
534        """Render pdf admission letter.
535        """
536        if student is None:
537            return
538        style = getSampleStyleSheet()
[9949]539        creator = self.getPDFCreator(student)
[9191]540        data = []
541        doc_title = view.label
542        author = '%s (%s)' % (view.request.principal.title,
543                              view.request.principal.id)
[9944]544        footer_text = view.label.split('\n')
545        if len(footer_text) > 1:
546            # We can add a department in first line
547            footer_text = footer_text[1]
548        else:
549            # Only the first line is used for the footer
550            footer_text = footer_text[0]
[9191]551        if getattr(student, 'student_id', None) is not None:
552            footer_text = "%s - %s - " % (student.student_id, footer_text)
553
554        # Admission text
555        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
556        inst_name = grok.getSite()['configuration'].name
557        text = trans(_(
558            'This is to inform you that you have been provisionally'
559            ' admitted into ${a} as follows:', mapping = {'a': inst_name}),
560            portal_language)
561        html = format_html(text)
562        data.append(Paragraph(html, NOTE_STYLE))
563        data.append(Spacer(1, 20))
564
565        # Student data
566        data.append(render_student_data(view))
567
568        # Insert history
569        data.append(Spacer(1, 20))
570        datelist = student.history.messages[0].split()[0].split('-')
571        creation_date = u'%s/%s/%s' % (datelist[2], datelist[1], datelist[0])
572        text = trans(_(
573            'Your Kofa student record was created on ${a}.',
574            mapping = {'a': creation_date}),
575            portal_language)
576        html = format_html(text)
577        data.append(Paragraph(html, NOTE_STYLE))
578
579        # Create pdf stream
580        view.response.setHeader(
581            'Content-Type', 'application/pdf')
582        pdf_stream = creator.create_pdf(
583            data, None, doc_title, author=author, footer=footer_text,
[9948]584            note=None)
[9191]585        return pdf_stream
586
[9949]587    def getPDFCreator(self, context):
588        """Get a pdf creator suitable for `context`.
589
590        The default implementation always returns the default creator.
591        """
592        return getUtility(IPDFCreator)
593
[8257]594    def renderPDF(self, view, filename='slip.pdf', student=None,
[9906]595                  studentview=None,
596                  tableheader_1=None, tabledata_1=None,
597                  tableheader_2=None, tabledata_2=None,
[9957]598                  tableheader_3=None, tabledata_3=None,
[9555]599                  note=None, signatures=None, sigs_in_footer=(),
[9913]600                  show_scans=True, topMargin=1.5):
[7841]601        """Render pdf slips for various pages.
602        """
[9916]603        # XXX: tell what the different parameters mean
[8112]604        style = getSampleStyleSheet()
[9949]605        creator = self.getPDFCreator(student)
[8112]606        data = []
607        doc_title = view.label
608        author = '%s (%s)' % (view.request.principal.title,
609                              view.request.principal.id)
[9913]610        footer_text = view.label.split('\n')
611        if len(footer_text) > 2:
612            # We can add a department in first line
613            footer_text = footer_text[1]
614        else:
[9917]615            # Only the first line is used for the footer
[9913]616            footer_text = footer_text[0]
[7714]617        if getattr(student, 'student_id', None) is not None:
[7310]618            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]619
[7318]620        # Insert student data table
[7819]621        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7310]622        if student is not None:
[8112]623            bd_translation = trans(_('Base Data'), portal_language)
[9910]624            data.append(Paragraph(bd_translation, HEADING_STYLE))
[8112]625            data.append(render_student_data(studentview))
[7304]626
[7318]627        # Insert widgets
[9191]628        if view.form_fields:
[9910]629            data.append(Paragraph(view.title, HEADING_STYLE))
[9191]630            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
631            separators = getattr(self, 'SEPARATORS_DICT', {})
632            table = creator.getWidgetsTable(
633                view.form_fields, view.context, None, lang=portal_language,
634                separators=separators)
635            data.append(table)
[7318]636
[8112]637        # Insert scanned docs
[9550]638        if show_scans:
639            data.extend(docs_as_flowables(view, portal_language))
[7318]640
[9452]641        # Insert history
[9910]642        if filename.startswith('clearance'):
[9452]643            hist_translation = trans(_('Workflow History'), portal_language)
[9910]644            data.append(Paragraph(hist_translation, HEADING_STYLE))
[9452]645            data.extend(creator.fromStringList(student.history.messages))
646
[9906]647       # Insert 1st content table (optionally on second page)
648        if tabledata_1 and tableheader_1:
[8141]649            #data.append(PageBreak())
[9910]650            #data.append(Spacer(1, 20))
651            data.append(Paragraph(view.content_title_1, HEADING_STYLE))
[9907]652            data.append(Spacer(1, 8))
[9906]653            contenttable = render_table_data(tableheader_1,tabledata_1)
[8112]654            data.append(contenttable)
[7318]655
[9906]656       # Insert 2nd content table (optionally on second page)
657        if tabledata_2 and tableheader_2:
658            #data.append(PageBreak())
[9910]659            #data.append(Spacer(1, 20))
660            data.append(Paragraph(view.content_title_2, HEADING_STYLE))
[9907]661            data.append(Spacer(1, 8))
[9906]662            contenttable = render_table_data(tableheader_2,tabledata_2)
663            data.append(contenttable)
664
[9957]665       # Insert 3rd content table (optionally on second page)
666        if tabledata_3 and tableheader_3:
667            #data.append(PageBreak())
668            #data.append(Spacer(1, 20))
669            data.append(Paragraph(view.content_title_3, HEADING_STYLE))
670            data.append(Spacer(1, 8))
671            contenttable = render_table_data(tableheader_3,tabledata_3)
672            data.append(contenttable)
673
[9010]674        # Insert signatures
[9555]675        if signatures and not sigs_in_footer:
[9010]676            data.append(Spacer(1, 20))
[9014]677            signaturetable = get_signature_table(signatures)
[9010]678            data.append(signaturetable)
679
[7150]680        view.response.setHeader(
681            'Content-Type', 'application/pdf')
[8112]682        try:
683            pdf_stream = creator.create_pdf(
[8257]684                data, None, doc_title, author=author, footer=footer_text,
[9948]685                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
[8112]686        except IOError:
687            view.flash('Error in image file.')
688            return view.redirect(view.url(view.context))
689        return pdf_stream
[7620]690
[9830]691    def maxCredits(self, studylevel):
692        """Return maximum credits.
[9532]693
[9830]694        In some universities maximum credits is not constant, it
695        depends on the student's study level.
696        """
697        return 50
698
[9532]699    def maxCreditsExceeded(self, studylevel, course):
[9830]700        max_credits = self.maxCredits(studylevel)
701        if max_credits and \
702            studylevel.total_credits + course.credits > max_credits:
703            return max_credits
[9532]704        return 0
705
[7841]706    VERDICTS_DICT = {
[8820]707        '0': _('(not yet)'),
[7841]708        'A': 'Successful student',
709        'B': 'Student with carryover courses',
710        'C': 'Student on probation',
711        }
[8099]712
713    SEPARATORS_DICT = {
714        }
[8410]715
716    #: A prefix used when generating new student ids. Each student id will
717    #: start with this string. The default is 'K' for ``Kofa``.
718    STUDENT_ID_PREFIX = u'K'
Note: See TracBrowser for help on using the repository browser.