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

Last change on this file since 10009 was 9984, checked in by Henrik Bettermann, 12 years ago

FCEOkene wants to hide the bed coordinates if maintenance fee is not yet paid. Thus we need additional property attributes which return the p_item (payment tickets) and the bed_coordinates attributes (bed tickets) by default and can be easily customized to hide this information in certain cases. bed_coordinates and p_item must be omitted on forms. The new display_ attributes are displayed instead.

All packages must now be adjusted.

The 'cost-benefit ratio' of these kinds of customizations is quite bad and we should think about declining such customization requests. However, I started customization and these are the changed made in the base package.

  • Property svn:keywords set to Id
File size: 15.3 KB
RevLine 
[8057]1## $Id: export.py 9984 2013-02-24 08:29:24Z 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
[9574]22from datetime import datetime
[9937]23from zope.component import getUtility
24from waeup.kofa.interfaces import IExtFileStore, IFileStoreNameChooser
[7944]25from waeup.kofa.interfaces import MessageFactory as _
[9843]26from waeup.kofa.students.catalog import StudentsQuery, CourseTicketsQuery
[8015]27from waeup.kofa.students.interfaces import (
[8371]28    IStudent, IStudentStudyCourse, IStudentStudyLevel, ICourseTicket,
[9427]29    IStudentOnlinePayment, ICSVStudentExporter, IBedTicket)
[9787]30from waeup.kofa.students.vocabularies import study_levels
[7944]31from waeup.kofa.utils.batching import ExporterBase
32from waeup.kofa.utils.helpers import iface_names
33
[8400]34#: A tuple containing all exporter names referring to students or
35#: subobjects thereof.
[9834]36EXPORTER_NAMES = ('students', 'studentstudycourses',
37        'studentstudylevels', 'coursetickets',
38        'studentpayments', 'bedtickets', 'paymentsoverview',
[9936]39        'studylevelsoverview', 'combocard')
[8400]40
[9787]41def get_students(site, stud_filter=StudentsQuery()):
[8414]42    """Get all students registered in catalog in `site`.
[7944]43    """
[9787]44    return stud_filter.query()
[8414]45
46def get_studycourses(students):
47    """Get studycourses of `students`.
48    """
49    return [x.get('studycourse', None) for x in students
50            if x is not None]
51
52def get_levels(students):
53    """Get all studylevels of `students`.
54    """
55    levels = []
56    for course in get_studycourses(students):
57        for level in course.values():
58            levels.append(level)
59    return levels
60
[9844]61def get_tickets(students, **kw):
62    """Get course tickets of `students`.
63
64    If code is passed through filter course tickets
65    which belong to this course code.
[8414]66    """
67    tickets = []
[9844]68    code = kw.get('code', None)
69    if code is None:
70        for level in get_levels(students):
71            for ticket in level.values():
72                tickets.append(ticket)
73    else:
74        for level in get_levels(students):
75            for ticket in level.values():
76                if ticket.code == code:
77                    tickets.append(ticket)
[8414]78    return tickets
79
80def get_payments(students):
81    """Get all payments of `students`.
82    """
83    payments = []
84    for student in students:
85        for payment in student.get('payments', {}).values():
86            payments.append(payment)
87    return payments
88
[9427]89def get_bedtickets(students):
90    """Get all bedtickets of `students`.
91    """
92    tickets = []
93    for student in students:
94        for ticket in student.get('accommodation', {}).values():
95            tickets.append(ticket)
96    return tickets
[8414]97
98class StudentExporterBase(ExporterBase):
99    """Exporter for students or related objects.
100
101    This is a baseclass.
102    """
103    grok.baseclass()
[8411]104    grok.implements(ICSVStudentExporter)
105    grok.provides(ICSVStudentExporter)
[8414]106
[9844]107    def filter_func(self, x, **kw):
[9802]108        return x
[9797]109
[9801]110    def get_filtered(self, site, **kw):
[9843]111        """Get students from a catalog filtered by keywords.
[9801]112
[9843]113        students_catalog is the default catalog. The keys must be valid
[9933]114        catalog index names.
[9843]115        Returns a simple empty list, a list with `Student`
116        objects or a catalog result set with `Student`
[9801]117        objects.
118
119        .. seealso:: `waeup.kofa.students.catalog.StudentsCatalog`
120
121        """
[9843]122        # Pass only given keywords to create FilteredCatalogQuery objects.
123        # This way we avoid
[9801]124        # trouble with `None` value ambivalences and queries are also
125        # faster (normally less indexes to ask). Drawback is, that
126        # developers must look into catalog to see what keywords are
127        # valid.
[9845]128        if kw.get('catalog', None) == 'coursetickets':
[9843]129            coursetickets = CourseTicketsQuery(**kw).query()
130            students = []
131            for ticket in coursetickets:
132                students.append(ticket.student)
133            return list(set(students))
[9801]134        query = StudentsQuery(**kw)
[9797]135        return query.query()
136
[8414]137    def export(self, values, filepath=None):
138        """Export `values`, an iterable, as CSV file.
139
140        If `filepath` is ``None``, a raw string with CSV data is returned.
141        """
142        writer, outfile = self.get_csv_writer(filepath)
143        for value in values:
144            self.write_item(value, writer)
145        return self.close_outfile(filepath, outfile)
146
[9797]147    def export_all(self, site, filepath=None):
148        """Export students into filepath as CSV data.
149
150        If `filepath` is ``None``, a raw string with CSV data is returned.
[9763]151        """
[9802]152        return self.export(self.filter_func(get_students(site)), filepath)
[8414]153
[9797]154    def export_student(self, student, filepath=None):
155        return self.export(self.filter_func([student]), filepath=filepath)
[9734]156
[9802]157    def export_filtered(self, site, filepath=None, **kw):
158        """Export items denoted by `kw`.
[9797]159
[9802]160        If `filepath` is ``None``, a raw string with CSV data should
161        be returned.
162        """
163        data = self.get_filtered(site, **kw)
[9844]164        return self.export(self.filter_func(data, **kw), filepath=filepath)
[9802]165
166
[8414]167class StudentsExporter(grok.GlobalUtility, StudentExporterBase):
168    """Exporter for Students.
169    """
[7944]170    grok.name('students')
171
172    #: Fieldnames considered by this exporter
[9936]173    fields = tuple(sorted(iface_names(IStudent))) + (
[9253]174        'password', 'state', 'history', 'certcode', 'is_postgrad',
175        'current_level', 'current_session')
[7944]176
177    #: The title under which this exporter will be displayed
178    title = _(u'Students')
179
[8493]180    def mangle_value(self, value, name, context=None):
181        if name == 'history':
182            value = value.messages
[8971]183        if name == 'phone' and value is not None:
184            # Append hash '#' to phone numbers to circumvent
185            # unwanted excel automatic
[8947]186            value = str('%s#' % value)
[8493]187        return super(
188            StudentsExporter, self).mangle_value(
189            value, name, context=context)
190
[7944]191
[8414]192class StudentStudyCourseExporter(grok.GlobalUtility, StudentExporterBase):
[7994]193    """Exporter for StudentStudyCourses.
194    """
195    grok.name('studentstudycourses')
196
197    #: Fieldnames considered by this exporter
[8493]198    fields = tuple(sorted(iface_names(IStudentStudyCourse))) + ('student_id',)
[7994]199
200    #: The title under which this exporter will be displayed
201    title = _(u'Student Study Courses')
202
[9844]203    def filter_func(self, x, **kw):
[9797]204        return get_studycourses(x)
205
[7994]206    def mangle_value(self, value, name, context=None):
[8493]207        """Treat location values special.
[7994]208        """
209        if name == 'certificate' and value is not None:
210            # XXX: hopefully cert codes are unique site-wide
211            value = value.code
[8493]212        if name == 'student_id' and context is not None:
[8736]213            student = context.student
[8493]214            value = getattr(student, name, None)
[7994]215        return super(
216            StudentStudyCourseExporter, self).mangle_value(
217            value, name, context=context)
218
219
[8414]220class StudentStudyLevelExporter(grok.GlobalUtility, StudentExporterBase):
[8015]221    """Exporter for StudentStudyLevels.
222    """
223    grok.name('studentstudylevels')
224
225    #: Fieldnames considered by this exporter
[8493]226    fields = tuple(sorted(iface_names(
[9253]227        IStudentStudyLevel) + ['level'])) + (
228        'student_id', 'number_of_tickets','certcode')
[8015]229
230    #: The title under which this exporter will be displayed
231    title = _(u'Student Study Levels')
232
[9844]233    def filter_func(self, x, **kw):
[9802]234        return get_levels(x)
235
[8015]236    def mangle_value(self, value, name, context=None):
[8493]237        """Treat location values special.
[8015]238        """
[8493]239        if name == 'student_id' and context is not None:
[8736]240            student = context.student
[8493]241            value = getattr(student, name, None)
[8015]242        return super(
243            StudentStudyLevelExporter, self).mangle_value(
244            value, name, context=context)
245
[8414]246class CourseTicketExporter(grok.GlobalUtility, StudentExporterBase):
[8342]247    """Exporter for CourseTickets.
248    """
249    grok.name('coursetickets')
250
251    #: Fieldnames considered by this exporter
[8493]252    fields = tuple(sorted(iface_names(ICourseTicket) +
[9859]253        ['level', 'code', 'level_session'])) + ('student_id', 'certcode')
[8342]254
255    #: The title under which this exporter will be displayed
256    title = _(u'Course Tickets')
257
[9844]258    def filter_func(self, x, **kw):
259        return get_tickets(x, **kw)
[9802]260
[8342]261    def mangle_value(self, value, name, context=None):
262        """Treat location values special.
263        """
264        if context is not None:
[8736]265            student = context.student
[8493]266            if name == 'student_id' and student is not None:
[8342]267                value = getattr(student, name, None)
268            if name == 'level':
[9925]269                value = getattr(context, 'level', lambda: None)
[9859]270            if name == 'level_session':
[9925]271                value = getattr(context, 'level_session', lambda: None)
[8342]272        return super(
273            CourseTicketExporter, self).mangle_value(
274            value, name, context=context)
275
276
[9832]277class StudentPaymentsExporter(grok.GlobalUtility, StudentExporterBase):
[8371]278    """Exporter for OnlinePayment instances.
279    """
280    grok.name('studentpayments')
281
282    #: Fieldnames considered by this exporter
283    fields = tuple(
[8493]284        sorted(iface_names(
[9984]285            IStudentOnlinePayment, exclude_attribs=False,
286            omit=['display_item']))) + (
287            'student_id','student_state','current_session')
[8371]288
289    #: The title under which this exporter will be displayed
[8576]290    title = _(u'Student Payments')
[8371]291
[9844]292    def filter_func(self, x, **kw):
[9802]293        return get_payments(x)
294
[8371]295    def mangle_value(self, value, name, context=None):
296        """Treat location values special.
297        """
298        if context is not None:
[8736]299            student = context.student
[8493]300            if name in ['student_id'] and student is not None:
[8371]301                value = getattr(student, name, None)
302        return super(
[9832]303            StudentPaymentsExporter, self).mangle_value(
[8371]304            value, name, context=context)
305
[9427]306class BedTicketsExporter(grok.GlobalUtility, StudentExporterBase):
307    """Exporter for BedTicket instances.
308    """
309    grok.name('bedtickets')
310
311    #: Fieldnames considered by this exporter
312    fields = tuple(
313        sorted(iface_names(
[9984]314            IBedTicket, exclude_attribs=False,
315            omit=['display_coordinates']))) + (
316            'student_id', 'actual_bed_type')
[9427]317
318    #: The title under which this exporter will be displayed
319    title = _(u'Bed Tickets')
320
[9844]321    def filter_func(self, x, **kw):
[9802]322        return get_bedtickets(x)
323
[9427]324    def mangle_value(self, value, name, context=None):
325        """Treat location values and others special.
326        """
327        if context is not None:
328            student = context.student
329            if name in ['student_id'] and student is not None:
330                value = getattr(student, name, None)
331        if name == 'bed' and value is not None:
332            value = getattr(value, 'bed_id', None)
333        if name == 'actual_bed_type':
334            value = getattr(getattr(context, 'bed', None), 'bed_type')
335        return super(
336            BedTicketsExporter, self).mangle_value(
337            value, name, context=context)
338
[9574]339class StudentPaymentsOverviewExporter(StudentsExporter):
340    """Exporter for students with payment overview.
341    """
342    grok.name('paymentsoverview')
343
344    curr_year = datetime.now().year
345    year_range = range(curr_year - 9, curr_year + 1)
346    year_range_tuple = tuple([str(year) for year in year_range])
347
348    #: Fieldnames considered by this exporter
[9983]349    fields = ('student_id', 'matric_number', 'display_fullname',
[9574]350        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
[9807]351        'current_level', 'current_session', 'current_mode',
[9574]352        ) + year_range_tuple
353
354    #: The title under which this exporter will be displayed
355    title = _(u'Student Payments Overview')
356
357    def mangle_value(self, value, name, context=None):
358        if name in self.year_range_tuple and context is not None:
359            value = ''
360            for ticket in context['payments'].values():
361                if ticket.p_state == 'paid' and \
362                    ticket.p_category == 'schoolfee' and \
363                    ticket.p_session == int(name):
364                    value = ticket.amount_auth
365                    break
366        return super(
367            StudentsExporter, self).mangle_value(
[9734]368            value, name, context=context)
[9744]369
370class StudentStudyLevelsOverviewExporter(StudentsExporter):
371    """Exporter for students with study level overview.
372    """
373    grok.name('studylevelsoverview')
374
[9787]375    avail_levels = tuple([str(x) for x in study_levels(None)])
[9744]376
377    #: Fieldnames considered by this exporter
378    fields = ('student_id', ) + (
379        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
[9761]380        'entry_session', 'current_level', 'current_session',
[9787]381        ) + avail_levels
[9744]382
383    #: The title under which this exporter will be displayed
384    title = _(u'Student Study Levels Overview')
385
386    def mangle_value(self, value, name, context=None):
[9787]387        if name in self.avail_levels and context is not None:
[9744]388            value = ''
389            for level in context['studycourse'].values():
390                if level.level == int(name):
[9761]391                    #value = '%s|%s|%s|%s' % (
392                    #    level.level_session,
393                    #    len(level),
394                    #    level.validated_by,
395                    #    level.level_verdict)
396                    value = '%s' % level.level_session
[9744]397                    break
398        return super(
399            StudentsExporter, self).mangle_value(
400            value, name, context=context)
[9936]401
402class ComboCardDataExporter(grok.GlobalUtility, StudentExporterBase):
403    """Exporter for Interswitch Combo Card Data.
404    """
405    grok.name('combocard')
406
407    #: Fieldnames considered by this exporter
408    fields = ('display_fullname',
[9937]409              'student_id','matric_number',
410              'certificate', 'faculty', 'department', 'passport_path')
[9936]411
412    #: The title under which this exporter will be displayed
413    title = _(u'Combo Card Data')
414
415    def mangle_value(self, value, name, context=None):
416        certificate = context['studycourse'].certificate
417        if name == 'certificate' and certificate is not None:
418            value = certificate.title
419        if name == 'department' and certificate is not None:
420            value = certificate.__parent__.__parent__.longtitle()
421        if name == 'faculty' and certificate is not None:
422            value = certificate.__parent__.__parent__.__parent__.longtitle()
[9937]423        if name == 'passport_path' and certificate is not None:
424            file_id = IFileStoreNameChooser(context).chooseName(attr='passport.jpg')
425            os_path = getUtility(IExtFileStore)._pathFromFileID(file_id)
426            if not os.path.exists(os_path):
427                value = None
428            else:
429                value = '/'.join(os_path.split('/')[-4:])
[9936]430        return super(
431            ComboCardDataExporter, self).mangle_value(
432            value, name, context=context)
Note: See TracBrowser for help on using the repository browser.