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

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

Store payment level in student payments.

For school fee paments p_session and p_level are calculated as defined
in w.k.students.utils

  • Property svn:keywords set to Id
File size: 15.1 KB
Line 
1## $Id: utils.py 8268 2012-04-25 06:02:54Z 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, PageBreak, Table,
32                                Spacer)
33from reportlab.platypus.tables import TableStyle
34from reportlab.platypus.flowables import PageBreak
35from zope.component import getUtility
36from zope.formlib.form import setUpEditWidgets
37
38from waeup.kofa.interfaces import IExtFileStore, IKofaUtils
39from waeup.kofa.interfaces import MessageFactory as _
40from waeup.kofa.students.interfaces import IStudentsUtils
41from waeup.kofa.utils.helpers import now
42
43SLIP_STYLE = [
44    ('VALIGN',(0,0),(-1,-1),'TOP'),
45    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
46    ]
47
48CONTENT_STYLE = [
49    ('VALIGN',(0,0),(-1,-1),'TOP'),
50    #('FONT', (0,0), (-1,-1), 'Helvetica', 8),
51    #('TEXTCOLOR',(0,0),(-1,0),colors.white),
52    ('BACKGROUND',(0,0),(-1,0),colors.black),
53    ]
54
55FONT_SIZE = 10
56FONT_COLOR = 'black'
57
58def formatted_label(color=FONT_COLOR, size=FONT_SIZE):
59    tag1 ='<font color=%s size=%d>' % (color, size)
60    return tag1 + '%s:</font>'
61
62def trans(text, lang):
63    # shortcut
64    return translate(text, 'waeup.kofa', target_language=lang)
65
66def formatted_text(text, color=FONT_COLOR, size=FONT_SIZE):
67    """Turn `text`, `color` and `size` into an HTML snippet.
68
69    The snippet is suitable for use with reportlab and generating PDFs.
70    Wraps the `text` into a ``<font>`` tag with passed attributes.
71
72    Also non-strings are converted. Raw strings are expected to be
73    utf-8 encoded (usually the case for widgets etc.).
74
75    Finally, a br tag is added if widgets contain div tags
76    which are not supported by reportlab.
77
78    The returned snippet is unicode type.
79    """
80    try:
81        # In unit tests IKofaUtils has not been registered
82        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
83    except:
84        portal_language = 'en'
85    if not isinstance(text, unicode):
86        if isinstance(text, basestring):
87            text = text.decode('utf-8')
88        else:
89            text = unicode(text)
90    # Mainly for boolean values we need our customized
91    # localisation of the zope domain
92    text = translate(text, 'zope', target_language=portal_language)
93    text = text.replace('</div>', '<br /></div>')
94    tag1 = u'<font color="%s" size="%d">' % (color, size)
95    return tag1 + u'%s</font>' % text
96
97def generate_student_id(students,letter):
98    if letter == '?':
99        letter= r().choice('ABCDEFGHKLMNPQRSTUVWXY')
100    sid = u"%c%d" % (letter,r().randint(99999,1000000))
101    while sid in students.keys():
102        sid = u"%c%d" % (letter,r().randint(99999,1000000))
103    return sid
104
105def set_up_widgets(view, ignore_request=False):
106    view.adapters = {}
107    view.widgets = setUpEditWidgets(
108        view.form_fields, view.prefix, view.context, view.request,
109        adapters=view.adapters, for_display=True,
110        ignore_request=ignore_request
111        )
112
113def render_student_data(studentview):
114    """Render student table for an existing frame.
115    """
116    width, height = A4
117    set_up_widgets(studentview, ignore_request=True)
118    data_left = []
119    data_right = []
120    style = getSampleStyleSheet()
121    img = getUtility(IExtFileStore).getFileByContext(
122        studentview.context, attr='passport.jpg')
123    if img is None:
124        from waeup.kofa.browser import DEFAULT_PASSPORT_IMAGE_PATH
125        img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
126    doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound')
127    data_left.append([doc_img])
128    #data.append([Spacer(1, 12)])
129    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
130    for widget in studentview.widgets:
131        if widget.name == 'form.adm_code':
132            continue
133        f_label = formatted_label(size=12) % translate(
134            widget.label.strip(), 'waeup.kofa',
135            target_language=portal_language)
136        f_label = Paragraph(f_label, style["Normal"])
137        f_text = formatted_text(widget(), size=12)
138        f_text = Paragraph(f_text, style["Normal"])
139        data_right.append([f_label,f_text])
140    table_left = Table(data_left,style=SLIP_STYLE)
141    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
142    table = Table([[table_left, table_right],],style=SLIP_STYLE)
143    return table
144
145def render_table_data(tableheader,tabledata):
146    """Render children table for an existing frame.
147    """
148    data = []
149    #data.append([Spacer(1, 12)])
150    line = []
151    style = getSampleStyleSheet()
152    for element in tableheader:
153        field = formatted_text(element[0], color='white')
154        field = Paragraph(field, style["Normal"])
155        line.append(field)
156    data.append(line)
157    for ticket in tabledata:
158        line = []
159        for element in tableheader:
160              field = formatted_text(getattr(ticket,element[1],u' '))
161              field = Paragraph(field, style["Normal"])
162              line.append(field)
163        data.append(line)
164    table = Table(data,colWidths=[
165        element[2]*cm for element in tableheader], style=CONTENT_STYLE)
166    return table
167
168def docs_as_flowables(view, lang='en'):
169    """Create reportlab flowables out of scanned docs.
170    """
171    # XXX: fix circular import problem
172    from waeup.kofa.students.viewlets import FileManager
173    from waeup.kofa.browser import DEFAULT_IMAGE_PATH
174    from waeup.kofa.browser.pdf import NORMAL_STYLE, ENTRY1_STYLE
175    style = getSampleStyleSheet()
176    data = []
177
178    # Collect viewlets
179    fm = FileManager(view.context, view.request, view)
180    fm.update()
181    if fm.viewlets:
182        sc_translation = trans(_('Scanned Documents'), lang)
183        data.append(Paragraph(sc_translation, style["Heading3"]))
184        # Insert list of scanned documents
185        table_data = []
186        for viewlet in fm.viewlets:
187            f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE)
188            img_path = getattr(getUtility(IExtFileStore).getFileByContext(
189                view.context, attr=viewlet.download_name), 'name', None)
190            f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE)
191            if img_path is None:
192                pass
193            elif not img_path.endswith('.jpg'):
194                # reportlab requires jpg images, I think.
195                f_text = Paragraph('%s (Not displayable)' % (
196                    viewlet.title,), ENTRY1_STYLE)
197            else:
198                f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound')
199            table_data.append([f_label, f_text])
200        if table_data:
201            # safety belt; empty tables lead to problems.
202            data.append(Table(table_data, style=SLIP_STYLE))
203    return data
204
205def insert_footer(pdf,width,style,text=None, number_of_pages=1):
206      """Render the whole footer frame.
207      """
208      story = []
209      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
210      tz = getUtility(IKofaUtils).tzinfo
211      timestamp = now(tz).strftime("%d/%m/%Y %H:%M:%S %Z")
212      left_text = '<font size=10>%s</font>' % timestamp
213      story.append(Paragraph(left_text, style["Normal"]))
214      frame_footer.addFromList(story,pdf)
215      story = []
216      frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
217      portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
218      right_text = translate(_('<font size=10>${a} Page ${b} of ${c}</font>',
219          mapping = {'a':text, 'b':pdf.getPageNumber(), 'c':number_of_pages}),
220          'waeup.kofa', target_language=portal_language)
221      story.append(Paragraph(right_text, style["Right"]))
222      frame_footer.addFromList(story,pdf)
223
224class StudentsUtils(grok.GlobalUtility):
225    """A collection of methods subject to customization.
226    """
227    grok.implements(IStudentsUtils)
228
229    def getReturningData(self, student):
230        """ This method defines what happens after school fee payment
231        depending on the student's senate verdict.
232
233        In the base configuration current level is always increased
234        by 100 no matter which verdict has been assigned.
235        """
236        new_level = student['studycourse'].current_level + 100
237        new_session = student['studycourse'].current_session + 1
238        return new_session, new_level
239
240    def setReturningData(self, student):
241        """ This method defines what happens after school fee payment
242        depending on the student's senate verdict. It folllows
243        the same algorithm as getReturningData but it also sets the new
244        values
245
246        In the base configuration current level is always increased
247        by 100 no matter which verdict has been assigned.
248        """
249        new_session, new_level = self.getReturningData(student)
250        student['studycourse'].current_level = new_level
251        student['studycourse'].current_session = new_session
252        verdict = student['studycourse'].current_verdict
253        student['studycourse'].current_verdict = '0'
254        student['studycourse'].previous_verdict = verdict
255        return
256
257    def getPaymentDetails(self, category, student):
258        """Get the payment dates of a student for the payment category
259        specified.
260        """
261        d = {}
262        d['p_item'] = u''
263        d['amount'] = 0.0
264        d['error'] = u''
265        d['p_session'] = student['studycourse'].current_session
266        d['p_level'] = student['studycourse'].current_level
267        session = str(d['p_session'])
268        try:
269            academic_session = grok.getSite()['configuration'][session]
270        except KeyError:
271            d['error'] = u'Session configuration object is not available.'
272            return d
273        if category == 'schoolfee':
274            d['amount'] = academic_session.school_fee_base
275            d['p_item'] = student['studycourse'].certificate.code
276            # In case of school fee payment the payment session and level
277            # contain the values of the session the student has paid for
278            d['p_session'], d['p_level'] = self.getReturningData(student)
279        elif category == 'clearance':
280            d['p_item'] = student['studycourse'].certificate.code
281            d['amount'] = academic_session.clearance_fee
282        elif category == 'bed_allocation':
283            d['p_item'] = self.getAccommodationDetails(student)['bt']
284            d['amount'] = academic_session.booking_fee
285        return d
286
287    def getAccommodationDetails(self, student):
288        """Determine the accommodation dates of a student.
289        """
290        d = {}
291        d['error'] = u''
292        site_configuration = grok.getSite()['configuration']
293        d['booking_session'] = site_configuration.accommodation_session
294        d['allowed_states'] = site_configuration.accommodation_states
295        # Determine bed type
296        studycourse = student['studycourse']
297        certificate = getattr(studycourse,'certificate',None)
298        entry_session = studycourse.entry_session
299        current_level = studycourse.current_level
300        if not (entry_session and current_level and certificate):
301            return
302        end_level = certificate.end_level
303        if entry_session == grok.getSite()[
304            'configuration'].accommodation_session:
305            bt = 'fr'
306        elif current_level >= end_level:
307            bt = 'fi'
308        else:
309            bt = 're'
310        if student.sex == 'f':
311            sex = 'female'
312        else:
313            sex = 'male'
314        special_handling = 'regular'
315        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
316        return d
317
318    def selectBed(self, available_beds):
319        """Select a bed from a list of available beds.
320
321        In the base configuration we select the first bed found,
322        but can also randomize the selection if we like.
323        """
324        return available_beds[0]
325
326    def renderPDF(self, view, filename='slip.pdf', student=None,
327                  studentview=None, tableheader=None, tabledata=None,
328                  note=None):
329        """Render pdf slips for various pages.
330        """
331        # XXX: we have to fix the import problems here.
332        from waeup.kofa.browser.interfaces import IPDFCreator
333        from waeup.kofa.browser.pdf import NORMAL_STYLE, ENTRY1_STYLE
334        style = getSampleStyleSheet()
335        creator = getUtility(IPDFCreator)
336        data = []
337        doc_title = view.label
338        author = '%s (%s)' % (view.request.principal.title,
339                              view.request.principal.id)
340        footer_text = view.label
341        if getattr(student, 'student_id', None) is not None:
342            footer_text = "%s - %s - " % (student.student_id, footer_text)
343
344        # Insert student data table
345        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
346        if student is not None:
347            bd_translation = trans(_('Base Data'), portal_language)
348            data.append(Paragraph(bd_translation, style["Heading3"]))
349            data.append(render_student_data(studentview))
350
351        # Insert widgets
352        data.append(Paragraph(view.title, style["Heading3"]))
353        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
354        separators = getattr(self, 'SEPARATORS_DICT', {})
355        table = creator.getWidgetsTable(
356            view.form_fields, view.context, None, lang=portal_language,
357            separators=separators)
358        data.append(table)
359
360        # Insert scanned docs
361        data.extend(docs_as_flowables(view, portal_language))
362
363        # Insert content table (optionally on second page)
364        if tabledata and tableheader:
365            #data.append(PageBreak())
366            data.append(Spacer(1, 20))
367            data.append(Paragraph(view.content_title, style["Heading3"]))
368            contenttable = render_table_data(tableheader,tabledata)
369            data.append(contenttable)
370
371        view.response.setHeader(
372            'Content-Type', 'application/pdf')
373        try:
374            pdf_stream = creator.create_pdf(
375                data, None, doc_title, author=author, footer=footer_text,
376                note=note)
377        except IOError:
378            view.flash('Error in image file.')
379            return view.redirect(view.url(view.context))
380        return pdf_stream
381
382    VERDICTS_DICT = {
383        '0': _('(not yet)'),
384        'A': 'Successful student',
385        'B': 'Student with carryover courses',
386        'C': 'Student on probation',
387        }
388
389    SEPARATORS_DICT = {
390        }
Note: See TracBrowser for help on using the repository browser.