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

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

Condense all pdf slips. Re-use ENTRY1_STYLE in render_student_data.

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