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

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

Put logic of customization of display_item and display_bedcoordinates into global utility methods. This simplifies customization.

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