source: main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py @ 7539

Last change on this file since 7539 was 7511, checked in by uli, 13 years ago

Make formatted_text a bit smarter regarding to encodings. It now
returns a unicode string instead of raw strings. I stumbled
over some broken PDF generations when names of students contained
umlauts. Hope, this will fix this without breaking other things. At
least reportlab is able to cope with unicode strings.

  • Property svn:keywords set to Id
File size: 13.2 KB
RevLine 
[7191]1## $Id: utils.py 7511 2012-01-25 14:25:17Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[7358]18"""General helper functions and utilities for the student section.
[6651]19"""
[7150]20import grok
[6662]21from random import SystemRandom as r
[7256]22from datetime import datetime
[7019]23from reportlab.pdfgen import canvas
[7318]24from reportlab.lib import colors
[7019]25from reportlab.lib.units import cm
[7310]26from reportlab.lib.enums import TA_RIGHT
[7019]27from reportlab.lib.pagesizes import A4
[7310]28from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
[7019]29from reportlab.platypus import (Frame, Paragraph, Image,
30    Table, Spacer)
31from reportlab.platypus.tables import TableStyle
[7310]32from reportlab.platypus.flowables import PageBreak
[7280]33from zope.component import getUtility
[7019]34from zope.formlib.form import setUpEditWidgets
[7280]35from waeup.sirp.interfaces import IExtFileStore
[7150]36from waeup.sirp.students.interfaces import IStudentsUtils
[6651]37
[7318]38SLIP_STYLE = [
39    ('VALIGN',(0,0),(-1,-1),'TOP'),
40    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
41    ]
[7019]42
[7318]43CONTENT_STYLE = [
44    ('VALIGN',(0,0),(-1,-1),'TOP'),
45    #('FONT', (0,0), (-1,-1), 'Helvetica', 8),
46    #('TEXTCOLOR',(0,0),(-1,0),colors.white),
47    ('BACKGROUND',(0,0),(-1,0),colors.black),
48    ]
[7304]49
[7318]50FONT_SIZE = 10
51FONT_COLOR = 'black'
52
[7319]53def formatted_label(color=FONT_COLOR, size=FONT_SIZE):
[7318]54    tag1 ='<font color=%s size=%d>' % (color, size)
[7319]55    return tag1 + '%s:</font>'
56
[7511]57def formatted_text(text, color=FONT_COLOR, size=FONT_SIZE):
58    """Turn `text`, `color` and `size` into an HTML snippet.
[7318]59
[7511]60    The snippet is suitable for use with reportlab and generating PDFs.
61    Wraps the `text` into a ``<font>`` tag with passed attributes.
62
63    Also non-strings are converted. Raw strings are expected to be
64    utf-8 encoded (usually the case for widgets etc.).
65
66    The returned snippet is unicode type.
67    """
68    if not isinstance(text, unicode):
69        if isinstance(text, basestring):
70            text = text.decode('utf-8')
71        else:
72            text = unicode(text)
73    tag1 = u'<font color="%s" size="%d">' % (color, size)
74    return tag1 + u'%s</font>' % text
75
[6749]76def generate_student_id(students,letter):
[6651]77    if letter == '?':
[6664]78        letter= r().choice('ABCDEFGHKLMNPQRSTUVWXY')
79    sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6749]80    while sid in students.keys():
[6664]81        sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6662]82    return sid
[6742]83
84def set_returning_data(student):
85    student['studycourse'].current_level += 100
86    student['studycourse'].current_session += 1
87    verdict = student['studycourse'].current_verdict
[6804]88    student['studycourse'].current_verdict = '0'
[6742]89    student['studycourse'].previous_verdict = verdict
90    return
[6876]91
[7186]92def set_up_widgets(view, ignore_request=False):
[7019]93    view.adapters = {}
94    view.widgets = setUpEditWidgets(
95        view.form_fields, view.prefix, view.context, view.request,
96        adapters=view.adapters, for_display=True,
97        ignore_request=ignore_request
98        )
99
[7310]100def render_student_data(studentview):
[7318]101    """Render student table for an existing frame.
102    """
103    width, height = A4
[7186]104    set_up_widgets(studentview, ignore_request=True)
[7318]105    data_left = []
106    data_right = []
[7019]107    style = getSampleStyleSheet()
[7280]108    img = getUtility(IExtFileStore).getFileByContext(
109        studentview.context, attr='passport.jpg')
110    if img is None:
111        from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
112        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]113    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
114    data_left.append([doc_img])
115    #data.append([Spacer(1, 12)])
[7019]116    for widget in studentview.widgets:
117        if widget.name == 'form.adm_code':
118            continue
[7319]119        f_label = formatted_label() % widget.label.strip()
[7019]120        f_label = Paragraph(f_label, style["Normal"])
[7511]121        f_text = formatted_text(widget())
[7019]122        f_text = Paragraph(f_text, style["Normal"])
[7318]123        data_right.append([f_label,f_text])
124    table_left = Table(data_left,style=SLIP_STYLE)
125    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[4*cm, 10*cm])
126    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]127    return table
128
[7304]129def render_table_data(tableheader,tabledata):
[7318]130    """Render children table for an existing frame.
131    """
[7304]132    data = []
[7318]133    #data.append([Spacer(1, 12)])
[7304]134    line = []
135    style = getSampleStyleSheet()
136    for element in tableheader:
[7511]137        field = formatted_text(element[0], color='white')
[7310]138        field = Paragraph(field, style["Normal"])
[7304]139        line.append(field)
140    data.append(line)
141    for ticket in tabledata:
142        line = []
143        for element in tableheader:
[7511]144              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]145              field = Paragraph(field, style["Normal"])
[7304]146              line.append(field)
147        data.append(line)
[7310]148    table = Table(data,colWidths=[
[7318]149        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]150    return table
151
[7318]152
153def insert_footer(pdf,width,style,text=None, number_of_pages=1):
154      """Render the whole footer frame.
155      """
156      story = []
157      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
158      timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
159      left_text = '<font size=10>%s</font>' % timestamp
160      story.append(Paragraph(left_text, style["Normal"]))
161      frame_footer.addFromList(story,pdf)
162      story = []
163      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
164      right_text = '<font size=10>%s Page %s of %s</font>' % (
165          text, pdf.getPageNumber(), number_of_pages)
166      story.append(Paragraph(right_text, style["Right"]))
167      frame_footer.addFromList(story,pdf)
168
[7150]169class StudentsUtils(grok.GlobalUtility):
170    """A collection of methods subject to customization.
171    """
172    grok.implements(IStudentsUtils)
[7019]173
[7186]174    def getPaymentDetails(self,category, student):
[7150]175        d = {}
176        d['p_item'] = u''
177        d['amount'] = 0
178        d['error'] = u''
179        d['p_session'] = student['studycourse'].current_session
180        session = str(d['p_session'])
181        try:
182            academic_session = grok.getSite()['configuration'][session]
183        except KeyError:
184            d['error'] = u'Session configuration object is not available.'
185            return d
186        d['surcharge_1'] = academic_session.surcharge_1
187        d['surcharge_2'] = academic_session.surcharge_2
188        d['surcharge_3'] = academic_session.surcharge_3
189        if category == 'schoolfee':
190            d['amount'] = academic_session.school_fee_base
191            d['p_item'] = student['studycourse'].certificate.code
192        elif category == 'clearance':
193            d['p_item'] = student['studycourse'].certificate.code
194            d['amount'] = academic_session.clearance_fee
195        elif category == 'bed_allocation':
[7186]196            d['p_item'] = self.getAccommodationDetails(student)['bt']
[7150]197            d['amount'] = academic_session.booking_fee
198        return d
[7019]199
[7186]200    def getAccommodationDetails(self, student):
[7150]201        d = {}
202        d['error'] = u''
[7365]203        site_configuration = grok.getSite()['configuration']
204        d['booking_session'] = site_configuration.accommodation_session
205        d['allowed_states'] = site_configuration.accommodation_states
[7150]206        # Determine bed type
207        studycourse = student['studycourse']
[7369]208        certificate = getattr(studycourse,'certificate',None)
[7150]209        entry_session = studycourse.entry_session
210        current_level = studycourse.current_level
[7369]211        if not (entry_session and current_level and certificate):
212            return
213        end_level = certificate.end_level
[7150]214        if entry_session == grok.getSite()['configuration'].accommodation_session:
215            bt = 'fr'
216        elif current_level >= end_level:
217            bt = 'fi'
218        else:
219            bt = 're'
220        if student.sex == 'f':
221            sex = 'female'
222        else:
223            sex = 'male'
224        special_handling = 'regular'
225        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
226        return d
[7019]227
[7150]228    # In the standard configuration we select the first bed found,
229    # but can also randomize the selection if we like.
[7186]230    def selectBed(self, available_beds):
[7150]231        return available_beds[0]
232
[7318]233    def renderPDF(self, view, filename='slip.pdf',
[7304]234        student=None, studentview=None, tableheader=None, tabledata=None):
[7150]235        # (0,0),(-1,-1) = whole table
236        # (0,0),(0,-1) = first column
237        # (-1,0),(-1,-1) = last column
238        # (0,0),(-1,0) = first row
239        # (0,-1),(-1,-1) = last row
240
241        pdf = canvas.Canvas(filename,pagesize=A4)
242        pdf.setTitle(view.label)
[7318]243        pdf.setSubject(view.title)
[7150]244        pdf.setAuthor('%s (%s)' % (view.request.principal.title,
245            view.request.principal.id))
246        pdf.setCreator('WAeUP SIRP')
247        width, height = A4
[7310]248        footer_text = view.label
249        if student is not None:
250            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]251        style = getSampleStyleSheet()
[7310]252        style.add(ParagraphStyle(name='Right', alignment=TA_RIGHT))
[7150]253        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
254
255        story = []
256        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
257        header_title = getattr(grok.getSite(), 'name', u'Sample University')
258        story.append(Paragraph(header_title, style["Heading1"]))
259        frame_header.addFromList(story,pdf)
260
[7318]261        # Insert student data table
[7150]262        story = []
263        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
264        story.append(Paragraph(view.label, style["Heading2"]))
[7310]265        if student is not None:
[7318]266            #story.append(Spacer(1, 12))
267            story.append(Paragraph('Student Base Data', style["Heading3"]))
[7310]268            studenttable = render_student_data(studentview)
[7150]269            story.append(studenttable)
[7304]270
[7318]271        # Insert widgets
272        story.append(Paragraph(view.title, style["Heading3"]))
[7186]273        set_up_widgets(view)
[7150]274        data = []
275        for widget in view.widgets:
[7319]276            f_label = formatted_label() % widget.label.strip()
[7150]277            f_label = Paragraph(f_label, style["Normal"])
[7511]278            f_text = formatted_text(widget())
[7150]279            f_text = Paragraph(f_text, style["Normal"])
280            data.append([f_label,f_text])
[7318]281        table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
282        story.append(table)
283
284        # Import browser components locally
[7280]285        # to avoid circular import
286        from waeup.sirp.students.viewlets import FileManager
287        from waeup.sirp.browser import DEFAULT_IMAGE_PATH
[7318]288        data = []
289        # Collect viewlets
[7280]290        fm = FileManager(view.context, view.request, view)
291        fm.update()
[7318]292        if fm.viewlets:
293            story.append(Paragraph('Scanned Documents', style["Heading3"]))
294            # Insert list of scanned documents
295            for viewlet in fm.viewlets:
[7319]296                f_label = formatted_label() % viewlet.label
[7318]297                f_label = Paragraph(f_label, style["Normal"])
298                if viewlet.template.__grok_name__ == 'imagedisplay':
299                    # In case we define FileDisplay viewlets with
300                    # an imagedisplay template in the customization package
301                    img = getUtility(IExtFileStore).getFileByContext(
302                        view.context, attr=viewlet.download_name)
303                    if img is None:
304                        img = open(DEFAULT_IMAGE_PATH, 'rb')
305                    doc_img = Image(img.name, width=2*cm, height=1*cm, kind='bound')
306                    data.append([f_label,doc_img])
307                else:
[7511]308                    f_text = formatted_text(viewlet.title)
[7318]309                    f_text = Paragraph(f_text, style["Normal"])
310                    data.append([f_label,f_text])
311            table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
312            story.append(table)
313
[7280]314        try:
315            frame_body.addFromList(story,pdf)
316        except IOError:
317            view.flash('Error in image file.')
318            return view.redirect(view.url(view.context))
[7318]319
[7304]320        if tabledata and tableheader:
[7318]321            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
322        else:
323            insert_footer(pdf,width,style,footer_text)
324
325        # Insert content table on second page
326        if tabledata and tableheader:
327            pdf.showPage()
328            frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(2*cm))
[7304]329            story = []
[7318]330            story.append(Paragraph(view.content_title, style["Heading3"]))
[7310]331            #story.append(Spacer(1, 12))
[7304]332            contenttable = render_table_data(tableheader,tabledata)
333            story.append(contenttable)
334            frame_body.addFromList(story,pdf)
[7318]335            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
336
[7150]337        view.response.setHeader(
338            'Content-Type', 'application/pdf')
339        return pdf.getpdfdata()
Note: See TracBrowser for help on using the repository browser.