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

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

Add payment status scholarship.

  • Property svn:keywords set to Id
File size: 30.1 KB
Line 
1## $Id: export.py 15792 2019-11-10 20:11:40Z 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``, ``waived`` and ``scholarship`` payment tickets.
484    """
485    grok.name('bursary')
486
487    def filter_func(self, x, **kw):
488        return get_payments(x, p_states=('paid', 'waived', 'scholarship'), **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``, ``waived`` and ``scholarship``
520    payment 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=(
529          'paid', 'waived', 'scholarship'), **kw)
530        kw['paycat'] = 'hostel_maintenance'
531        payments += get_payments(x, p_states=(
532          'paid', 'waived', 'scholarship'), **kw)
533        return payments
534
535    title = _(u'Accommodation Payments')
536
537class BedTicketExporter(grok.GlobalUtility, StudentExporterBase):
538    """The Bed Ticket Exporter first filters the set of students
539    by searching the students catalog. Then it exports bed
540    tickets by iterating over the items of the student's ``accommodation``
541    container.
542    """
543    grok.name('bedtickets')
544
545    fields = tuple(
546        sorted(iface_names(
547            IBedTicket, exclude_attribs=False,
548            omit=['display_coordinates', 'maint_payment_made']))) + (
549            'student_id', 'actual_bed_type')
550    title = _(u'Bed Tickets')
551
552    def filter_func(self, x, **kw):
553        return get_bedtickets(x)
554
555    def mangle_value(self, value, name, context=None):
556        """The mangler determines the student id and the type of the bed
557        which has been booked in the ticket.
558        """
559        if context is not None:
560            student = context.student
561            if name in ['student_id'] and student is not None:
562                value = getattr(student, name, None)
563        if name == 'bed' and value is not None:
564            value = getattr(value, 'bed_id', None)
565        if name == 'actual_bed_type':
566            value = getattr(getattr(context, 'bed', None), 'bed_type', None)
567        return super(
568            BedTicketExporter, self).mangle_value(
569            value, name, context=context)
570
571class SchoolFeePaymentsOverviewExporter(StudentExporter):
572    """The School Fee Payments Overview Exporter first filters the set of students
573    by searching the students catalog. Then it exports some student base data
574    together with the total school fee amount paid in each year over a
575    predefined year range (current year - 9, ... , current year + 1).
576    """
577    grok.name('sfpaymentsoverview')
578
579    curr_year = datetime.now().year
580    year_range = range(curr_year - 11, curr_year + 1)
581    year_range_tuple = tuple([str(year) for year in year_range])
582    fields = ('student_id', 'matric_number', 'display_fullname',
583        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
584        'current_level', 'current_session', 'current_mode',
585        'entry_session', 'reg_number'
586        ) + year_range_tuple
587    title = _(u'Student School Fee Payments Overview')
588
589    def mangle_value(self, value, name, context=None):
590        """The mangler summarizes the school fee amounts made per year. It
591        iterates over all paid school fee payment tickets and
592        adds together the amounts paid in a year. Waived payments
593        are marked ``waived`` and scholarship payments marked `scholarship`.
594        """
595        if name in self.year_range_tuple and context is not None:
596            value = 0
597            for ticket in context['payments'].values():
598                if ticket.p_category == 'schoolfee' and \
599                    ticket.p_session == int(name):
600                    if ticket.p_state == 'waived':
601                        value = 'waived'
602                        break
603                    if ticket.p_state == 'scholarship':
604                        value = 'scholarship'
605                        break
606                    if ticket.p_state == 'paid':
607                        try:
608                            value += ticket.amount_auth
609                        except TypeError:
610                            pass
611            if value == 0:
612                value = ''
613            elif isinstance(value, float):
614                value = round(value, 2)
615        return super(
616            StudentExporter, self).mangle_value(
617            value, name, context=context)
618
619class SessionPaymentsOverviewExporter(StudentExporter):
620    """The Session Payments Overview Exporter first filters the set of students
621    by searching the students catalog. Then it exports some student base data
622    together with the total amount paid in predefined payment categories
623    over the previous three session (referring to current academic session).
624    Sample output:
625
626    header: ``...schoolfee13,schoolfee14,schoolfee15,gown13,gown14,gown15...``
627
628    data: ``...2000.0,,3000.0,,,1000.0,...``
629
630    This csv data string means that the student paid 2000.0 school fee in 2013
631    and 3000.0 in 2015. S/He furthermore paid 1000.0 for gown rental in 2015.
632    """
633    grok.name('sessionpaymentsoverview')
634
635    paycats = ('schoolfee', 'clearance', 'gown', 'transcript')
636    regular_fields = ('student_id', 'matric_number', 'display_fullname',
637        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
638        'current_level', 'current_session', 'current_mode',
639        'entry_session', 'reg_number'
640        )
641    title = _(u'Session Payments Overview')
642
643    @property
644    def paycatyears(self):
645        cas = grok.getSite()['configuration'].current_academic_session
646        paycatyears = []
647        if cas:
648            year_range = range(cas - 2, cas+1)
649            year_range_tuple = tuple([str(year)[2:] for year in year_range])
650            paycatyears = [
651                cat+year for cat in self.paycats for year in year_range_tuple]
652        return paycatyears
653
654    @property
655    def fields(self):
656        return self.regular_fields + tuple(self.paycatyears)
657
658    def mangle_value(self, value, name, context=None):
659        """
660        """
661        amounts = dict()
662        for catyear in self.paycatyears:
663            amounts[catyear] = 0.0
664        if name[:-2] in self.paycats and context is not None:
665            for ticket in context['payments'].values():
666                if ticket.p_category == name[:-2]:
667                    if ticket.p_state in ('waived', 'paid'):
668                        if str(ticket.p_session)[2:] == name[-2:]:
669                            amounts[name] += ticket.amount_auth
670            if amounts[name] == 0.0:
671                value = ''
672            elif isinstance(amounts[name], float):
673                value = round(amounts[name], 2)
674        return super(
675            StudentExporter, self).mangle_value(
676            value, name, context=context)
677
678class StudentStudyLevelsOverviewExporter(StudentExporter):
679    """The Student Study Levels Overview Exporter first filters the set of
680    students by searching the students catalog. Then it exports some student
681    base data together with the session key of registered levels.
682    Sample output:
683
684    header: ``...100,110,120,200,210,220,300...``
685
686    data: ``...2010,,,2011,2012,,2013...``
687
688    This csv data string means that level 100 was registered in session
689    2010/2011, level 200 in session 2011/2012, level 210 (200 on 1st probation)
690    in session 2012/2013 and level 300 in session 2013/2014.
691    """
692    grok.name('studylevelsoverview')
693
694    avail_levels = tuple([str(x) for x in study_levels(None)])
695
696    fields = ('student_id', ) + (
697        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
698        'entry_session', 'current_level', 'current_session',
699        ) + avail_levels
700    title = _(u'Student Study Levels Overview')
701
702    def mangle_value(self, value, name, context=None):
703        """The mangler checks if a given level has been registered. It returns
704        the ``level_session`` attribute of the student study level object
705        if the named level exists.
706        """
707        if name in self.avail_levels and context is not None:
708            value = ''
709            for level in context['studycourse'].values():
710                if level.level == int(name):
711                    value = '%s' % level.level_session
712                    break
713        return super(
714            StudentExporter, self).mangle_value(
715            value, name, context=context)
716
717class ComboCardDataExporter(grok.GlobalUtility, StudentExporterBase):
718    """Like all other exporters the Combo Card Data Exporter first filters the
719    set of students by searching the students catalog. Then it exports some
720    student base data which are neccessary to print for the Interswitch combo
721    card (identity card for students). The output contains a ``passport_path``
722    column which contains the filesystem path of the passport image file.
723    If no path is given, no passport image file exists.
724    """
725    grok.name('combocard')
726
727    fields = ('display_fullname',
728              'student_id','matric_number',
729              'certificate', 'faculty', 'department', 'passport_path')
730    title = _(u'Combo Card Data')
731
732    def mangle_value(self, value, name, context=None):
733        """The mangler determines the titles of faculty, department
734        and certificate. It also computes the path of passport image file
735        stored in the filesystem.
736        """
737        certificate = context['studycourse'].certificate
738        if name == 'certificate' and certificate is not None:
739            value = certificate.title
740        if name == 'department' and certificate is not None:
741            value = certificate.__parent__.__parent__.longtitle
742        if name == 'faculty' and certificate is not None:
743            value = certificate.__parent__.__parent__.__parent__.longtitle
744        if name == 'passport_path' and certificate is not None:
745            file_id = IFileStoreNameChooser(context).chooseName(
746                attr='passport.jpg')
747            os_path = getUtility(IExtFileStore)._pathFromFileID(file_id)
748            if not os.path.exists(os_path):
749                value = None
750            else:
751                value = '/'.join(os_path.split('/')[-4:])
752        return super(
753            ComboCardDataExporter, self).mangle_value(
754            value, name, context=context)
Note: See TracBrowser for help on using the repository browser.