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

Last change on this file since 11476 was 11452, checked in by Henrik Bettermann, 11 years ago

Only school fee payments are not allowed.

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