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

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

Fix viewlet collector.

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