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

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

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

  • Property svn:keywords set to Id
File size: 15.2 KB
Line 
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##
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.kofa.interfaces import IExtFileStore, IKofaUtils
38from waeup.kofa.interfaces import MessageFactory as _
39from waeup.kofa.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.kofa.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(IKofaUtils).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.kofa',
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(IKofaUtils).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.kofa', 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        """ 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        """
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):
193        """Get the payment dates of a student for the payment category
194        specified.
195        """
196        d = {}
197        d['p_item'] = u''
198        d['amount'] = 0.0
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
216            d['surcharge_1'] = 0.0 # no portal fee
217        elif category == 'bed_allocation':
218            d['p_item'] = self.getAccommodationDetails(student)['bt']
219            d['amount'] = academic_session.booking_fee
220            d['surcharge_1'] = 0.0 # no portal fee
221        return d
222
223    def getAccommodationDetails(self, student):
224        """Determine the accommodation dates of a student.
225        """
226        d = {}
227        d['error'] = u''
228        site_configuration = grok.getSite()['configuration']
229        d['booking_session'] = site_configuration.accommodation_session
230        d['allowed_states'] = site_configuration.accommodation_states
231        # Determine bed type
232        studycourse = student['studycourse']
233        certificate = getattr(studycourse,'certificate',None)
234        entry_session = studycourse.entry_session
235        current_level = studycourse.current_level
236        if not (entry_session and current_level and certificate):
237            return
238        end_level = certificate.end_level
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
252
253    def selectBed(self, available_beds):
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        """
259        return available_beds[0]
260
261    def renderPDF(self, view, filename='slip.pdf',
262        student=None, studentview=None, tableheader=None, tabledata=None):
263        """Render pdf slips for various pages.
264        """
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)
273        pdf.setSubject(view.title)
274        pdf.setAuthor('%s (%s)' % (view.request.principal.title,
275            view.request.principal.id))
276        pdf.setCreator('WAeUP Kofa')
277        width, height = A4
278        footer_text = view.label
279        if getattr(student, 'student_id', None) is not None:
280            footer_text = "%s - %s - " % (student.student_id, footer_text)
281        style = getSampleStyleSheet()
282        style.add(ParagraphStyle(name='Right', alignment=TA_RIGHT))
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
291        # Insert student data table
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"]))
295        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
296        if student is not None:
297            #story.append(Spacer(1, 12))
298            bd_translation = translate(_('Base Data'),
299                'waeup.kofa', target_language=portal_language)
300            story.append(Paragraph(bd_translation, style["Heading3"]))
301            studenttable = render_student_data(studentview)
302            story.append(studenttable)
303
304        # Insert widgets
305        story.append(Paragraph(view.title, style["Heading3"]))
306        set_up_widgets(view)
307        data = []
308        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
309        for widget in view.widgets:
310            f_label = '<font size=12>%s</font>:' % translate(
311                widget.label.strip(), 'waeup.kofa',
312                target_language=portal_language)
313            f_label = Paragraph(f_label, style["Normal"])
314            f_text = formatted_text(widget(), size=12)
315            f_text = Paragraph(f_text, style["Normal"])
316            data.append([f_label,f_text])
317        table = Table(data,style=SLIP_STYLE, colWidths=[5*cm, 13.5*cm])
318        story.append(table)
319
320        # Import browser components locally
321        # to avoid circular import
322        from waeup.kofa.students.viewlets import FileManager
323        from waeup.kofa.browser import DEFAULT_IMAGE_PATH
324        data = []
325        # Collect viewlets
326        fm = FileManager(view.context, view.request, view)
327        fm.update()
328        if fm.viewlets:
329            sc_translation = translate(_('Scanned Documents'), 'waeup.kofa',
330                target_language=portal_language)
331            story.append(Paragraph(sc_translation, style["Heading3"]))
332            # Insert list of scanned documents
333            for viewlet in fm.viewlets:
334                f_label = formatted_label(size=12) % translate(
335                viewlet.label, 'waeup.kofa',
336                target_language=portal_language)
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:
348                    f_text = formatted_text(viewlet.title, size=12)
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
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))
359
360        if tabledata and tableheader:
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))
369            story = []
370            story.append(Paragraph(view.content_title, style["Heading3"]))
371            #story.append(Spacer(1, 12))
372            contenttable = render_table_data(tableheader,tabledata)
373            story.append(contenttable)
374            frame_body.addFromList(story,pdf)
375            insert_footer(pdf,width,style,footer_text,number_of_pages=2)
376
377        view.response.setHeader(
378            'Content-Type', 'application/pdf')
379        return pdf.getpdfdata()
380
381    VERDICTS_DICT = {
382        '0': _('(not yet)'),
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.