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

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

Remove breakpoint and prepare formatted_text() for unit tests.

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