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

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

Use different clearance form field interfaces for undergraduate and postgraduate students.

  • Property svn:keywords set to Id
File size: 15.2 KB
RevLine 
[7191]1## $Id: utils.py 7993 2012-03-27 20:59:30Z 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
[7019]31from reportlab.platypus import (Frame, Paragraph, Image,
32    Table, Spacer)
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
[7819]37from waeup.kofa.interfaces import IExtFileStore, IKofaUtils
[7811]38from waeup.kofa.interfaces import MessageFactory as _
39from waeup.kofa.students.interfaces import IStudentsUtils
[6651]40
[7318]41SLIP_STYLE = [
42    ('VALIGN',(0,0),(-1,-1),'TOP'),
43    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
44    ]
[7019]45
[7318]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    ]
[7304]52
[7318]53FONT_SIZE = 10
54FONT_COLOR = 'black'
55
[7319]56def formatted_label(color=FONT_COLOR, size=FONT_SIZE):
[7318]57    tag1 ='<font color=%s size=%d>' % (color, size)
[7319]58    return tag1 + '%s:</font>'
59
[7511]60def formatted_text(text, color=FONT_COLOR, size=FONT_SIZE):
61    """Turn `text`, `color` and `size` into an HTML snippet.
[7318]62
[7511]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
[7804]69    Finally, a br tag is added if widgets contain div tags
70    which are not supported by reportlab.
71
[7511]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)
[7804]79    text = text.replace('</div>', '<br /></div>')
[7511]80    tag1 = u'<font color="%s" size="%d">' % (color, size)
81    return tag1 + u'%s</font>' % text
82
[6749]83def generate_student_id(students,letter):
[6651]84    if letter == '?':
[6664]85        letter= r().choice('ABCDEFGHKLMNPQRSTUVWXY')
86    sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6749]87    while sid in students.keys():
[6664]88        sid = u"%c%d" % (letter,r().randint(99999,1000000))
[6662]89    return sid
[6742]90
[7186]91def set_up_widgets(view, ignore_request=False):
[7019]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
[7310]99def render_student_data(studentview):
[7318]100    """Render student table for an existing frame.
101    """
102    width, height = A4
[7186]103    set_up_widgets(studentview, ignore_request=True)
[7318]104    data_left = []
105    data_right = []
[7019]106    style = getSampleStyleSheet()
[7280]107    img = getUtility(IExtFileStore).getFileByContext(
108        studentview.context, attr='passport.jpg')
109    if img is None:
[7811]110        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
[7280]111        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7318]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)])
[7819]115    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7019]116    for widget in studentview.widgets:
117        if widget.name == 'form.adm_code':
118            continue
[7714]119        f_label = formatted_label(size=12) % translate(
[7811]120            widget.label.strip(), 'waeup.kofa',
[7714]121            target_language=portal_language)
[7019]122        f_label = Paragraph(f_label, style["Normal"])
[7714]123        f_text = formatted_text(widget(), size=12)
[7019]124        f_text = Paragraph(f_text, style["Normal"])
[7318]125        data_right.append([f_label,f_text])
126    table_left = Table(data_left,style=SLIP_STYLE)
[7741]127    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[6*cm, 8*cm])
[7318]128    table = Table([[table_left, table_right],],style=SLIP_STYLE)
[7019]129    return table
130
[7304]131def render_table_data(tableheader,tabledata):
[7318]132    """Render children table for an existing frame.
133    """
[7304]134    data = []
[7318]135    #data.append([Spacer(1, 12)])
[7304]136    line = []
137    style = getSampleStyleSheet()
138    for element in tableheader:
[7511]139        field = formatted_text(element[0], color='white')
[7310]140        field = Paragraph(field, style["Normal"])
[7304]141        line.append(field)
142    data.append(line)
143    for ticket in tabledata:
144        line = []
145        for element in tableheader:
[7511]146              field = formatted_text(getattr(ticket,element[1],u' '))
[7318]147              field = Paragraph(field, style["Normal"])
[7304]148              line.append(field)
149        data.append(line)
[7310]150    table = Table(data,colWidths=[
[7318]151        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
[7304]152    return table
153
[7318]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)
[7819]166      portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7714]167      right_text = translate(_('<font size=10>${a} Page ${b} of ${c}</font>',
168          mapping = {'a':text, 'b':pdf.getPageNumber(), 'c':number_of_pages}),
[7811]169          'waeup.kofa', target_language=portal_language)
[7318]170      story.append(Paragraph(right_text, style["Right"]))
171      frame_footer.addFromList(story,pdf)
172
[7150]173class StudentsUtils(grok.GlobalUtility):
174    """A collection of methods subject to customization.
175    """
176    grok.implements(IStudentsUtils)
[7019]177
[7615]178    def setReturningData(self, student):
[7841]179        """ This method defines what happens after school fee payment
180        depending on the student's senate verdict.
181
182        In the base configuration current level is always increased
183        by 100 no matter which verdict has been assigned.
184        """
[7615]185        student['studycourse'].current_level += 100
186        student['studycourse'].current_session += 1
187        verdict = student['studycourse'].current_verdict
188        student['studycourse'].current_verdict = '0'
189        student['studycourse'].previous_verdict = verdict
190        return
191
192    def getPaymentDetails(self, category, student):
[7841]193        """Get the payment dates of a student for the payment category
194        specified.
195        """
[7150]196        d = {}
197        d['p_item'] = u''
[7927]198        d['amount'] = 0.0
[7150]199        d['error'] = u''
200        d['p_session'] = student['studycourse'].current_session
201        session = str(d['p_session'])
202        try:
203            academic_session = grok.getSite()['configuration'][session]
204        except KeyError:
205            d['error'] = u'Session configuration object is not available.'
206            return d
207        d['surcharge_1'] = academic_session.surcharge_1
208        d['surcharge_2'] = academic_session.surcharge_2
209        d['surcharge_3'] = academic_session.surcharge_3
210        if category == 'schoolfee':
211            d['amount'] = academic_session.school_fee_base
212            d['p_item'] = student['studycourse'].certificate.code
213        elif category == 'clearance':
214            d['p_item'] = student['studycourse'].certificate.code
215            d['amount'] = academic_session.clearance_fee
[7927]216            d['surcharge_1'] = 0.0 # no portal fee
[7150]217        elif category == 'bed_allocation':
[7186]218            d['p_item'] = self.getAccommodationDetails(student)['bt']
[7150]219            d['amount'] = academic_session.booking_fee
[7927]220            d['surcharge_1'] = 0.0 # no portal fee
[7150]221        return d
[7019]222
[7186]223    def getAccommodationDetails(self, student):
[7841]224        """Determine the accommodation dates of a student.
225        """
[7150]226        d = {}
227        d['error'] = u''
[7365]228        site_configuration = grok.getSite()['configuration']
229        d['booking_session'] = site_configuration.accommodation_session
230        d['allowed_states'] = site_configuration.accommodation_states
[7150]231        # Determine bed type
232        studycourse = student['studycourse']
[7369]233        certificate = getattr(studycourse,'certificate',None)
[7150]234        entry_session = studycourse.entry_session
235        current_level = studycourse.current_level
[7369]236        if not (entry_session and current_level and certificate):
237            return
238        end_level = certificate.end_level
[7150]239        if entry_session == grok.getSite()['configuration'].accommodation_session:
240            bt = 'fr'
241        elif current_level >= end_level:
242            bt = 'fi'
243        else:
244            bt = 're'
245        if student.sex == 'f':
246            sex = 'female'
247        else:
248            sex = 'male'
249        special_handling = 'regular'
250        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
251        return d
[7019]252
[7186]253    def selectBed(self, available_beds):
[7841]254        """Select a bed from a list of available beds.
255
256        In the base configuration we select the first bed found,
257        but can also randomize the selection if we like.
258        """
[7150]259        return available_beds[0]
260
[7318]261    def renderPDF(self, view, filename='slip.pdf',
[7304]262        student=None, studentview=None, tableheader=None, tabledata=None):
[7841]263        """Render pdf slips for various pages.
264        """
[7150]265        # (0,0),(-1,-1) = whole table
266        # (0,0),(0,-1) = first column
267        # (-1,0),(-1,-1) = last column
268        # (0,0),(-1,0) = first row
269        # (0,-1),(-1,-1) = last row
270
271        pdf = canvas.Canvas(filename,pagesize=A4)
272        pdf.setTitle(view.label)
[7318]273        pdf.setSubject(view.title)
[7150]274        pdf.setAuthor('%s (%s)' % (view.request.principal.title,
275            view.request.principal.id))
[7819]276        pdf.setCreator('WAeUP Kofa')
[7150]277        width, height = A4
[7310]278        footer_text = view.label
[7714]279        if getattr(student, 'student_id', None) is not None:
[7310]280            footer_text = "%s - %s - " % (student.student_id, footer_text)
[7150]281        style = getSampleStyleSheet()
[7310]282        style.add(ParagraphStyle(name='Right', alignment=TA_RIGHT))
[7150]283        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
284
285        story = []
286        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
287        header_title = getattr(grok.getSite(), 'name', u'Sample University')
288        story.append(Paragraph(header_title, style["Heading1"]))
289        frame_header.addFromList(story,pdf)
290
[7318]291        # Insert student data table
[7150]292        story = []
293        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
294        story.append(Paragraph(view.label, style["Heading2"]))
[7819]295        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7310]296        if student is not None:
[7318]297            #story.append(Spacer(1, 12))
[7714]298            bd_translation = translate(_('Base Data'),
[7811]299                'waeup.kofa', target_language=portal_language)
[7714]300            story.append(Paragraph(bd_translation, style["Heading3"]))
[7310]301            studenttable = render_student_data(studentview)
[7150]302            story.append(studenttable)
[7304]303
[7318]304        # Insert widgets
305        story.append(Paragraph(view.title, style["Heading3"]))
[7186]306        set_up_widgets(view)
[7150]307        data = []
[7819]308        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7150]309        for widget in view.widgets:
[7714]310            f_label = '<font size=12>%s</font>:' % translate(
[7811]311                widget.label.strip(), 'waeup.kofa',
[7714]312                target_language=portal_language)
[7150]313            f_label = Paragraph(f_label, style["Normal"])
[7714]314            f_text = formatted_text(widget(), size=12)
[7150]315            f_text = Paragraph(f_text, style["Normal"])
316            data.append([f_label,f_text])
[7318]317        table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
318        story.append(table)
319
320        # Import browser components locally
[7280]321        # to avoid circular import
[7811]322        from waeup.kofa.students.viewlets import FileManager
323        from waeup.kofa.browser import DEFAULT_IMAGE_PATH
[7318]324        data = []
325        # Collect viewlets
[7280]326        fm = FileManager(view.context, view.request, view)
327        fm.update()
[7318]328        if fm.viewlets:
[7811]329            sc_translation = translate(_('Scanned Documents'), 'waeup.kofa',
[7741]330                target_language=portal_language)
331            story.append(Paragraph(sc_translation, style["Heading3"]))
[7318]332            # Insert list of scanned documents
333            for viewlet in fm.viewlets:
[7741]334                f_label = formatted_label(size=12) % translate(
[7811]335                viewlet.label, 'waeup.kofa',
[7741]336                target_language=portal_language)
[7318]337                f_label = Paragraph(f_label, style["Normal"])
338                if viewlet.template.__grok_name__ == 'imagedisplay':
339                    # In case we define FileDisplay viewlets with
340                    # an imagedisplay template in the customization package
341                    img = getUtility(IExtFileStore).getFileByContext(
342                        view.context, attr=viewlet.download_name)
343                    if img is None:
344                        img = open(DEFAULT_IMAGE_PATH, 'rb')
345                    doc_img = Image(img.name, width=2*cm, height=1*cm, kind='bound')
346                    data.append([f_label,doc_img])
347                else:
[7714]348                    f_text = formatted_text(viewlet.title, size=12)
[7318]349                    f_text = Paragraph(f_text, style["Normal"])
350                    data.append([f_label,f_text])
351            table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
352            story.append(table)
353
[7280]354        try:
355            frame_body.addFromList(story,pdf)
356        except IOError:
357            view.flash('Error in image file.')
358            return view.redirect(view.url(view.context))
[7318]359
[7304]360        if tabledata and tableheader:
[7318]361            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
362        else:
363            insert_footer(pdf,width,style,footer_text)
364
365        # Insert content table on second page
366        if tabledata and tableheader:
367            pdf.showPage()
368            frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(2*cm))
[7304]369            story = []
[7318]370            story.append(Paragraph(view.content_title, style["Heading3"]))
[7310]371            #story.append(Spacer(1, 12))
[7304]372            contenttable = render_table_data(tableheader,tabledata)
373            story.append(contenttable)
374            frame_body.addFromList(story,pdf)
[7318]375            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
376
[7150]377        view.response.setHeader(
378            'Content-Type', 'application/pdf')
379        return pdf.getpdfdata()
[7620]380
[7841]381    VERDICTS_DICT = {
[7993]382        '0': _('(not yet)'),
[7841]383        'A': 'Successful student',
384        'B': 'Student with carryover courses',
385        'C': 'Student on probation',
386        }
Note: See TracBrowser for help on using the repository browser.