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

Last change on this file since 8132 was 8120, checked in by Henrik Bettermann, 13 years ago

Localize 'not provided' mesaage.

  • Property svn:keywords set to Id
File size: 13.8 KB
RevLine 
[7191]1## $Id: utils.py 8120 2012-04-12 09:00:56Z 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
[6662]21from random import SystemRandom as r
[7256]22from datetime import datetime
[7714]23from zope.i18n import translate
24from zope.component import getUtility
[7019]25from reportlab.pdfgen import canvas
[7318]26from reportlab.lib import colors
[7019]27from reportlab.lib.units import cm
[7310]28from reportlab.lib.enums import TA_RIGHT
[7019]29from reportlab.lib.pagesizes import A4
[7310]30from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
[8112]31from reportlab.platypus import (Frame, Paragraph, Image, PageBreak, Table,
32                                Spacer)
[7019]33from reportlab.platypus.tables import TableStyle
[7310]34from reportlab.platypus.flowables import PageBreak
[7280]35from zope.component import getUtility
[7019]36from zope.formlib.form import setUpEditWidgets
[8112]37
[7819]38from waeup.kofa.interfaces import IExtFileStore, IKofaUtils
[7811]39from waeup.kofa.interfaces import MessageFactory as _
40from waeup.kofa.students.interfaces import IStudentsUtils
[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),
51    ('BACKGROUND',(0,0),(-1,0),colors.black),
52    ]
[7304]53
[7318]54FONT_SIZE = 10
55FONT_COLOR = 'black'
56
[7319]57def formatted_label(color=FONT_COLOR, size=FONT_SIZE):
[7318]58    tag1 ='<font color=%s size=%d>' % (color, size)
[7319]59    return tag1 + '%s:</font>'
60
[8112]61def trans(text, lang):
62    # shortcut
63    return translate(text, 'waeup.kofa', target_language=lang)
64
[7511]65def formatted_text(text, color=FONT_COLOR, size=FONT_SIZE):
66    """Turn `text`, `color` and `size` into an HTML snippet.
[7318]67
[7511]68    The snippet is suitable for use with reportlab and generating PDFs.
69    Wraps the `text` into a ``<font>`` tag with passed attributes.
70
71    Also non-strings are converted. Raw strings are expected to be
72    utf-8 encoded (usually the case for widgets etc.).
73
[7804]74    Finally, a br tag is added if widgets contain div tags
75    which are not supported by reportlab.
76
[7511]77    The returned snippet is unicode type.
78    """
79    if not isinstance(text, unicode):
80        if isinstance(text, basestring):
81            text = text.decode('utf-8')
82        else:
83            text = unicode(text)
[7804]84    text = text.replace('</div>', '<br /></div>')
[7511]85    tag1 = u'<font color="%s" size="%d">' % (color, size)
86    return tag1 + u'%s</font>' % text
87
[6749]88def generate_student_id(students,letter):
[6651]89    if letter == '?':
[6664]90        letter= r().choice('ABCDEFGHKLMNPQRSTUVWXY')
91    sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6749]92    while sid in students.keys():
[6664]93        sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6662]94    return sid
[6742]95
[7186]96def set_up_widgets(view, ignore_request=False):
[7019]97    view.adapters = {}
98    view.widgets = setUpEditWidgets(
99        view.form_fields, view.prefix, view.context, view.request,
100        adapters=view.adapters, for_display=True,
101        ignore_request=ignore_request
102        )
103
[7310]104def render_student_data(studentview):
[7318]105    """Render student table for an existing frame.
106    """
107    width, height = A4
[7186]108    set_up_widgets(studentview, ignore_request=True)
[7318]109    data_left = []
110    data_right = []
[7019]111    style = getSampleStyleSheet()
[7280]112    img = getUtility(IExtFileStore).getFileByContext(
113        studentview.context, attr='passport.jpg')
114    if img is None:
[7811]115        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
[7280]116        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]117    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
118    data_left.append([doc_img])
119    #data.append([Spacer(1, 12)])
[7819]120    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7019]121    for widget in studentview.widgets:
122        if widget.name == 'form.adm_code':
123            continue
[7714]124        f_label = formatted_label(size=12) % translate(
[7811]125            widget.label.strip(), 'waeup.kofa',
[7714]126            target_language=portal_language)
[7019]127        f_label = Paragraph(f_label, style["Normal"])
[7714]128        f_text = formatted_text(widget(), size=12)
[7019]129        f_text = Paragraph(f_text, style["Normal"])
[7318]130        data_right.append([f_label,f_text])
131    table_left = Table(data_left,style=SLIP_STYLE)
[8112]132    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
[7318]133    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]134    return table
135
[7304]136def render_table_data(tableheader,tabledata):
[7318]137    """Render children table for an existing frame.
138    """
[7304]139    data = []
[7318]140    #data.append([Spacer(1, 12)])
[7304]141    line = []
142    style = getSampleStyleSheet()
143    for element in tableheader:
[7511]144        field = formatted_text(element[0], color='white')
[7310]145        field = Paragraph(field, style["Normal"])
[7304]146        line.append(field)
147    data.append(line)
148    for ticket in tabledata:
149        line = []
150        for element in tableheader:
[7511]151              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]152              field = Paragraph(field, style["Normal"])
[7304]153              line.append(field)
154        data.append(line)
[7310]155    table = Table(data,colWidths=[
[7318]156        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]157    return table
158
[8112]159def docs_as_flowables(view, lang='en'):
160    """Create reportlab flowables out of scanned docs.
161    """
162    # XXX: fix circular import problem
163    from waeup.kofa.students.viewlets import FileManager
164    from waeup.kofa.browser import DEFAULT_IMAGE_PATH
165    from waeup.kofa.browser.pdf import NORMAL_STYLE, ENTRY1_STYLE
166    style = getSampleStyleSheet()
167    data = []
[7318]168
[8112]169    # Collect viewlets
170    fm = FileManager(view.context, view.request, view)
171    fm.update()
172    if fm.viewlets:
173        sc_translation = trans(_('Scanned Documents'), lang)
174        data.append(Paragraph(sc_translation, style["Heading3"]))
175        # Insert list of scanned documents
176        table_data = []
177        for viewlet in fm.viewlets:
178            f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE)
179            img_path = getattr(getUtility(IExtFileStore).getFileByContext(
180                view.context, attr=viewlet.download_name), 'name', None)
[8120]181            f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE)
[8112]182            if img_path is None:
183                pass
184            elif not img_path.endswith('.jpg'):
185                # reportlab requires jpg images, I think.
186                f_text = Paragraph('%s (Not displayable)' % (
187                    viewlet.title,), ENTRY1_STYLE)
188            else:
[8117]189                f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound')
[8112]190            table_data.append([f_label, f_text])
191        if table_data:
192            # safety belt; empty tables lead to problems.
193            data.append(Table(table_data, style=SLIP_STYLE))
194    return data
195
[7318]196def insert_footer(pdf,width,style,text=None, number_of_pages=1):
197      """Render the whole footer frame.
198      """
199      story = []
200      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
201      timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
202      left_text = '<font size=10>%s</font>' % timestamp
203      story.append(Paragraph(left_text, style["Normal"]))
204      frame_footer.addFromList(story,pdf)
205      story = []
206      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
[7819]207      portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7714]208      right_text = translate(_('<font size=10>${a} Page ${b} of ${c}</font>',
209          mapping = {'a':text, 'b':pdf.getPageNumber(), 'c':number_of_pages}),
[7811]210          'waeup.kofa', target_language=portal_language)
[7318]211      story.append(Paragraph(right_text, style["Right"]))
212      frame_footer.addFromList(story,pdf)
213
[7150]214class StudentsUtils(grok.GlobalUtility):
215    """A collection of methods subject to customization.
216    """
217    grok.implements(IStudentsUtils)
[7019]218
[7615]219    def setReturningData(self, student):
[7841]220        """ This method defines what happens after school fee payment
221        depending on the student's senate verdict.
222
223        In the base configuration current level is always increased
224        by 100 no matter which verdict has been assigned.
225        """
[7615]226        student['studycourse'].current_level += 100
227        student['studycourse'].current_session += 1
228        verdict = student['studycourse'].current_verdict
229        student['studycourse'].current_verdict = '0'
230        student['studycourse'].previous_verdict = verdict
231        return
232
233    def getPaymentDetails(self, category, student):
[7841]234        """Get the payment dates of a student for the payment category
235        specified.
236        """
[7150]237        d = {}
238        d['p_item'] = u''
[7927]239        d['amount'] = 0.0
[7150]240        d['error'] = u''
241        d['p_session'] = student['studycourse'].current_session
242        session = str(d['p_session'])
243        try:
244            academic_session = grok.getSite()['configuration'][session]
245        except KeyError:
246            d['error'] = u'Session configuration object is not available.'
247            return d
248        d['surcharge_1'] = academic_session.surcharge_1
249        d['surcharge_2'] = academic_session.surcharge_2
250        d['surcharge_3'] = academic_session.surcharge_3
251        if category == 'schoolfee':
252            d['amount'] = academic_session.school_fee_base
253            d['p_item'] = student['studycourse'].certificate.code
254        elif category == 'clearance':
255            d['p_item'] = student['studycourse'].certificate.code
256            d['amount'] = academic_session.clearance_fee
[7927]257            d['surcharge_1'] = 0.0 # no portal fee
[7150]258        elif category == 'bed_allocation':
[7186]259            d['p_item'] = self.getAccommodationDetails(student)['bt']
[7150]260            d['amount'] = academic_session.booking_fee
[7927]261            d['surcharge_1'] = 0.0 # no portal fee
[7150]262        return d
[7019]263
[7186]264    def getAccommodationDetails(self, student):
[7841]265        """Determine the accommodation dates of a student.
266        """
[7150]267        d = {}
268        d['error'] = u''
[7365]269        site_configuration = grok.getSite()['configuration']
270        d['booking_session'] = site_configuration.accommodation_session
271        d['allowed_states'] = site_configuration.accommodation_states
[7150]272        # Determine bed type
273        studycourse = student['studycourse']
[7369]274        certificate = getattr(studycourse,'certificate',None)
[7150]275        entry_session = studycourse.entry_session
276        current_level = studycourse.current_level
[7369]277        if not (entry_session and current_level and certificate):
278            return
279        end_level = certificate.end_level
[8112]280        if entry_session == grok.getSite()[
281            'configuration'].accommodation_session:
[7150]282            bt = 'fr'
283        elif current_level >= end_level:
284            bt = 'fi'
285        else:
286            bt = 're'
287        if student.sex == 'f':
288            sex = 'female'
289        else:
290            sex = 'male'
291        special_handling = 'regular'
292        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
293        return d
[7019]294
[7186]295    def selectBed(self, available_beds):
[7841]296        """Select a bed from a list of available beds.
297
298        In the base configuration we select the first bed found,
299        but can also randomize the selection if we like.
300        """
[7150]301        return available_beds[0]
302
[7318]303    def renderPDF(self, view, filename='slip.pdf',
[7304]304        student=None, studentview=None, tableheader=None, tabledata=None):
[7841]305        """Render pdf slips for various pages.
306        """
[8112]307        # XXX: we have to fix the import problems here.
308        from waeup.kofa.browser.interfaces import IPDFCreator
309        from waeup.kofa.browser.pdf import NORMAL_STYLE, ENTRY1_STYLE
310        style = getSampleStyleSheet()
311        creator = getUtility(IPDFCreator)
312        data = []
313        doc_title = view.label
314        author = '%s (%s)' % (view.request.principal.title,
315                              view.request.principal.id)
[7310]316        footer_text = view.label
[7714]317        if getattr(student, 'student_id', None) is not None:
[7310]318            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]319
[7318]320        # Insert student data table
[7819]321        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7310]322        if student is not None:
[8112]323            bd_translation = trans(_('Base Data'), portal_language)
324            data.append(Paragraph(bd_translation, style["Heading3"]))
325            data.append(render_student_data(studentview))
[7304]326
[7318]327        # Insert widgets
[8112]328        data.append(Paragraph(view.title, style["Heading3"]))
[7819]329        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[8112]330        table = creator.getWidgetsTable(
331            view.form_fields, view.context, None, lang=portal_language)
332        data.append(table)
[7318]333
[8112]334        # Insert scanned docs
335        data.extend(docs_as_flowables(view, portal_language))
[7318]336
337        # Insert content table on second page
338        if tabledata and tableheader:
[8112]339            data.append(PageBreak())
340            data.append(Paragraph(view.content_title, style["Heading3"]))
[7304]341            contenttable = render_table_data(tableheader,tabledata)
[8112]342            data.append(contenttable)
[7318]343
[7150]344        view.response.setHeader(
345            'Content-Type', 'application/pdf')
[8112]346        try:
347            pdf_stream = creator.create_pdf(
348                data, None, doc_title, author=author, footer=footer_text)
349        except IOError:
350            view.flash('Error in image file.')
351            return view.redirect(view.url(view.context))
352        return pdf_stream
[7620]353
[7841]354    VERDICTS_DICT = {
[7993]355        '0': _('(not yet)'),
[7841]356        'A': 'Successful student',
357        'B': 'Student with carryover courses',
358        'C': 'Student on probation',
359        }
[8099]360
361    SEPARATORS_DICT = {
362        }
Note: See TracBrowser for help on using the repository browser.