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

Last change on this file since 15369 was 15297, checked in by Henrik Bettermann, 6 years ago

Remove bug. paysession is a string.

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