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

Last change on this file since 9918 was 9917, checked in by uli, 12 years ago

A way to overcrowd forms.

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