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

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

We can use the hash symbol at the end of p_id in import files
to avoid annoying automatic number transformation
by Excel or Calc.

  • Property svn:keywords set to Id
File size: 14.4 KB
RevLine 
[7191]1## $Id: utils.py 7626 2012-02-10 20:26: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##
[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
[7186]84def set_up_widgets(view, ignore_request=False):
[7019]85    view.adapters = {}
86    view.widgets = setUpEditWidgets(
87        view.form_fields, view.prefix, view.context, view.request,
88        adapters=view.adapters, for_display=True,
89        ignore_request=ignore_request
90        )
91
[7310]92def render_student_data(studentview):
[7318]93    """Render student table for an existing frame.
94    """
95    width, height = A4
[7186]96    set_up_widgets(studentview, ignore_request=True)
[7318]97    data_left = []
98    data_right = []
[7019]99    style = getSampleStyleSheet()
[7280]100    img = getUtility(IExtFileStore).getFileByContext(
101        studentview.context, attr='passport.jpg')
102    if img is None:
103        from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
104        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]105    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
106    data_left.append([doc_img])
107    #data.append([Spacer(1, 12)])
[7019]108    for widget in studentview.widgets:
109        if widget.name == 'form.adm_code':
110            continue
[7319]111        f_label = formatted_label() % widget.label.strip()
[7019]112        f_label = Paragraph(f_label, style["Normal"])
[7511]113        f_text = formatted_text(widget())
[7019]114        f_text = Paragraph(f_text, style["Normal"])
[7318]115        data_right.append([f_label,f_text])
116    table_left = Table(data_left,style=SLIP_STYLE)
117    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[4*cm, 10*cm])
118    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]119    return table
120
[7304]121def render_table_data(tableheader,tabledata):
[7318]122    """Render children table for an existing frame.
123    """
[7304]124    data = []
[7318]125    #data.append([Spacer(1, 12)])
[7304]126    line = []
127    style = getSampleStyleSheet()
128    for element in tableheader:
[7511]129        field = formatted_text(element[0], color='white')
[7310]130        field = Paragraph(field, style["Normal"])
[7304]131        line.append(field)
132    data.append(line)
133    for ticket in tabledata:
134        line = []
135        for element in tableheader:
[7511]136              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]137              field = Paragraph(field, style["Normal"])
[7304]138              line.append(field)
139        data.append(line)
[7310]140    table = Table(data,colWidths=[
[7318]141        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]142    return table
143
[7318]144
145def insert_footer(pdf,width,style,text=None, number_of_pages=1):
146      """Render the whole footer frame.
147      """
148      story = []
149      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
150      timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
151      left_text = '<font size=10>%s</font>' % timestamp
152      story.append(Paragraph(left_text, style["Normal"]))
153      frame_footer.addFromList(story,pdf)
154      story = []
155      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
156      right_text = '<font size=10>%s Page %s of %s</font>' % (
157          text, pdf.getPageNumber(), number_of_pages)
158      story.append(Paragraph(right_text, style["Right"]))
159      frame_footer.addFromList(story,pdf)
160
[7150]161class StudentsUtils(grok.GlobalUtility):
162    """A collection of methods subject to customization.
163    """
164    grok.implements(IStudentsUtils)
[7019]165
[7615]166    def setReturningData(self, student):
167        student['studycourse'].current_level += 100
168        student['studycourse'].current_session += 1
169        verdict = student['studycourse'].current_verdict
170        student['studycourse'].current_verdict = '0'
171        student['studycourse'].previous_verdict = verdict
172        return
173
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()
[7620]340
341    def getVerdictsDict(self):
342        """Provide a dictionary of verdicts.
343        """
344        return {
345            '0': 'not yet',
346            'A': 'Successful student',
[7624]347            'B': 'Student with carryover courses',
[7620]348            'C': 'Student on probation',
[7624]349            'D': 'Withdrawn from the faculty',
[7620]350            'E': 'Student who were previously on probation',
[7624]351            'F': 'Medical case',
[7620]352            'G': 'Absent from examination',
[7624]353            'H': 'Withheld results',
[7620]354            'I': 'Expelled/rusticated/suspended student',
[7624]355            'J': 'Temporary withdrawn from the university',
[7620]356            'K': 'Unregistered student',
[7624]357            'L': 'Referred student',
[7620]358            'M': 'Reinstatement',
[7624]359            'N': 'Student on transfer',
[7620]360            'O': 'NCE-III repeater',
361            'Y': 'No previous verdict',
362            'X': 'New 300 level student',
[7626]363            'Z': 'Successful student (provisional)',
[7624]364            'A1': 'First Class',
365            'A2': 'Second Class Upper',
366            'A3': 'Second Class Lower',
367            'A4': 'Third Class',
368            'A5': 'Pass',
369            'A6': 'Distinction',
370            'A7': 'Credit',
371            'A8': 'Merit',
372            }
Note: See TracBrowser for help on using the repository browser.