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

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

Test if the transcript pdf slip can be opened.

Add date_of_birth to base data.

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