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

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

Be more precise.

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