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

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

Ease skipping file upload viewlets in custom packages.

  • Property svn:keywords set to Id
File size: 26.6 KB
RevLine 
[7191]1## $Id: utils.py 10021 2013-03-13 06:16:28Z 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:
[10020]224            if viewlet.file_exists:
225                # Show viewlet only if file exists
226                f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE)
227                img_path = getattr(getUtility(IExtFileStore).getFileByContext(
228                    view.context, attr=viewlet.download_name), 'name', None)
229                #f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE)
230                if img_path is None:
231                    pass
232                elif not img_path[-4:] in ('.jpg', '.JPG'):
233                    # reportlab requires jpg images, I think.
234                    f_text = Paragraph('%s (not displayable)' % (
235                        viewlet.title,), ENTRY1_STYLE)
236                else:
237                    f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound')
238                table_data.append([f_label, f_text])
[8112]239        if table_data:
240            # safety belt; empty tables lead to problems.
241            data.append(Table(table_data, style=SLIP_STYLE))
242    return data
243
[7150]244class StudentsUtils(grok.GlobalUtility):
245    """A collection of methods subject to customization.
246    """
247    grok.implements(IStudentsUtils)
[7019]248
[8268]249    def getReturningData(self, student):
[9005]250        """ Define what happens after school fee payment
[7841]251        depending on the student's senate verdict.
252
253        In the base configuration current level is always increased
254        by 100 no matter which verdict has been assigned.
255        """
[8268]256        new_level = student['studycourse'].current_level + 100
257        new_session = student['studycourse'].current_session + 1
258        return new_session, new_level
259
260    def setReturningData(self, student):
[9005]261        """ Define what happens after school fee payment
262        depending on the student's senate verdict.
[8268]263
[9005]264        This method folllows the same algorithm as getReturningData but
265        it also sets the new values.
[8268]266        """
267        new_session, new_level = self.getReturningData(student)
[9922]268        try:
269            student['studycourse'].current_level = new_level
270        except ConstraintNotSatisfied:
271            # Do not change level if level exceeds the
272            # certificate's end_level.
273            pass
[8268]274        student['studycourse'].current_session = new_session
[7615]275        verdict = student['studycourse'].current_verdict
[8820]276        student['studycourse'].current_verdict = '0'
[7615]277        student['studycourse'].previous_verdict = verdict
278        return
279
[9519]280    def _getSessionConfiguration(self, session):
281        try:
282            return grok.getSite()['configuration'][str(session)]
283        except KeyError:
284            return None
285
[9148]286    def setPaymentDetails(self, category, student,
[9151]287            previous_session, previous_level):
[8595]288        """Create Payment object and set the payment data of a student for
289        the payment category specified.
290
[7841]291        """
[8595]292        p_item = u''
293        amount = 0.0
[9148]294        if previous_session:
[9517]295            if previous_session < student['studycourse'].entry_session:
296                return _('The previous session must not fall below '
297                         'your entry session.'), None
298            if category == 'schoolfee':
299                # School fee is always paid for the following session
300                if previous_session > student['studycourse'].current_session:
301                    return _('This is not a previous session.'), None
302            else:
303                if previous_session > student['studycourse'].current_session - 1:
304                    return _('This is not a previous session.'), None
[9148]305            p_session = previous_session
306            p_level = previous_level
307            p_current = False
308        else:
309            p_session = student['studycourse'].current_session
310            p_level = student['studycourse'].current_level
311            p_current = True
[9519]312        academic_session = self._getSessionConfiguration(p_session)
313        if academic_session == None:
[8595]314            return _(u'Session configuration object is not available.'), None
[9521]315        # Determine fee.
[7150]316        if category == 'schoolfee':
[8595]317            try:
[8596]318                certificate = student['studycourse'].certificate
319                p_item = certificate.code
[8595]320            except (AttributeError, TypeError):
321                return _('Study course data are incomplete.'), None
[9148]322            if previous_session:
[9916]323                # Students can pay for previous sessions in all
324                # workflow states.  Fresh students are excluded by the
325                # update method of the PreviousPaymentAddFormPage.
[9148]326                if previous_level == 100:
327                    amount = getattr(certificate, 'school_fee_1', 0.0)
328                else:
329                    amount = getattr(certificate, 'school_fee_2', 0.0)
330            else:
331                if student.state == CLEARED:
332                    amount = getattr(certificate, 'school_fee_1', 0.0)
333                elif student.state == RETURNING:
[9916]334                    # In case of returning school fee payment the
335                    # payment session and level contain the values of
336                    # the session the student has paid for. Payment
337                    # session is always next session.
[9148]338                    p_session, p_level = self.getReturningData(student)
[9519]339                    academic_session = self._getSessionConfiguration(p_session)
340                    if academic_session == None:
[9916]341                        return _(
342                            u'Session configuration object is not available.'
343                            ), None
[9148]344                    amount = getattr(certificate, 'school_fee_2', 0.0)
345                elif student.is_postgrad and student.state == PAID:
[9916]346                    # Returning postgraduate students also pay for the
347                    # next session but their level always remains the
348                    # same.
[9148]349                    p_session += 1
[9519]350                    academic_session = self._getSessionConfiguration(p_session)
351                    if academic_session == None:
[9916]352                        return _(
353                            u'Session configuration object is not available.'
354                            ), None
[9148]355                    amount = getattr(certificate, 'school_fee_2', 0.0)
[7150]356        elif category == 'clearance':
[9178]357            try:
358                p_item = student['studycourse'].certificate.code
359            except (AttributeError, TypeError):
360                return _('Study course data are incomplete.'), None
[8595]361            amount = academic_session.clearance_fee
[7150]362        elif category == 'bed_allocation':
[8595]363            p_item = self.getAccommodationDetails(student)['bt']
364            amount = academic_session.booking_fee
[9423]365        elif category == 'hostel_maintenance':
366            amount = academic_session.maint_fee
[9429]367            bedticket = student['accommodation'].get(
368                str(student.current_session), None)
369            if bedticket:
370                p_item = bedticket.bed_coordinates
371            else:
372                # Should not happen because this is already checked
373                # in the browser module, but anyway ...
374                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
375                p_item = trans(_('no bed allocated'), portal_language)
[8595]376        if amount in (0.0, None):
[9517]377            return _('Amount could not be determined.'), None
[8595]378        for key in student['payments'].keys():
379            ticket = student['payments'][key]
380            if ticket.p_state == 'paid' and\
381               ticket.p_category == category and \
382               ticket.p_item == p_item and \
383               ticket.p_session == p_session:
[9517]384                  return _('This type of payment has already been made.'), None
[8708]385        payment = createObject(u'waeup.StudentOnlinePayment')
[8951]386        timestamp = ("%d" % int(time()*10000))[1:]
[8595]387        payment.p_id = "p%s" % timestamp
388        payment.p_category = category
389        payment.p_item = p_item
390        payment.p_session = p_session
391        payment.p_level = p_level
[9148]392        payment.p_current = p_current
[8595]393        payment.amount_auth = amount
394        return None, payment
[7019]395
[9868]396    def setBalanceDetails(self, category, student,
[9864]397            balance_session, balance_level, balance_amount):
398        """Create Payment object and set the payment data of a student for.
399
400        """
[9868]401        p_item = u'Balance'
[9864]402        p_session = balance_session
403        p_level = balance_level
404        p_current = False
405        amount = balance_amount
406        academic_session = self._getSessionConfiguration(p_session)
407        if academic_session == None:
408            return _(u'Session configuration object is not available.'), None
[9874]409        if amount in (0.0, None) or amount < 0:
410            return _('Amount must be greater than 0.'), None
[9864]411        for key in student['payments'].keys():
412            ticket = student['payments'][key]
413            if ticket.p_state == 'paid' and\
414               ticket.p_category == 'balance' 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
[9868]421        payment.p_category = category
[9864]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
[7186]429    def getAccommodationDetails(self, student):
[9219]430        """Determine the accommodation data of a student.
[7841]431        """
[7150]432        d = {}
433        d['error'] = u''
[8685]434        hostels = grok.getSite()['hostels']
435        d['booking_session'] = hostels.accommodation_session
436        d['allowed_states'] = hostels.accommodation_states
[8688]437        d['startdate'] = hostels.startdate
438        d['enddate'] = hostels.enddate
439        d['expired'] = hostels.expired
[7150]440        # Determine bed type
441        studycourse = student['studycourse']
[7369]442        certificate = getattr(studycourse,'certificate',None)
[7150]443        entry_session = studycourse.entry_session
444        current_level = studycourse.current_level
[9187]445        if None in (entry_session, current_level, certificate):
446            return d
[7369]447        end_level = certificate.end_level
[9148]448        if current_level == 10:
449            bt = 'pr'
450        elif entry_session == grok.getSite()['hostels'].accommodation_session:
[7150]451            bt = 'fr'
452        elif current_level >= end_level:
453            bt = 'fi'
454        else:
455            bt = 're'
456        if student.sex == 'f':
457            sex = 'female'
458        else:
459            sex = 'male'
460        special_handling = 'regular'
461        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
462        return d
[7019]463
[7186]464    def selectBed(self, available_beds):
[7841]465        """Select a bed from a list of available beds.
466
467        In the base configuration we select the first bed found,
468        but can also randomize the selection if we like.
469        """
[7150]470        return available_beds[0]
471
[9981]472    def _admissionText(self, student, portal_language):
[9979]473        inst_name = grok.getSite()['configuration'].name
474        text = trans(_(
475            'This is to inform you that you have been provisionally'
476            ' admitted into ${a} as follows:', mapping = {'a': inst_name}),
477            portal_language)
478        return text
479
[9191]480    def renderPDFAdmissionLetter(self, view, student=None):
481        """Render pdf admission letter.
482        """
483        if student is None:
484            return
485        style = getSampleStyleSheet()
[9949]486        creator = self.getPDFCreator(student)
[9979]487        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[9191]488        data = []
489        doc_title = view.label
490        author = '%s (%s)' % (view.request.principal.title,
491                              view.request.principal.id)
[9944]492        footer_text = view.label.split('\n')
493        if len(footer_text) > 1:
494            # We can add a department in first line
495            footer_text = footer_text[1]
496        else:
497            # Only the first line is used for the footer
498            footer_text = footer_text[0]
[9191]499        if getattr(student, 'student_id', None) is not None:
500            footer_text = "%s - %s - " % (student.student_id, footer_text)
501
502        # Admission text
[9981]503        html = format_html(self._admissionText(student, portal_language))
[9191]504        data.append(Paragraph(html, NOTE_STYLE))
505        data.append(Spacer(1, 20))
506
507        # Student data
508        data.append(render_student_data(view))
509
510        # Insert history
511        data.append(Spacer(1, 20))
512        datelist = student.history.messages[0].split()[0].split('-')
513        creation_date = u'%s/%s/%s' % (datelist[2], datelist[1], datelist[0])
514        text = trans(_(
515            'Your Kofa student record was created on ${a}.',
516            mapping = {'a': creation_date}),
517            portal_language)
518        html = format_html(text)
519        data.append(Paragraph(html, NOTE_STYLE))
520
521        # Create pdf stream
522        view.response.setHeader(
523            'Content-Type', 'application/pdf')
524        pdf_stream = creator.create_pdf(
525            data, None, doc_title, author=author, footer=footer_text,
[9948]526            note=None)
[9191]527        return pdf_stream
528
[9949]529    def getPDFCreator(self, context):
530        """Get a pdf creator suitable for `context`.
531
532        The default implementation always returns the default creator.
533        """
534        return getUtility(IPDFCreator)
535
[8257]536    def renderPDF(self, view, filename='slip.pdf', student=None,
[9906]537                  studentview=None,
538                  tableheader_1=None, tabledata_1=None,
539                  tableheader_2=None, tabledata_2=None,
[9957]540                  tableheader_3=None, tabledata_3=None,
[9555]541                  note=None, signatures=None, sigs_in_footer=(),
[9913]542                  show_scans=True, topMargin=1.5):
[7841]543        """Render pdf slips for various pages.
544        """
[9916]545        # XXX: tell what the different parameters mean
[8112]546        style = getSampleStyleSheet()
[9949]547        creator = self.getPDFCreator(student)
[8112]548        data = []
549        doc_title = view.label
550        author = '%s (%s)' % (view.request.principal.title,
551                              view.request.principal.id)
[9913]552        footer_text = view.label.split('\n')
553        if len(footer_text) > 2:
554            # We can add a department in first line
555            footer_text = footer_text[1]
556        else:
[9917]557            # Only the first line is used for the footer
[9913]558            footer_text = footer_text[0]
[7714]559        if getattr(student, 'student_id', None) is not None:
[7310]560            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]561
[7318]562        # Insert student data table
[7819]563        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7310]564        if student is not None:
[8112]565            bd_translation = trans(_('Base Data'), portal_language)
[9910]566            data.append(Paragraph(bd_translation, HEADING_STYLE))
[8112]567            data.append(render_student_data(studentview))
[7304]568
[7318]569        # Insert widgets
[9191]570        if view.form_fields:
[9910]571            data.append(Paragraph(view.title, HEADING_STYLE))
[9191]572            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
573            separators = getattr(self, 'SEPARATORS_DICT', {})
574            table = creator.getWidgetsTable(
575                view.form_fields, view.context, None, lang=portal_language,
576                separators=separators)
577            data.append(table)
[7318]578
[8112]579        # Insert scanned docs
[9550]580        if show_scans:
581            data.extend(docs_as_flowables(view, portal_language))
[7318]582
[9452]583        # Insert history
[9910]584        if filename.startswith('clearance'):
[9452]585            hist_translation = trans(_('Workflow History'), portal_language)
[9910]586            data.append(Paragraph(hist_translation, HEADING_STYLE))
[9452]587            data.extend(creator.fromStringList(student.history.messages))
588
[9906]589       # Insert 1st content table (optionally on second page)
590        if tabledata_1 and tableheader_1:
[8141]591            #data.append(PageBreak())
[9910]592            #data.append(Spacer(1, 20))
593            data.append(Paragraph(view.content_title_1, HEADING_STYLE))
[9907]594            data.append(Spacer(1, 8))
[9906]595            contenttable = render_table_data(tableheader_1,tabledata_1)
[8112]596            data.append(contenttable)
[7318]597
[9906]598       # Insert 2nd content table (optionally on second page)
599        if tabledata_2 and tableheader_2:
600            #data.append(PageBreak())
[9910]601            #data.append(Spacer(1, 20))
602            data.append(Paragraph(view.content_title_2, HEADING_STYLE))
[9907]603            data.append(Spacer(1, 8))
[9906]604            contenttable = render_table_data(tableheader_2,tabledata_2)
605            data.append(contenttable)
606
[9957]607       # Insert 3rd content table (optionally on second page)
608        if tabledata_3 and tableheader_3:
609            #data.append(PageBreak())
610            #data.append(Spacer(1, 20))
611            data.append(Paragraph(view.content_title_3, HEADING_STYLE))
612            data.append(Spacer(1, 8))
613            contenttable = render_table_data(tableheader_3,tabledata_3)
614            data.append(contenttable)
615
[9010]616        # Insert signatures
[9965]617        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
618        # do not have a test for the following lines.
[9555]619        if signatures and not sigs_in_footer:
[9010]620            data.append(Spacer(1, 20))
[9966]621            # Render one signature table per signature to
622            # get date and signature in line.
623            for signature in signatures:
624                signaturetables = get_signature_tables(signature)
625                data.append(signaturetables[0])
[9010]626
[7150]627        view.response.setHeader(
628            'Content-Type', 'application/pdf')
[8112]629        try:
630            pdf_stream = creator.create_pdf(
[8257]631                data, None, doc_title, author=author, footer=footer_text,
[9948]632                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
[8112]633        except IOError:
634            view.flash('Error in image file.')
635            return view.redirect(view.url(view.context))
636        return pdf_stream
[7620]637
[9830]638    def maxCredits(self, studylevel):
639        """Return maximum credits.
[9532]640
[9830]641        In some universities maximum credits is not constant, it
642        depends on the student's study level.
643        """
644        return 50
645
[9532]646    def maxCreditsExceeded(self, studylevel, course):
[9830]647        max_credits = self.maxCredits(studylevel)
648        if max_credits and \
649            studylevel.total_credits + course.credits > max_credits:
650            return max_credits
[9532]651        return 0
652
[9987]653    def getBedCoordinates(self, bedticket):
654        """Return bed coordinates.
655
656        This method can be used to customize the display_coordinates
657        property method.
658        """
659        return bedticket.bed_coordinates
660
[7841]661    VERDICTS_DICT = {
[8820]662        '0': _('(not yet)'),
[7841]663        'A': 'Successful student',
664        'B': 'Student with carryover courses',
665        'C': 'Student on probation',
666        }
[8099]667
668    SEPARATORS_DICT = {
669        }
[8410]670
[10021]671    SKIP_UPLOAD_VIEWLETS = ()
672
[8410]673    #: A prefix used when generating new student ids. Each student id will
674    #: start with this string. The default is 'K' for ``Kofa``.
675    STUDENT_ID_PREFIX = u'K'
Note: See TracBrowser for help on using the repository browser.