source: main/waeup.kofa/trunk/src/waeup/kofa/students/export.py @ 15685

Last change on this file since 15685 was 15546, checked in by Henrik Bettermann, 5 years ago

Add semester filter to Fix CourseTicketExporter.

  • Property svn:keywords set to Id
File size: 29.8 KB
Line 
1## $Id: export.py 15546 2019-08-16 12:40:20Z 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"""Exporters for student related stuff.
19"""
20import os
21import grok
22from datetime import datetime, timedelta
23from zope.component import getUtility
24from waeup.kofa.interfaces import (
25    IExtFileStore, IFileStoreNameChooser, IKofaUtils)
26from waeup.kofa.interfaces import MessageFactory as _
27from waeup.kofa.students.catalog import StudentsQuery, CourseTicketsQuery
28from waeup.kofa.students.interfaces import (
29    IStudent, IStudentStudyCourse, IStudentStudyLevel, ICourseTicket,
30    IStudentOnlinePayment, ICSVStudentExporter, IBedTicket)
31from waeup.kofa.students.vocabularies import study_levels
32from waeup.kofa.utils.batching import ExporterBase
33from waeup.kofa.utils.helpers import iface_names, to_timezone
34
35
36def get_students(site, stud_filter=StudentsQuery()):
37    """Get all students registered in catalog in `site`.
38    """
39    return stud_filter.query()
40
41def get_studycourses(students):
42    """Get studycourses of `students`.
43    """
44    return [x.get('studycourse', None) for x in students
45            if x is not None]
46
47def get_levels(students):
48    """Get all studylevels of `students`.
49    """
50    levels = []
51    for course in get_studycourses(students):
52        for level in course.values():
53            levels.append(level)
54    return levels
55
56def get_tickets(students, **kw):
57    """Get course tickets of `students`.
58    If code is passed through, filter course tickets
59    which belong to this course code and meet level=level
60    and level_session=level_session.
61    If not, but ct_level, ct_session and ct_semester
62    are passed through, filter course tickets
63    which meet level==ct_level, level_session==ct_session
64    and semester==ct_semester.
65    """
66    tickets = []
67    code = kw.get('code', None)
68    level = kw.get('level', None)
69    session = kw.get('session', None)
70    ct_level = kw.get('ct_level', None)
71    ct_session = kw.get('ct_session', None)
72    ct_semester = kw.get('ct_semester', None)
73    if code is None:
74        for level_obj in get_levels(students):
75            for ticket in level_obj.values():
76                if ct_level not in ('all', None):
77                    if level_obj.level in (10, 999, None)  \
78                        and int(ct_level) != level_obj.level:
79                        continue
80                    if level_obj.level not in range(
81                        int(ct_level), int(ct_level)+100, 10):
82                        continue
83                if ct_session not in ('all', None) and \
84                    int(ct_session) != level_obj.level_session:
85                    continue
86                if ct_semester not in ('all', None) and \
87                    int(ct_semester) != ticket.semester:
88                    continue
89                tickets.append(ticket)
90    else:
91        for level_obj in get_levels(students):
92            for ticket in level_obj.values():
93                if ticket.code != code:
94                    continue
95                if level not in ('all', None):
96                    if level_obj.level in (10, 999, None)  \
97                        and int(level) != level_obj.level:
98                        continue
99                    if level_obj.level not in range(
100                        int(level), int(level)+100, 10):
101                        continue
102                if session not in ('all', None) and \
103                    int(session) != level_obj.level_session:
104                    continue
105                tickets.append(ticket)
106    return tickets
107
108def get_tickets_for_lecturer(students, **kw):
109    """Get course tickets of `students`.
110    Filter course tickets which belong to this course code and
111    which are editable by lecturers.
112    """
113    tickets = []
114    code = kw.get('code', None)
115    level_session = kw.get('session', None)
116    level = kw.get('level', None)
117    for level_obj in get_levels(students):
118        for ticket in level_obj.values():
119            if ticket.code != code:
120                continue
121            if not ticket.editable_by_lecturer:
122                continue
123            if level not in ('all', None):
124                if level_obj.level in (10, 999, None)  \
125                    and int(level) != level_obj.level:
126                    continue
127                if level_obj.level not in range(int(level), int(level)+100, 10):
128                    continue
129            if level_session not in ('all', None) and \
130                int(level_session) != level_obj.level_session:
131                continue
132            tickets.append(ticket)
133    return tickets
134
135def get_payments(students, p_states=None, **kw):
136    """Get all payments of `students` within given payment_date period.
137    """
138    date_format = '%d/%m/%Y'
139    payments = []
140    p_start = kw.get('payments_start')
141    p_end = kw.get('payments_end')
142    paycat = kw.get('paycat')
143    paysession = kw.get('paysession')
144    for student in students:
145        for payment in student.get('payments', {}).values():
146            if p_start and p_end:
147                if not payment.payment_date:
148                    continue
149                payments_start = datetime.strptime(p_start, date_format)
150                payments_end = datetime.strptime(p_end, date_format)
151                tz = getUtility(IKofaUtils).tzinfo
152                payments_start = tz.localize(payments_start)
153                payments_end = tz.localize(payments_end) + timedelta(days=1)
154                payment_date = to_timezone(payment.payment_date, tz)
155                if payment_date < payments_start or payment_date > payments_end:
156                    continue
157            if p_states and not payment.p_state in p_states:
158                continue
159            if paycat not in ('all', None) and payment.p_category != paycat:
160                continue
161            if paysession not in ('all', None) \
162                and payment.p_session != int(paysession):
163                continue
164            payments.append(payment)
165    return payments
166
167def get_bedtickets(students):
168    """Get all bedtickets of `students`.
169    """
170    tickets = []
171    for student in students:
172        for ticket in student.get('accommodation', {}).values():
173            tickets.append(ticket)
174    return tickets
175
176class StudentExporterBase(ExporterBase):
177    """Exporter for students or related objects.
178    This is a baseclass.
179    """
180    grok.baseclass()
181    grok.implements(ICSVStudentExporter)
182    grok.provides(ICSVStudentExporter)
183
184    def filter_func(self, x, **kw):
185        return x
186
187    def get_filtered(self, site, **kw):
188        """Get students from a catalog filtered by keywords.
189        students_catalog is the default catalog. The keys must be valid
190        catalog index names.
191        Returns a simple empty list, a list with `Student`
192        objects or a catalog result set with `Student`
193        objects.
194
195        .. seealso:: `waeup.kofa.students.catalog.StudentsCatalog`
196
197        """
198        # Pass only given keywords to create FilteredCatalogQuery objects.
199        # This way we avoid
200        # trouble with `None` value ambivalences and queries are also
201        # faster (normally less indexes to ask). Drawback is, that
202        # developers must look into catalog to see what keywords are
203        # valid.
204        if kw.get('catalog', None) == 'coursetickets':
205            coursetickets = CourseTicketsQuery(**kw).query()
206            students = []
207            for ticket in coursetickets:
208                students.append(ticket.student)
209            return list(set(students))
210        # Payments can be filtered by payment date and payment category.
211        # These parameters are not keys of the catalog and must thus be
212        # removed from kw.
213        try:
214            del kw['payments_start']
215            del kw['payments_end']
216            del kw['paycat']
217            del kw['paysession']
218        except KeyError:
219            pass
220        # Coursetickets can be filtered by level and session.
221        # These parameters are not keys of the catalog and must thus be
222        # removed from kw.
223        try:
224            del kw['ct_level']
225            del kw['ct_session']
226            del kw['ct_semester']
227        except KeyError:
228            pass
229        query = StudentsQuery(**kw)
230        return query.query()
231
232    def get_selected(self, site, selected):
233        """Get set of selected students.
234        Returns a simple empty list or a list with `Student`
235        objects.
236        """
237        students = []
238        students_container = site.get('students', {})
239        for id in selected:
240            student = students_container.get(id, None)
241            if student is None:
242                # try matric number
243                result = list(StudentsQuery(matric_number=id).query())
244                if result:
245                    student = result[0]
246                else:
247                    continue
248            students.append(student)
249        return students
250
251    def export(self, values, filepath=None):
252        """Export `values`, an iterable, as CSV file.
253        If `filepath` is ``None``, a raw string with CSV data is returned.
254        """
255        writer, outfile = self.get_csv_writer(filepath)
256        for value in values:
257            self.write_item(value, writer)
258        return self.close_outfile(filepath, outfile)
259
260    def export_all(self, site, filepath=None):
261        """Export students into filepath as CSV data.
262        If `filepath` is ``None``, a raw string with CSV data is returned.
263        """
264        return self.export(self.filter_func(get_students(site)), filepath)
265
266    def export_student(self, student, filepath=None):
267        return self.export(self.filter_func([student]), filepath=filepath)
268
269    def export_filtered(self, site, filepath=None, **kw):
270        """Export items denoted by `kw`.
271        If `filepath` is ``None``, a raw string with CSV data should
272        be returned.
273        """
274        data = self.get_filtered(site, **kw)
275        return self.export(self.filter_func(data, **kw), filepath=filepath)
276
277    def export_selected(self,site, filepath=None, **kw):
278        """Export data for selected set of students.
279        """
280        selected = kw.get('selected', [])
281        data = self.get_selected(site, selected)
282        return self.export(self.filter_func(data, **kw), filepath=filepath)
283
284
285class StudentExporter(grok.GlobalUtility, StudentExporterBase):
286    """The Student Exporter first filters the set of students by searching the
287    students catalog. Then it exports student base data of this set of students.
288    """
289    grok.name('students')
290
291    fields = tuple(sorted(iface_names(IStudent))) + (
292        'password', 'state', 'history', 'certcode', 'is_postgrad',
293        'current_level', 'current_session')
294    title = _(u'Students')
295
296    def mangle_value(self, value, name, context=None):
297        """The mangler prepares the history messages and adds a hash symbol at
298        the end of the phone number to avoid annoying automatic number
299        transformation by Excel or Calc."""
300        if name == 'history':
301            value = value.messages
302        if 'phone' in name and value is not None:
303            # Append hash '#' to phone numbers to circumvent
304            # unwanted excel automatic
305            value = str('%s#' % value)
306        return super(
307            StudentExporter, self).mangle_value(
308            value, name, context=context)
309
310
311class StudentStudyCourseExporter(grok.GlobalUtility, StudentExporterBase):
312    """The Student Study Course Exporter first filters the set of students
313    by searching the students catalog. Then it exports the data of the current
314    study course container of each student from this set. It does
315    not export their content.
316    """
317    grok.name('studentstudycourses')
318
319    fields = tuple(sorted(iface_names(IStudentStudyCourse))) + ('student_id',)
320    title = _(u'Student Study Courses')
321
322    def filter_func(self, x, **kw):
323        return get_studycourses(x)
324
325    def mangle_value(self, value, name, context=None):
326        """The mangler determines the certificate code and the student id.
327        """
328        if name == 'certificate' and value is not None:
329            # XXX: hopefully cert codes are unique site-wide
330            value = value.code
331        if name == 'student_id' and context is not None:
332            student = context.student
333            value = getattr(student, name, None)
334        return super(
335            StudentStudyCourseExporter, self).mangle_value(
336            value, name, context=context)
337
338
339class StudentStudyLevelExporter(grok.GlobalUtility, StudentExporterBase):
340    """The Student Study Level Exporter first filters the set of students
341    by searching the students catalog. Then it exports the data of the student's
342    study level container data but not their content (course tickets).
343    The exporter iterates over all objects in the students' ``studycourse``
344    containers.
345    """
346    grok.name('studentstudylevels')
347
348    fields = tuple(sorted(iface_names(
349        IStudentStudyLevel))) + (
350        'student_id', 'number_of_tickets','certcode')
351    title = _(u'Student Study Levels')
352
353    def filter_func(self, x, **kw):
354        return get_levels(x)
355
356    def mangle_value(self, value, name, context=None):
357        """The mangler determines the student id, nothing else.
358        """
359        if name == 'student_id' and context is not None:
360            student = context.student
361            value = getattr(student, name, None)
362        return super(
363            StudentStudyLevelExporter, self).mangle_value(
364            value, name, context=context)
365
366class CourseTicketExporter(grok.GlobalUtility, StudentExporterBase):
367    """The Course Ticket Exporter exports course tickets. Usually,
368    the exporter first filters the set of students by searching the
369    students catalog. Then it collects and iterates over all ``studylevel``
370    containers of the filtered student set and finally
371    iterates over all items inside these containers.
372
373    If the course code is passed through, the exporter uses a different
374    catalog. It searches for students in the course tickets catalog and
375    exports those course tickets which belong to the given course code and
376    also meet level and session passed through at the same time.
377    This happens if the exporter is called at course level in the academic
378    section.
379    """
380    grok.name('coursetickets')
381
382    fields = tuple(sorted(iface_names(ICourseTicket) +
383        ['level', 'code', 'level_session'])) + ('student_id',
384        'certcode', 'display_fullname')
385    title = _(u'Course Tickets')
386
387    def filter_func(self, x, **kw):
388        return get_tickets(x, **kw)
389
390    def mangle_value(self, value, name, context=None):
391        """The mangler determines the student's id and fullname.
392        """
393        if context is not None:
394            student = context.student
395            if name in ('student_id', 'display_fullname') and student is not None:
396                value = getattr(student, name, None)
397        return super(
398            CourseTicketExporter, self).mangle_value(
399            value, name, context=context)
400
401class DataForLecturerExporter(grok.GlobalUtility, StudentExporterBase):
402    """The Data for Lecturer Exporter searches for students in the course
403    tickets catalog and exports those course tickets which belong to the
404    given course code, meet level and session passed through at the
405    same time, and which are editable by lecturers. This exporter can only
406    be called at course level in the academic section.
407    """
408    grok.name('lecturer')
409
410    fields = ('matric_number', 'student_id', 'display_fullname',
411              'level', 'code', 'level_session', 'score')
412
413    title = _(u'Data for Lecturer')
414
415    def filter_func(self, x, **kw):
416        return get_tickets_for_lecturer(x, **kw)
417
418    def mangle_value(self, value, name, context=None):
419        """The mangler determines the student's id and fullname.
420        """
421        if context is not None:
422            student = context.student
423            if name in ('matric_number',
424                        'reg_number',
425                        'student_id',
426                        'display_fullname',) and student is not None:
427                value = getattr(student, name, None)
428        return super(
429            DataForLecturerExporter, self).mangle_value(
430            value, name, context=context)
431
432class StudentPaymentExporter(grok.GlobalUtility, StudentExporterBase):
433    """The Student Payment Exporter first filters the set of students
434    by searching the students catalog. Then it exports student payment
435    tickets by iterating over the items of the student's ``payments``
436    container. If the payment period is given, only tickets, which were
437    paid in payment period, are considered for export.
438    """
439    grok.name('studentpayments')
440
441    fields = tuple(
442        sorted(iface_names(
443            IStudentOnlinePayment, exclude_attribs=False,
444            omit=['display_item', 'certificate', 'student']))) + (
445            'student_id','state','current_session')
446    title = _(u'Student Payments')
447
448    def filter_func(self, x, **kw):
449        return get_payments(x, **kw)
450
451    def mangle_value(self, value, name, context=None):
452        """The mangler determines the student's id, registration
453        state and current session.
454        """
455        if context is not None:
456            student = context.student
457            if name in ['student_id','state',
458                        'current_session'] and student is not None:
459                value = getattr(student, name, None)
460        return super(
461            StudentPaymentExporter, self).mangle_value(
462            value, name, context=context)
463
464class StudentUnpaidPaymentExporter(StudentPaymentExporter):
465    """The Student Unpaid Payment Exporter works just like the
466    Student Payments Exporter but it exports only unpaid tickets.
467    This exporter is designed for finding and finally purging outdated
468    payment ticket.
469    """
470    grok.name('studentunpaidpayments')
471
472    title = _(u'Student Unpaid Payments')
473
474    def filter_func(self, x, **kw):
475        return get_payments(x, p_states=('unpaid',) , **kw)
476
477class DataForBursaryExporter(StudentPaymentExporter):
478    """The Data for Bursary Exporter works just like the Student Payment
479    Exporter but it exports much more information about the student. It combines
480    payment and student data in one table in order to spare postprocessing of
481    two seperate export files. The exporter is primarily used by bursary
482    officers who have exclusively access to this exporter. The exporter
483    exports ``paid`` and ``waived`` payment tickets.
484    """
485    grok.name('bursary')
486
487    def filter_func(self, x, **kw):
488        return get_payments(x, p_states=('paid', 'waived'), **kw)
489
490    fields = tuple(
491        sorted(iface_names(
492            IStudentOnlinePayment, exclude_attribs=False,
493            omit=['display_item', 'certificate', 'student']))) + (
494            'student_id','matric_number','reg_number',
495            'firstname', 'middlename', 'lastname',
496            'state','current_session',
497            'entry_session', 'entry_mode',
498            'faccode', 'depcode','certcode')
499    title = _(u'Payment Data for Bursary')
500
501    def mangle_value(self, value, name, context=None):
502        """The mangler fetches the student data.
503        """
504        if context is not None:
505            student = context.student
506            if name in [
507                'student_id','matric_number', 'reg_number',
508                'firstname', 'middlename', 'lastname',
509                'state', 'current_session',
510                'entry_session', 'entry_mode',
511                'faccode', 'depcode', 'certcode'] and student is not None:
512                value = getattr(student, name, None)
513        return super(
514            StudentPaymentExporter, self).mangle_value(
515            value, name, context=context)
516
517class AccommodationPaymentsExporter(DataForBursaryExporter):
518    """The Accommodation Payments Exporter works like the Data for Bursary
519    Exporter above. The exporter exports ``paid`` and ``waived`` payment
520    tickets with category ``bed_allocation`` or ``hostel_maintenance``.
521    The exporter is primarily used by accommodation officers who have
522    exclusively access to this exporter.
523    """
524    grok.name('accommodationpayments')
525
526    def filter_func(self, x, **kw):
527        kw['paycat'] = 'bed_allocation'
528        payments = get_payments(x, p_states=('paid', 'waived'), **kw)
529        kw['paycat'] = 'hostel_maintenance'
530        payments += get_payments(x, p_states=('paid', 'waived'), **kw)
531        return payments
532
533    title = _(u'Accommodation Payments')
534
535class BedTicketExporter(grok.GlobalUtility, StudentExporterBase):
536    """The Bed Ticket Exporter first filters the set of students
537    by searching the students catalog. Then it exports bed
538    tickets by iterating over the items of the student's ``accommodation``
539    container.
540    """
541    grok.name('bedtickets')
542
543    fields = tuple(
544        sorted(iface_names(
545            IBedTicket, exclude_attribs=False,
546            omit=['display_coordinates', 'maint_payment_made']))) + (
547            'student_id', 'actual_bed_type')
548    title = _(u'Bed Tickets')
549
550    def filter_func(self, x, **kw):
551        return get_bedtickets(x)
552
553    def mangle_value(self, value, name, context=None):
554        """The mangler determines the student id and the type of the bed
555        which has been booked in the ticket.
556        """
557        if context is not None:
558            student = context.student
559            if name in ['student_id'] and student is not None:
560                value = getattr(student, name, None)
561        if name == 'bed' and value is not None:
562            value = getattr(value, 'bed_id', None)
563        if name == 'actual_bed_type':
564            value = getattr(getattr(context, 'bed', None), 'bed_type', None)
565        return super(
566            BedTicketExporter, self).mangle_value(
567            value, name, context=context)
568
569class SchoolFeePaymentsOverviewExporter(StudentExporter):
570    """The School Fee Payments Overview Exporter first filters the set of students
571    by searching the students catalog. Then it exports some student base data
572    together with the total school fee amount paid in each year over a
573    predefined year range (current year - 9, ... , current year + 1).
574    """
575    grok.name('sfpaymentsoverview')
576
577    curr_year = datetime.now().year
578    year_range = range(curr_year - 11, curr_year + 1)
579    year_range_tuple = tuple([str(year) for year in year_range])
580    fields = ('student_id', 'matric_number', 'display_fullname',
581        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
582        'current_level', 'current_session', 'current_mode',
583        'entry_session', 'reg_number'
584        ) + year_range_tuple
585    title = _(u'Student School Fee Payments Overview')
586
587    def mangle_value(self, value, name, context=None):
588        """The mangler summarizes the school fee amounts made per year. It
589        iterates over all paid school fee payment tickets and
590        adds together the amounts paid in a year. Waived payments
591        are marked ``waived``.
592        """
593        if name in self.year_range_tuple and context is not None:
594            value = 0
595            for ticket in context['payments'].values():
596                if ticket.p_category == 'schoolfee' and \
597                    ticket.p_session == int(name):
598                    if ticket.p_state == 'waived':
599                        value = 'waived'
600                        break
601                    if ticket.p_state == 'paid':
602                        try:
603                            value += ticket.amount_auth
604                        except TypeError:
605                            pass
606            if value == 0:
607                value = ''
608            elif isinstance(value, float):
609                value = round(value, 2)
610        return super(
611            StudentExporter, self).mangle_value(
612            value, name, context=context)
613
614class SessionPaymentsOverviewExporter(StudentExporter):
615    """The Session Payments Overview Exporter first filters the set of students
616    by searching the students catalog. Then it exports some student base data
617    together with the total amount paid in predefined payment categories
618    over the previous three session (referring to current academic session).
619    Sample output:
620
621    header: ``...schoolfee13,schoolfee14,schoolfee15,gown13,gown14,gown15...``
622
623    data: ``...2000.0,,3000.0,,,1000.0,...``
624
625    This csv data string means that the student paid 2000.0 school fee in 2013
626    and 3000.0 in 2015. S/He furthermore paid 1000.0 for gown rental in 2015.
627    """
628    grok.name('sessionpaymentsoverview')
629
630    paycats = ('schoolfee', 'clearance', 'gown', 'transcript')
631    regular_fields = ('student_id', 'matric_number', 'display_fullname',
632        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
633        'current_level', 'current_session', 'current_mode',
634        'entry_session', 'reg_number'
635        )
636    title = _(u'Session Payments Overview')
637
638    @property
639    def paycatyears(self):
640        cas = grok.getSite()['configuration'].current_academic_session
641        paycatyears = []
642        if cas:
643            year_range = range(cas - 2, cas+1)
644            year_range_tuple = tuple([str(year)[2:] for year in year_range])
645            paycatyears = [
646                cat+year for cat in self.paycats for year in year_range_tuple]
647        return paycatyears
648
649    @property
650    def fields(self):
651        return self.regular_fields + tuple(self.paycatyears)
652
653    def mangle_value(self, value, name, context=None):
654        """
655        """
656        amounts = dict()
657        for catyear in self.paycatyears:
658            amounts[catyear] = 0.0
659        if name[:-2] in self.paycats and context is not None:
660            for ticket in context['payments'].values():
661                if ticket.p_category == name[:-2]:
662                    if ticket.p_state in ('waived', 'paid'):
663                        if str(ticket.p_session)[2:] == name[-2:]:
664                            amounts[name] += ticket.amount_auth
665            if amounts[name] == 0.0:
666                value = ''
667            elif isinstance(amounts[name], float):
668                value = round(amounts[name], 2)
669        return super(
670            StudentExporter, self).mangle_value(
671            value, name, context=context)
672
673class StudentStudyLevelsOverviewExporter(StudentExporter):
674    """The Student Study Levels Overview Exporter first filters the set of
675    students by searching the students catalog. Then it exports some student
676    base data together with the session key of registered levels.
677    Sample output:
678
679    header: ``...100,110,120,200,210,220,300...``
680
681    data: ``...2010,,,2011,2012,,2013...``
682
683    This csv data string means that level 100 was registered in session
684    2010/2011, level 200 in session 2011/2012, level 210 (200 on 1st probation)
685    in session 2012/2013 and level 300 in session 2013/2014.
686    """
687    grok.name('studylevelsoverview')
688
689    avail_levels = tuple([str(x) for x in study_levels(None)])
690
691    fields = ('student_id', ) + (
692        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
693        'entry_session', 'current_level', 'current_session',
694        ) + avail_levels
695    title = _(u'Student Study Levels Overview')
696
697    def mangle_value(self, value, name, context=None):
698        """The mangler checks if a given level has been registered. It returns
699        the ``level_session`` attribute of the student study level object
700        if the named level exists.
701        """
702        if name in self.avail_levels and context is not None:
703            value = ''
704            for level in context['studycourse'].values():
705                if level.level == int(name):
706                    value = '%s' % level.level_session
707                    break
708        return super(
709            StudentExporter, self).mangle_value(
710            value, name, context=context)
711
712class ComboCardDataExporter(grok.GlobalUtility, StudentExporterBase):
713    """Like all other exporters the Combo Card Data Exporter first filters the
714    set of students by searching the students catalog. Then it exports some
715    student base data which are neccessary to print for the Interswitch combo
716    card (identity card for students). The output contains a ``passport_path``
717    column which contains the filesystem path of the passport image file.
718    If no path is given, no passport image file exists.
719    """
720    grok.name('combocard')
721
722    fields = ('display_fullname',
723              'student_id','matric_number',
724              'certificate', 'faculty', 'department', 'passport_path')
725    title = _(u'Combo Card Data')
726
727    def mangle_value(self, value, name, context=None):
728        """The mangler determines the titles of faculty, department
729        and certificate. It also computes the path of passport image file
730        stored in the filesystem.
731        """
732        certificate = context['studycourse'].certificate
733        if name == 'certificate' and certificate is not None:
734            value = certificate.title
735        if name == 'department' and certificate is not None:
736            value = certificate.__parent__.__parent__.longtitle
737        if name == 'faculty' and certificate is not None:
738            value = certificate.__parent__.__parent__.__parent__.longtitle
739        if name == 'passport_path' and certificate is not None:
740            file_id = IFileStoreNameChooser(context).chooseName(
741                attr='passport.jpg')
742            os_path = getUtility(IExtFileStore)._pathFromFileID(file_id)
743            if not os.path.exists(os_path):
744                value = None
745            else:
746                value = '/'.join(os_path.split('/')[-4:])
747        return super(
748            ComboCardDataExporter, self).mangle_value(
749            value, name, context=context)
Note: See TracBrowser for help on using the repository browser.