source: main/waeup.aaue/trunk/src/waeup/aaue/students/utils.py @ 16671

Last change on this file since 16671 was 16670, checked in by Henrik Bettermann, 3 years ago

Disable returning student states.

  • Property svn:keywords set to Id
File size: 27.4 KB
Line 
1## $Id: utils.py 16670 2021-10-07 07:42:31Z 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##
18import grok
19from time import time
20from zope.component import createObject, queryUtility
21from zope.catalog.interfaces import ICatalog
22from waeup.kofa.interfaces import (
23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED, academic_sessions_vocab)
25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
26from waeup.kofa.accesscodes import create_accesscode
27from waeup.kofa.students.utils import trans
28from waeup.aaue.interswitch.browser import gateway_net_amt, GATEWAY_AMT
29from waeup.aaue.interfaces import MessageFactory as _
30
31MINIMUM_UNITS_THRESHOLD = 15
32
33class CustomStudentsUtils(NigeriaStudentsUtils):
34    """A collection of customized methods.
35
36    """
37
38    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE, REQUESTED, CLEARED)
39
40    def GPABoundaries(self, faccode=None, depcode=None,
41                            certcode=None, student=None):
42        if student and student.current_mode.startswith('dp'):
43            return ((1.5, 'IRNS / NER / NYV'),
44                    (2.4, 'Pass'),
45                    (3.5, 'Merit'),
46                    (4.5, 'Credit'),
47                    (5, 'Distinction'))
48        elif student:
49            return ((1, 'FRNS / NER / NYV'),
50                    (1.5, 'Pass'),
51                    (2.4, '3rd Class Honours'),
52                    (3.5, '2nd Class Honours Lower Division'),
53                    (4.5, '2nd Class Honours Upper Division'),
54                    (5, '1st Class Honours'))
55        # Session Results Presentations depend on certificate
56        results = None
57        if certcode:
58            cat = queryUtility(ICatalog, name='certificates_catalog')
59            results = list(
60                cat.searchResults(code=(certcode, certcode)))
61        if results and results[0].study_mode.startswith('dp'):
62            return ((1.5, 'IRNS / NER / NYV'),
63                    (2.4, 'Pass'),
64                    (3.5, 'Merit'),
65                    (4.5, 'Credit'),
66                    (5, 'Distinction'))
67        else:
68            return ((1, 'FRNS / NER / NYV'),
69                    (1.5, 'Pass'),
70                    (2.4, '3rd Class Honours'),
71                    (3.5, '2nd Class Honours Lower Division'),
72                    (4.5, '2nd Class Honours Upper Division'),
73                    (5, '1st Class Honours'))
74
75    def getClassFromCGPA(self, gpa, student):
76        gpa_boundaries = self.GPABoundaries(student=student)
77
78        try:
79            certificate = getattr(student['studycourse'],'certificate',None)
80            end_level = getattr(certificate, 'end_level', None)
81            final_level = max([studylevel.level for studylevel in student['studycourse'].values()])
82            if end_level and final_level >= end_level:
83                dummy, repeat = divmod(final_level, 100)
84                if gpa <= 5.1 and repeat == 20:
85                    # Irrespective of the CGPA of a student, if the He/She has
86                    # 3rd Extension, such student will be graduated with a "Pass".
87                    return 1, gpa_boundaries[1][1]
88        except ValueError:
89            pass
90
91        if gpa < gpa_boundaries[0][0]:
92            # FRNS / Fail
93            return 0, gpa_boundaries[0][1]
94        if student.entry_session < 2013 or \
95            student.current_mode.startswith('dp'):
96            if gpa < gpa_boundaries[1][0]:
97                # Pass
98                return 1, gpa_boundaries[1][1]
99        else:
100            if gpa < gpa_boundaries[1][0]:
101                # FRNS
102                # Pass degree has been phased out in 2013 for non-diploma
103                # students
104                return 0, gpa_boundaries[0][1]
105        if gpa < gpa_boundaries[2][0]:
106            # 3rd / Merit
107            return 2, gpa_boundaries[2][1]
108        if gpa < gpa_boundaries[3][0]:
109            # 2nd L / Credit
110            return 3, gpa_boundaries[3][1]
111        if gpa < gpa_boundaries[4][0]:
112            # 2nd U / Distinction
113            return 4, gpa_boundaries[4][1]
114        if gpa <= gpa_boundaries[5][0]:
115            # 1st
116            return 5, gpa_boundaries[5][1]
117        return
118
119    def getDegreeClassNumber(self, level_obj):
120        """Get degree class number (used for SessionResultsPresentation
121        reports).
122        """
123        certificate = getattr(level_obj.__parent__,'certificate', None)
124        end_level = getattr(certificate, 'end_level', None)
125        if level_obj.gpa_params[1] < MINIMUM_UNITS_THRESHOLD:
126            # credits taken below limit
127            return 0
128        if end_level and level_obj.level >= end_level:
129            failed_courses = level_obj.passed_params[4]
130            not_taken_courses = level_obj.passed_params[5]
131            if '_m' in failed_courses:
132                return 0
133            if len(not_taken_courses) \
134                and not not_taken_courses == 'Nil':
135                return 0
136        if level_obj.level_verdict in ('FRNS', 'NER', 'NYV'):
137            return 0
138        # use gpa_boundaries above
139        return self.getClassFromCGPA(
140            level_obj.cumulative_params[0], level_obj.student)[0]
141
142    def increaseMatricInteger(self, student):
143        """Increase counter for matric numbers.
144        This counter can be a centrally stored attribute or an attribute of
145        faculties, departments or certificates. In the base package the counter
146        is as an attribute of the site configuration container.
147        """
148        if student.current_mode in ('ug_pt', 'de_pt', 'ug_dsh', 'de_dsh'):
149            grok.getSite()['configuration'].next_matric_integer += 1
150            return
151        elif student.is_postgrad:
152            grok.getSite()['configuration'].next_matric_integer_3 += 1
153            return
154        elif student.current_mode in ('dp_ft',):
155            grok.getSite()['configuration'].next_matric_integer_4 += 1
156            return
157        grok.getSite()['configuration'].next_matric_integer_2 += 1
158        return
159
160    def _concessionalPaymentMade(self, student):
161        if len(student['payments']):
162            for ticket in student['payments'].values():
163                if ticket.p_state == 'paid' and \
164                    ticket.p_category == 'concessional':
165                    return True
166        return False
167
168    def constructMatricNumber(self, student):
169        faccode = student.faccode
170        depcode = student.depcode
171        certcode = student.certcode
172        degree = getattr(
173            getattr(student.get('studycourse', None), 'certificate', None),
174                'degree', None)
175        year = unicode(student.entry_session)[2:]
176        if not student.state in (PAID, ) or not student.is_fresh or \
177            student.current_mode in ('found', 'ijmbe'):
178            return _('Matriculation number cannot be set.'), None
179        #if student.current_mode not in ('mug_ft', 'mde_ft') and \
180        #    not self._concessionalPaymentMade(student):
181        #    return _('Matriculation number cannot be set.'), None
182        if student.is_postgrad:
183            next_integer = grok.getSite()['configuration'].next_matric_integer_3
184            if not degree or next_integer == 0:
185                return _('Matriculation number cannot be set.'), None
186            if student.faccode in ('IOE'):
187                return None, "AAU/SPS/%s/%s/%s/%05d" % (
188                    faccode, year, degree, next_integer)
189            return None, "AAU/SPS/%s/%s/%s/%s/%05d" % (
190                faccode, depcode, year, degree, next_integer)
191        if student.current_mode in ('ug_dsh', 'de_dsh'):
192            next_integer = grok.getSite()['configuration'].next_matric_integer
193            if next_integer == 0:
194                return _('Matriculation number cannot be set.'), None
195            return None, "DSH/%s/%s/%s/%05d" % (
196                faccode, depcode, year, next_integer)
197        if student.current_mode in ('ug_pt', 'de_pt'):
198            next_integer = grok.getSite()['configuration'].next_matric_integer
199            if next_integer == 0:
200                return _('Matriculation number cannot be set.'), None
201            return None, "PTP/%s/%s/%s/%05d" % (
202                faccode, depcode, year, next_integer)
203        if student.current_mode in ('dp_ft',):
204            next_integer = grok.getSite()['configuration'].next_matric_integer_4
205            if next_integer == 0:
206                return _('Matriculation number cannot be set.'), None
207            return None, "IOE/DIP/%s/%05d" % (year, next_integer)
208        next_integer = grok.getSite()['configuration'].next_matric_integer_2
209        if next_integer == 0:
210            return _('Matriculation number cannot be set.'), None
211        if student.faccode in ('FBM', 'FCS'):
212            return None, "CMS/%s/%s/%s/%05d" % (
213                faccode, depcode, year, next_integer)
214        return None, "%s/%s/%s/%05d" % (faccode, depcode, year, next_integer)
215
216    def getReturningData(self, student):
217        """ This method defines what happens after school fee payment
218        of returning students depending on the student's senate verdict.
219        """
220        prev_level = student['studycourse'].current_level
221        cur_verdict = student['studycourse'].current_verdict
222        if cur_verdict in ('A','B','L','M','N','Z',):
223            # Successful student
224            new_level = divmod(int(prev_level),100)[0]*100 + 100
225        elif cur_verdict == 'C':
226            # Student on probation
227            new_level = int(prev_level) + 10
228        else:
229            # Student is somehow in an undefined state.
230            # Level has to be set manually.
231            new_level = prev_level
232        new_session = student['studycourse'].current_session + 1
233        return new_session, new_level
234
235    def _isPaymentDisabled(self, p_session, category, student):
236        academic_session = self._getSessionConfiguration(p_session)
237        if category.startswith('schoolfee'):
238            if 'sf_all' in academic_session.payment_disabled:
239                return True
240            if 'sf_pg' in academic_session.payment_disabled and \
241                student.is_postgrad:
242                return True
243            if 'sf_ug_pt' in academic_session.payment_disabled and \
244                student.current_mode in ('ug_pt', 'de_pt'):
245                return True
246            if 'sf_found' in academic_session.payment_disabled and \
247                student.current_mode == 'found':
248                return True
249        if category.startswith('clearance') and \
250            'cl_regular' in academic_session.payment_disabled and \
251            student.current_mode in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
252            return True
253        if category == 'hostel_maintenance' and \
254            'maint_all' in academic_session.payment_disabled:
255            return True
256        return False
257
258    def setPaymentDetails(self, category, student,
259            previous_session=None, previous_level=None, combi=[]):
260        """Create Payment object and set the payment data of a student for
261        the payment category specified.
262
263        """
264        details = {}
265        p_item = u''
266        amount = 0.0
267        error = u''
268        if previous_session:
269            if previous_session < student['studycourse'].entry_session:
270                return _('The previous session must not fall below '
271                         'your entry session.'), None
272            if category == 'schoolfee':
273                # School fee is always paid for the following session
274                if previous_session > student['studycourse'].current_session:
275                    return _('This is not a previous session.'), None
276            else:
277                if previous_session > student['studycourse'].current_session - 1:
278                    return _('This is not a previous session.'), None
279            p_session = previous_session
280            p_level = previous_level
281            p_current = False
282        else:
283            p_session = student['studycourse'].current_session
284            p_level = student['studycourse'].current_level
285            p_current = True
286        academic_session = self._getSessionConfiguration(p_session)
287        if academic_session == None:
288            return _(u'Session configuration object is not available.'), None
289        # Determine fee.
290        if category == 'transfer':
291            amount = academic_session.transfer_fee
292        elif category == 'transcript_local':
293            amount = academic_session.transcript_fee_local
294        elif category == 'transcript_inter':
295            amount = academic_session.transcript_fee_inter
296        elif category == 'bed_allocation':
297            amount = academic_session.booking_fee
298        elif category == 'restitution':
299            if student.current_session != 2016 \
300                or student.current_mode not in ('ug_ft', 'dp_ft') \
301                or student.is_fresh:
302                return _(u'Restitution fee payment not required.'), None
303            amount = academic_session.restitution_fee
304        elif category == 'hostel_maintenance':
305            amount = 0.0
306            bedticket = student['accommodation'].get(
307                str(student.current_session), None)
308            if bedticket is not None and bedticket.bed is not None:
309                p_item = bedticket.display_coordinates
310                if bedticket.bed.__parent__.maint_fee > 0:
311                    amount = bedticket.bed.__parent__.maint_fee
312                else:
313                    # fallback
314                    amount = academic_session.maint_fee
315            else:
316                return _(u'No bed allocated.'), None
317        elif student.current_mode == 'found' and category not in (
318            'schoolfee', 'clearance', 'late_registration'):
319            return _('Not allowed.'), None
320        elif category.startswith('clearance'):
321            if student.state not in (ADMITTED, CLEARANCE, REQUESTED, CLEARED):
322                return _(u'Acceptance Fee payments not allowed.'), None
323            if student.current_mode in (
324                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
325                'transfer', 'mug_ft', 'mde_ft') \
326                and category != 'clearance_incl':
327                    return _("Additional fees must be included."), None
328            if student.current_mode == 'ijmbe':
329                amount = academic_session.clearance_fee_ijmbe
330            elif student.current_mode == 'bridge':
331                amount = academic_session.clearance_fee_bridge
332            elif student.current_mode == 'dp_ft':
333                amount = academic_session.clearance_fee_dp
334            elif student.faccode == 'FP':
335                amount = academic_session.clearance_fee_fp
336            elif student.current_mode in ('ug_pt', 'de_pt', 'ug_dsh', 'de_dsh'):
337                amount = academic_session.clearance_fee_ug_pt
338            elif student.current_mode == 'special_pg_pt':
339                amount = academic_session.clearance_fee_pg_pt
340            elif student.faccode == 'FCS':
341                # Students in clinical medical sciences pay the medical
342                # acceptance fee
343                amount = academic_session.clearance_fee_med
344            elif student.current_mode == 'special_pg_ft':
345                if category != 'clearance':
346                    return _("No additional fees required."), None
347                amount = academic_session.clearance_fee_pg
348            else:
349                amount = academic_session.clearance_fee
350            p_item = student['studycourse'].certificate.code
351            if amount in (0.0, None):
352                return _(u'Amount could not be determined.'), None
353            # Add Matric Gown Fee and Lapel Fee
354            if category == 'clearance_incl':
355                amount += gateway_net_amt(academic_session.matric_gown_fee) + \
356                    gateway_net_amt(academic_session.lapel_fee)
357        elif category == 'late_registration':
358            if student.is_postgrad:
359                amount = academic_session.late_pg_registration_fee
360            else:
361                amount = academic_session.late_registration_fee
362        elif category == 'ict':
363            if student.is_fresh:
364                amount = 2200
365            else:
366                amount = 1200
367        elif category.startswith('schoolfee'):
368            try:
369                certificate = student['studycourse'].certificate
370                p_item = certificate.code
371            except (AttributeError, TypeError):
372                return _('Study course data are incomplete.'), None
373            if student.is_postgrad and category != 'schoolfee':
374                return _("No additional fees required."), None
375            if not previous_session and student.current_mode in (
376                'ug_ft', 'ug_pt', 'de_ft', 'de_pt',
377                'transfer', 'mug_ft', 'mde_ft') \
378                and not category in (
379                'schoolfee_incl', 'schoolfee_1', 'schoolfee_2'):
380                    return _("You must choose a payment which includes "
381                             "additional fees."), None
382            if category in ('schoolfee_1', 'schoolfee_2'):
383                if student.current_mode == 'ug_pt':
384                    return _("Part-time students are not allowed "
385                             "to pay by instalments."), None
386                if student.entry_session < 2015:
387                    return _("You are not allowed "
388                             "to pay by instalments."), None
389            if previous_session:
390                # Students can pay for previous sessions in all
391                # workflow states.  Fresh students are excluded by the
392                # update method of the PreviousPaymentAddFormPage.
393                if previous_level == 100:
394                    amount = getattr(certificate, 'school_fee_1', 0.0)
395                else:
396                    if student.entry_session in (2015, 2016):
397                        amount = getattr(certificate, 'school_fee_2', 0.0)
398                    else:
399                        amount = getattr(certificate, 'school_fee_3', 0.0)
400            elif student.state == CLEARED and category != 'schoolfee_2':
401                amount = getattr(certificate, 'school_fee_1', 0.0)
402                # Cut school fee by 50%
403                if category == 'schoolfee_1' and amount:
404                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
405            elif student.is_fresh and category == 'schoolfee_2':
406                amount = getattr(certificate, 'school_fee_1', 0.0)
407                # Cut school fee by 50%
408                if amount:
409                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
410            elif student.state == RETURNING and category != 'schoolfee_2':
411                if not student.father_name:
412                    return _("Personal data form is not properly filled."), None
413                # In case of returning school fee payment the payment session
414                # and level contain the values of the session the student
415                # has paid for.
416                p_session, p_level = self.getReturningData(student)
417                try:
418                    academic_session = grok.getSite()[
419                        'configuration'][str(p_session)]
420                except KeyError:
421                    return _(u'Session configuration object is not available.'), None
422                if student.entry_session >= 2015:
423                    amount = getattr(certificate, 'school_fee_2', 0.0)
424                else:
425                    amount = getattr(certificate, 'school_fee_3', 0.0)
426                # Cut school fee by 50%
427                if category == 'schoolfee_1' and amount:
428                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
429            elif category == 'schoolfee_2':
430                amount = getattr(certificate, 'school_fee_2', 0.0)
431                # Cut school fee by 50%
432                if amount:
433                    amount = gateway_net_amt(amount) / 2 + GATEWAY_AMT
434            else:
435                return _('Wrong state.'), None
436            if amount in (0.0, None):
437                return _(u'Amount could not be determined.'), None
438            # Add Student Union Fee, Student Id Card Fee, Sports Dev. Fee,
439            # Library Dev. Fee and Welfare Assurance
440            if category in ('schoolfee_incl', 'schoolfee_1') \
441                and student.current_mode != 'ijmbe':
442                amount += gateway_net_amt(academic_session.welfare_fee) + \
443                    gateway_net_amt(academic_session.union_fee)
444                if student.entry_session >= 2018 and student.is_fresh:
445                    amount += gateway_net_amt(academic_session.sports_fee)
446                    if student.is_postgrad:
447                        amount += gateway_net_amt(academic_session.library_pg_fee)
448                    else:
449                        amount += gateway_net_amt(academic_session.library_fee)
450            # Add non-indigenous fee and session specific penalty fees
451            if student.is_postgrad:
452                amount += academic_session.penalty_pg
453                if student.lga and not student.lga.startswith('edo'):
454                    amount += 20000.0
455            else:
456                amount += academic_session.penalty_ug
457        #elif category == ('fac_dep'):
458        #    if student.faccode in ('FAT', 'FED', 'FLW', 'FMS', 'FSS'):
459        #        amount = 4200.0
460        #    elif student.faccode in ('FAG', 'FBM', 'FCS',
461        #                             'FES', 'FET', 'FLS', 'FPS'):
462        #        amount = 5200.0
463        #    elif student.faccode == 'FAG' and student.current_level == 400:
464        #        amount = 10200.0
465        #    elif student.depcode == 'VTE':
466        #        amount = 5200.0
467        #    if student.entry_session >= 2019:
468        #        amount += gateway_net_amt(academic_session.ict_fee)
469        elif category == 'sports_library': # temporarily in 2020
470            if student.is_postgrad:
471                amount = academic_session.sports_fee + gateway_net_amt(
472                    academic_session.library_pg_fee)
473            else:
474                amount = academic_session.sports_fee + gateway_net_amt(
475                    academic_session.library_fee)
476        elif not student.is_postgrad:
477            fee_name = category + '_fee'
478            amount = getattr(academic_session, fee_name, 0.0)
479        if amount in (0.0, None):
480            return _(u'Amount could not be determined.'), None
481        # Create ticket.
482        for key in student['payments'].keys():
483            ticket = student['payments'][key]
484            if ticket.p_state == 'paid' and\
485               ticket.p_category == category and \
486               not ticket.p_category.startswith('transcript') and \
487               ticket.p_item == p_item and \
488               ticket.p_session == p_session:
489                  return _('This type of payment has already been made.'), None
490            # Additional condition in AAUE
491            if category in ('schoolfee', 'schoolfee_incl', 'schoolfee_1'):
492                if ticket.p_state == 'paid' and \
493                   ticket.p_category in ('schoolfee',
494                                         'schoolfee_incl',
495                                         'schoolfee_1') and \
496                   ticket.p_item == p_item and \
497                   ticket.p_session == p_session:
498                      return _(
499                          'Another school fee payment for this '
500                          'session has already been made.'), None
501
502        if self._isPaymentDisabled(p_session, category, student):
503            return _('This category of payments has been disabled.'), None
504        payment = createObject(u'waeup.StudentOnlinePayment')
505        timestamp = ("%d" % int(time()*10000))[1:]
506        payment.p_id = "p%s" % timestamp
507        payment.p_category = category
508        payment.p_item = p_item
509        payment.p_session = p_session
510        payment.p_level = p_level
511        payment.p_current = p_current
512        payment.amount_auth = amount
513        return None, payment
514
515    def _admissionText(self, student, portal_language):
516        inst_name = grok.getSite()['configuration'].name
517        entry_session = student['studycourse'].entry_session
518        entry_session = academic_sessions_vocab.getTerm(entry_session).title
519        text = trans(_(
520            'This is to inform you that you have been offered provisional'
521            ' admission into ${a} for the ${b} academic session as follows:',
522            mapping = {'a': inst_name, 'b': entry_session}),
523            portal_language)
524        return text
525
526    def warnCreditsOOR(self, studylevel, course=None):
527        studycourse = studylevel.__parent__
528        certificate = getattr(studycourse,'certificate', None)
529        current_level = studycourse.current_level
530        if None in (current_level, certificate):
531            return
532        end_level = certificate.end_level
533        if current_level >= end_level:
534            limit = 52
535        else:
536            limit = 48
537        if course and studylevel.total_credits + course.credits > limit:
538            return  _('Maximum credits exceeded.')
539        elif studylevel.total_credits > limit:
540            return _('Maximum credits exceeded.')
541        return
542
543    def getBedCoordinates(self, bedticket):
544        """Return descriptive bed coordinates.
545        This method can be used to customize the `display_coordinates`
546        property method in order to  display a
547        customary description of the bed space.
548        """
549        bc = bedticket.bed_coordinates.split(',')
550        if len(bc) == 4:
551            return bc[0]
552        return bedticket.bed_coordinates
553
554    def getAccommodationDetails(self, student):
555        """Determine the accommodation data of a student.
556        """
557        d = {}
558        d['error'] = u''
559        hostels = grok.getSite()['hostels']
560        d['booking_session'] = hostels.accommodation_session
561        d['allowed_states'] = hostels.accommodation_states
562        d['startdate'] = hostels.startdate
563        d['enddate'] = hostels.enddate
564        d['expired'] = hostels.expired
565        # Determine bed type
566        bt = 'all'
567        if student.sex == 'f':
568            sex = 'female'
569        else:
570            sex = 'male'
571        special_handling = 'regular'
572        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
573        return d
574
575    def checkAccommodationRequirements(self, student, acc_details):
576        msg = super(CustomStudentsUtils, self).checkAccommodationRequirements(
577            student, acc_details)
578        if msg:
579            return msg
580        if student.current_mode not in ('ug_ft', 'de_ft', 'mug_ft', 'mde_ft'):
581            return _('You are not eligible to book accommodation.')
582        return
583
584    # AAUE prefix
585    STUDENT_ID_PREFIX = u'E'
586
587    STUDENT_EXPORTER_NAMES = (
588            'students',
589            'studentstudycourses',
590            'studentstudylevels',
591            'coursetickets',
592            'studentpayments',
593            'bedtickets',
594            'unpaidpayments',
595            'sfpaymentsoverview',
596            'studylevelsoverview',
597            'combocard',
598            'bursary',
599            'levelreportdata',
600            'outstandingcourses',
601            'sessionpaymentsoverview',
602            'accommodationpayments',
603            'transcriptdata',
604            'trimmedpayments',
605            'trimmed',
606            )
607
608    # Maximum size of upload files in kB
609    MAX_KB = 500
Note: See TracBrowser for help on using the repository browser.