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

Last change on this file since 14996 was 14984, checked in by Henrik Bettermann, 7 years ago

Add ticket level and session filter to CourseTicketExporter.

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