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

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

Insert a br tag if widgets contain div tags which are not supported by reportlab.

  • Property svn:keywords set to Id
File size: 15.6 KB
Line 
1## $Id: utils.py 7804 2012-03-08 16:30:34Z 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##
18"""General helper functions and utilities for the student section.
19"""
20import grok
21from random import SystemRandom as r
22from datetime import datetime
23from zope.i18n import translate
24from zope.component import getUtility
25from reportlab.pdfgen import canvas
26from reportlab.lib import colors
27from reportlab.lib.units import cm
28from reportlab.lib.enums import TA_RIGHT
29from reportlab.lib.pagesizes import A4
30from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
31from reportlab.platypus import (Frame, Paragraph, Image,
32    Table, Spacer)
33from reportlab.platypus.tables import TableStyle
34from reportlab.platypus.flowables import PageBreak
35from zope.component import getUtility
36from zope.formlib.form import setUpEditWidgets
37from waeup.sirp.interfaces import IExtFileStore, ISIRPUtils
38from waeup.sirp.interfaces import MessageFactory as _
39from waeup.sirp.students.interfaces import IStudentsUtils
40
41SLIP_STYLE = [
42    ('VALIGN',(0,0),(-1,-1),'TOP'),
43    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
44    ]
45
46CONTENT_STYLE = [
47    ('VALIGN',(0,0),(-1,-1),'TOP'),
48    #('FONT', (0,0), (-1,-1), 'Helvetica', 8),
49    #('TEXTCOLOR',(0,0),(-1,0),colors.white),
50    ('BACKGROUND',(0,0),(-1,0),colors.black),
51    ]
52
53FONT_SIZE = 10
54FONT_COLOR = 'black'
55
56def formatted_label(color=FONT_COLOR, size=FONT_SIZE):
57    tag1 ='<font color=%s size=%d>' % (color, size)
58    return tag1 + '%s:</font>'
59
60def formatted_text(text, color=FONT_COLOR, size=FONT_SIZE):
61    """Turn `text`, `color` and `size` into an HTML snippet.
62
63    The snippet is suitable for use with reportlab and generating PDFs.
64    Wraps the `text` into a ``<font>`` tag with passed attributes.
65
66    Also non-strings are converted. Raw strings are expected to be
67    utf-8 encoded (usually the case for widgets etc.).
68
69    Finally, a br tag is added if widgets contain div tags
70    which are not supported by reportlab.
71
72    The returned snippet is unicode type.
73    """
74    if not isinstance(text, unicode):
75        if isinstance(text, basestring):
76            text = text.decode('utf-8')
77        else:
78            text = unicode(text)
79    text = text.replace('</div>', '<br /></div>')
80    tag1 = u'<font color="%s" size="%d">' % (color, size)
81    return tag1 + u'%s</font>' % text
82
83def generate_student_id(students,letter):
84    if letter == '?':
85        letter= r().choice('ABCDEFGHKLMNPQRSTUVWXY')
86    sid = u"%c%d" % (letter,r().randint(99999,1000000))
87    while sid in students.keys():
88        sid = u"%c%d" % (letter,r().randint(99999,1000000))
89    return sid
90
91def set_up_widgets(view, ignore_request=False):
92    view.adapters = {}
93    view.widgets = setUpEditWidgets(
94        view.form_fields, view.prefix, view.context, view.request,
95        adapters=view.adapters, for_display=True,
96        ignore_request=ignore_request
97        )
98
99def render_student_data(studentview):
100    """Render student table for an existing frame.
101    """
102    width, height = A4
103    set_up_widgets(studentview, ignore_request=True)
104    data_left = []
105    data_right = []
106    style = getSampleStyleSheet()
107    img = getUtility(IExtFileStore).getFileByContext(
108        studentview.context, attr='passport.jpg')
109    if img is None:
110        from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
111        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
112    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
113    data_left.append([doc_img])
114    #data.append([Spacer(1, 12)])
115    portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
116    for widget in studentview.widgets:
117        if widget.name == 'form.adm_code':
118            continue
119        f_label = formatted_label(size=12) % translate(
120            widget.label.strip(), 'waeup.sirp',
121            target_language=portal_language)
122        f_label = Paragraph(f_label, style["Normal"])
123        f_text = formatted_text(widget(), size=12)
124        f_text = Paragraph(f_text, style["Normal"])
125        data_right.append([f_label,f_text])
126    table_left = Table(data_left,style=SLIP_STYLE)
127    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[6*cm, 8*cm])
128    table = Table([[table_left, table_right],],style=SLIP_STYLE)
129    return table
130
131def render_table_data(tableheader,tabledata):
132    """Render children table for an existing frame.
133    """
134    data = []
135    #data.append([Spacer(1, 12)])
136    line = []
137    style = getSampleStyleSheet()
138    for element in tableheader:
139        field = formatted_text(element[0], color='white')
140        field = Paragraph(field, style["Normal"])
141        line.append(field)
142    data.append(line)
143    for ticket in tabledata:
144        line = []
145        for element in tableheader:
146              field = formatted_text(getattr(ticket,element[1],u' '))
147              field = Paragraph(field, style["Normal"])
148              line.append(field)
149        data.append(line)
150    table = Table(data,colWidths=[
151        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
152    return table
153
154
155def insert_footer(pdf,width,style,text=None, number_of_pages=1):
156      """Render the whole footer frame.
157      """
158      story = []
159      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
160      timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
161      left_text = '<font size=10>%s</font>' % timestamp
162      story.append(Paragraph(left_text, style["Normal"]))
163      frame_footer.addFromList(story,pdf)
164      story = []
165      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
166      portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
167      right_text = translate(_('<font size=10>${a} Page ${b} of ${c}</font>',
168          mapping = {'a':text, 'b':pdf.getPageNumber(), 'c':number_of_pages}),
169          'waeup.sirp', target_language=portal_language)
170      story.append(Paragraph(right_text, style["Right"]))
171      frame_footer.addFromList(story,pdf)
172
173class StudentsUtils(grok.GlobalUtility):
174    """A collection of methods subject to customization.
175    """
176    grok.implements(IStudentsUtils)
177
178    def setReturningData(self, student):
179        student['studycourse'].current_level += 100
180        student['studycourse'].current_session += 1
181        verdict = student['studycourse'].current_verdict
182        student['studycourse'].current_verdict = '0'
183        student['studycourse'].previous_verdict = verdict
184        return
185
186    def getPaymentDetails(self, category, student):
187        d = {}
188        d['p_item'] = u''
189        d['amount'] = 0
190        d['error'] = u''
191        d['p_session'] = student['studycourse'].current_session
192        session = str(d['p_session'])
193        try:
194            academic_session = grok.getSite()['configuration'][session]
195        except KeyError:
196            d['error'] = u'Session configuration object is not available.'
197            return d
198        d['surcharge_1'] = academic_session.surcharge_1
199        d['surcharge_2'] = academic_session.surcharge_2
200        d['surcharge_3'] = academic_session.surcharge_3
201        if category == 'schoolfee':
202            d['amount'] = academic_session.school_fee_base
203            d['p_item'] = student['studycourse'].certificate.code
204        elif category == 'clearance':
205            d['p_item'] = student['studycourse'].certificate.code
206            d['amount'] = academic_session.clearance_fee
207        elif category == 'bed_allocation':
208            d['p_item'] = self.getAccommodationDetails(student)['bt']
209            d['amount'] = academic_session.booking_fee
210        return d
211
212    def getAccommodationDetails(self, student):
213        d = {}
214        d['error'] = u''
215        site_configuration = grok.getSite()['configuration']
216        d['booking_session'] = site_configuration.accommodation_session
217        d['allowed_states'] = site_configuration.accommodation_states
218        # Determine bed type
219        studycourse = student['studycourse']
220        certificate = getattr(studycourse,'certificate',None)
221        entry_session = studycourse.entry_session
222        current_level = studycourse.current_level
223        if not (entry_session and current_level and certificate):
224            return
225        end_level = certificate.end_level
226        if entry_session == grok.getSite()['configuration'].accommodation_session:
227            bt = 'fr'
228        elif current_level >= end_level:
229            bt = 'fi'
230        else:
231            bt = 're'
232        if student.sex == 'f':
233            sex = 'female'
234        else:
235            sex = 'male'
236        special_handling = 'regular'
237        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
238        return d
239
240    # In the standard configuration we select the first bed found,
241    # but can also randomize the selection if we like.
242    def selectBed(self, available_beds):
243        return available_beds[0]
244
245    def renderPDF(self, view, filename='slip.pdf',
246        student=None, studentview=None, tableheader=None, tabledata=None):
247        # (0,0),(-1,-1) = whole table
248        # (0,0),(0,-1) = first column
249        # (-1,0),(-1,-1) = last column
250        # (0,0),(-1,0) = first row
251        # (0,-1),(-1,-1) = last row
252
253        pdf = canvas.Canvas(filename,pagesize=A4)
254        pdf.setTitle(view.label)
255        pdf.setSubject(view.title)
256        pdf.setAuthor('%s (%s)' % (view.request.principal.title,
257            view.request.principal.id))
258        pdf.setCreator('WAeUP SIRP')
259        width, height = A4
260        footer_text = view.label
261        if getattr(student, 'student_id', None) is not None:
262            footer_text = "%s - %s - " % (student.student_id, footer_text)
263        style = getSampleStyleSheet()
264        style.add(ParagraphStyle(name='Right', alignment=TA_RIGHT))
265        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
266
267        story = []
268        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
269        header_title = getattr(grok.getSite(), 'name', u'Sample University')
270        story.append(Paragraph(header_title, style["Heading1"]))
271        frame_header.addFromList(story,pdf)
272
273        # Insert student data table
274        story = []
275        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
276        story.append(Paragraph(view.label, style["Heading2"]))
277        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
278        if student is not None:
279            #story.append(Spacer(1, 12))
280            bd_translation = translate(_('Base Data'),
281                'waeup.sirp', target_language=portal_language)
282            story.append(Paragraph(bd_translation, style["Heading3"]))
283            studenttable = render_student_data(studentview)
284            story.append(studenttable)
285
286        # Insert widgets
287        story.append(Paragraph(view.title, style["Heading3"]))
288        set_up_widgets(view)
289        data = []
290        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
291        for widget in view.widgets:
292            f_label = '<font size=12>%s</font>:' % translate(
293                widget.label.strip(), 'waeup.sirp',
294                target_language=portal_language)
295            f_label = Paragraph(f_label, style["Normal"])
296            f_text = formatted_text(widget(), size=12)
297            f_text = Paragraph(f_text, style["Normal"])
298            data.append([f_label,f_text])
299        table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
300        story.append(table)
301
302        # Import browser components locally
303        # to avoid circular import
304        from waeup.sirp.students.viewlets import FileManager
305        from waeup.sirp.browser import DEFAULT_IMAGE_PATH
306        data = []
307        # Collect viewlets
308        fm = FileManager(view.context, view.request, view)
309        fm.update()
310        if fm.viewlets:
311            sc_translation = translate(_('Scanned Documents'), 'waeup.sirp',
312                target_language=portal_language)
313            story.append(Paragraph(sc_translation, style["Heading3"]))
314            # Insert list of scanned documents
315            for viewlet in fm.viewlets:
316                f_label = formatted_label(size=12) % translate(
317                viewlet.label, 'waeup.sirp',
318                target_language=portal_language)
319                f_label = Paragraph(f_label, style["Normal"])
320                if viewlet.template.__grok_name__ == 'imagedisplay':
321                    # In case we define FileDisplay viewlets with
322                    # an imagedisplay template in the customization package
323                    img = getUtility(IExtFileStore).getFileByContext(
324                        view.context, attr=viewlet.download_name)
325                    if img is None:
326                        img = open(DEFAULT_IMAGE_PATH, 'rb')
327                    doc_img = Image(img.name, width=2*cm, height=1*cm, kind='bound')
328                    data.append([f_label,doc_img])
329                else:
330                    f_text = formatted_text(viewlet.title, size=12)
331                    f_text = Paragraph(f_text, style["Normal"])
332                    data.append([f_label,f_text])
333            table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
334            story.append(table)
335
336        try:
337            frame_body.addFromList(story,pdf)
338        except IOError:
339            view.flash('Error in image file.')
340            return view.redirect(view.url(view.context))
341
342        if tabledata and tableheader:
343            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
344        else:
345            insert_footer(pdf,width,style,footer_text)
346
347        # Insert content table on second page
348        if tabledata and tableheader:
349            pdf.showPage()
350            frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(2*cm))
351            story = []
352            story.append(Paragraph(view.content_title, style["Heading3"]))
353            #story.append(Spacer(1, 12))
354            contenttable = render_table_data(tableheader,tabledata)
355            story.append(contenttable)
356            frame_body.addFromList(story,pdf)
357            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
358
359        view.response.setHeader(
360            'Content-Type', 'application/pdf')
361        return pdf.getpdfdata()
362
363    def getVerdictsDict(self):
364        """Provide a dictionary of verdicts.
365        """
366        return {
367            '0': 'not yet',
368            'A': 'Successful student',
369            'B': 'Student with carryover courses',
370            'C': 'Student on probation',
371            'D': 'Withdrawn from the faculty',
372            'E': 'Student who were previously on probation',
373            'F': 'Medical case',
374            'G': 'Absent from examination',
375            'H': 'Withheld results',
376            'I': 'Expelled/rusticated/suspended student',
377            'J': 'Temporary withdrawn from the university',
378            'K': 'Unregistered student',
379            'L': 'Referred student',
380            'M': 'Reinstatement',
381            'N': 'Student on transfer',
382            'O': 'NCE-III repeater',
383            'Y': 'No previous verdict',
384            'X': 'New 300 level student',
385            'Z': 'Successful student (provisional)',
386            'A1': 'First Class',
387            'A2': 'Second Class Upper',
388            'A3': 'Second Class Lower',
389            'A4': 'Third Class',
390            'A5': 'Pass',
391            'A6': 'Distinction',
392            'A7': 'Credit',
393            'A8': 'Merit',
394            }
Note: See TracBrowser for help on using the repository browser.