source: main/waeup.uniben/trunk/src/waeup/uniben/students/utils.py @ 16374

Last change on this file since 16374 was 16374, checked in by Henrik Bettermann, 4 years ago

Add cl_allexj payment disabled filter.

  • Property svn:keywords set to Id
File size: 28.3 KB
Line 
1## $Id: utils.py 16374 2021-01-14 13:00:40Z 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 reportlab.platypus import Paragraph, Table
21from zope.component import createObject, getUtility
22from reportlab.lib.styles import getSampleStyleSheet
23from waeup.kofa.browser.pdf import ENTRY1_STYLE
24from waeup.kofa.interfaces import (IKofaUtils, ADMITTED, CLEARANCE,
25    CLEARED, RETURNING, PAID, REGISTERED, VALIDATED, GRADUATED)
26from waeup.kofa.utils.helpers import to_timezone
27from waeup.kofa.students.utils import (
28    trans, render_student_data, formatted_text, render_transcript_data,
29    SLIP_STYLE)
30from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
31from waeup.uniben.interfaces import MessageFactory as _
32
33class CustomStudentsUtils(NigeriaStudentsUtils):
34    """A collection of customized methods.
35
36    """
37
38    def getReturningData(self, student):
39        """ This method defines what happens after school fee payment
40        of returning students depending on the student's senate verdict.
41        """
42        prev_level = student['studycourse'].current_level
43        cur_verdict = student['studycourse'].current_verdict
44        if cur_verdict == 'N' and prev_level == 100:
45            new_level = prev_level
46        elif cur_verdict in ('A','B','L','M','N','Z',):
47            # Successful student
48            new_level = divmod(int(prev_level),100)[0]*100 + 100
49        elif cur_verdict == 'C':
50            # Student on probation
51            new_level = int(prev_level) + 10
52        else:
53            # Student is somehow in an undefined state.
54            # Level has to be set manually.
55            new_level = prev_level
56        new_session = student['studycourse'].current_session + 1
57        return new_session, new_level
58
59
60    def checkAccommodationRequirements(self, student, acc_details):
61        if acc_details.get('expired', False):
62            startdate = acc_details.get('startdate')
63            enddate = acc_details.get('enddate')
64            if startdate and enddate:
65                tz = getUtility(IKofaUtils).tzinfo
66                startdate = to_timezone(
67                    startdate, tz).strftime("%d/%m/%Y %H:%M:%S")
68                enddate = to_timezone(
69                    enddate, tz).strftime("%d/%m/%Y %H:%M:%S")
70                return _("Outside booking period: ${a} - ${b}",
71                         mapping = {'a': startdate, 'b': enddate})
72            else:
73                return _("Outside booking period.")
74        if not student.is_postgrad and student.current_mode != 'ug_ft':
75            return _("Only undergraduate full-time students are eligible to book accommodation.")
76        bt = acc_details.get('bt')
77        if not bt:
78            return _("Your data are incomplete.")
79        if not student.state in acc_details['allowed_states']:
80            return _("You are in the wrong registration state.")
81        if student['studycourse'].current_session != acc_details[
82            'booking_session']:
83            return _('Your current session does not '
84                     'match accommodation session.')
85        stage = bt.split('_')[2]
86        if not student.is_postgrad and stage != 'fr' and not student[
87            'studycourse'].previous_verdict in (
88                'A', 'B', 'F', 'J', 'L', 'M', 'C', 'Z'):
89            return _("Your are not eligible to book accommodation.")
90        bsession = str(acc_details['booking_session'])
91        if bsession in student['accommodation'].keys() \
92            and not 'booking expired' in \
93            student['accommodation'][bsession].bed_coordinates:
94            return _('You already booked a bed space in '
95                     'current accommodation session.')
96        return
97
98    def getAccommodationDetails(self, student):
99        """Determine the accommodation data of a student.
100        """
101        d = {}
102        d['error'] = u''
103        hostels = grok.getSite()['hostels']
104        d['booking_session'] = hostels.accommodation_session
105        d['allowed_states'] = hostels.accommodation_states
106        d['startdate'] = hostels.startdate
107        d['enddate'] = hostels.enddate
108        d['expired'] = hostels.expired
109        # Determine bed type
110        studycourse = student['studycourse']
111        certificate = getattr(studycourse,'certificate',None)
112        entry_session = studycourse.entry_session
113        current_level = studycourse.current_level
114        if None in (entry_session, current_level, certificate):
115            return d
116        if student.sex == 'f':
117            sex = 'female'
118        else:
119            sex = 'male'
120        if student.is_postgrad:
121            bt = 'all'
122            special_handling = 'pg'
123        else:
124            end_level = certificate.end_level
125            if current_level == 10:
126                bt = 'pr'
127            elif entry_session == grok.getSite()['hostels'].accommodation_session:
128                bt = 'fr'
129            elif current_level >= end_level:
130                bt = 'fi'
131            else:
132                bt = 're'
133            special_handling = 'regular'
134            desired_hostel = student['accommodation'].desired_hostel
135            if student.faccode in ('MED', 'DEN') and (
136                not desired_hostel or desired_hostel.startswith('clinical')):
137                special_handling = 'clinical'
138            elif student.certcode in ('BARTMAS', 'BARTTHR', 'BARTFAA',
139                                      'BAEDFAA', 'BSCEDECHED', 'BAFAA'):
140                special_handling = 'ekenwan'
141        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
142        return d
143
144    def _paymentMade(self, student, session):
145        if len(student['payments']):
146            for ticket in student['payments'].values():
147                if ticket.p_state == 'paid' and \
148                    ticket.p_category == 'schoolfee' and \
149                    ticket.p_session == session:
150                    return True
151        return False
152
153    def _isPaymentDisabled(self, p_session, category, student):
154        academic_session = self._getSessionConfiguration(p_session)
155        if category == 'schoolfee':
156            if 'sf_all' in academic_session.payment_disabled:
157                return True
158            if student.current_mode == 'found' and \
159                'sf_found' in academic_session.payment_disabled:
160                return True
161            if student.is_postgrad:
162                if 'sf_pg' in academic_session.payment_disabled:
163                    return True
164                return False
165            if student.current_mode.endswith('ft') and \
166                'sf_ft' in academic_session.payment_disabled:
167                return True
168            if student.current_mode.endswith('pt') and \
169                'sf_pt' in academic_session.payment_disabled:
170                return True
171            if student.current_mode.startswith('dp') and \
172                'sf_dp' in academic_session.payment_disabled:
173                return True
174            if student.current_mode.endswith('sw') and \
175                'sf_sw' in academic_session.payment_disabled:
176                return True
177        if category == 'hostel_maintenance' and \
178            'maint_all' in academic_session.payment_disabled:
179            return True
180        if category == 'clearance':
181            if 'cl_all' in academic_session.payment_disabled:
182                return True
183            if student.is_jupeb and \
184                'cl_jupeb' in academic_session.payment_disabled:
185                return True
186            if not student.is_jupeb and \
187                'cl_allexj' in academic_session.payment_disabled:
188                return True
189        return False
190
191    #def _hostelApplicationPaymentMade(self, student, session):
192    #    if len(student['payments']):
193    #        for ticket in student['payments'].values():
194    #            if ticket.p_state == 'paid' and \
195    #                ticket.p_category == 'hostel_application' and \
196    #                ticket.p_session == session:
197    #                return True
198    #    return False
199
200    def _pharmdInstallments(self, student):
201        installments = 0.0
202        if len(student['payments']):
203            for ticket in student['payments'].values():
204                if ticket.p_state == 'paid' and \
205                    ticket.p_category.startswith('pharmd') and \
206                    ticket.p_session == student.current_session:
207                    installments += ticket.amount_auth
208        return installments
209
210    def samePaymentMade(self, student, category, p_item, p_session):
211        if category in ('bed_allocation', 'transcript'):
212            return False
213        for key in student['payments'].keys():
214            ticket = student['payments'][key]
215            if ticket.p_state == 'paid' and\
216               ticket.p_category == category and \
217               ticket.p_item == p_item and \
218               ticket.p_session == p_session:
219                  return True
220        return False
221
222    def setPaymentDetails(self, category, student,
223            previous_session, previous_level, combi):
224        """Create Payment object and set the payment data of a student for
225        the payment category specified.
226
227        """
228        p_item = u''
229        amount = 0.0
230        if previous_session:
231            if previous_session < student['studycourse'].entry_session:
232                return _('The previous session must not fall below '
233                         'your entry session.'), None
234            if category == 'schoolfee':
235                # School fee is always paid for the following session
236                if previous_session > student['studycourse'].current_session:
237                    return _('This is not a previous session.'), None
238            else:
239                if previous_session > student['studycourse'].current_session - 1:
240                    return _('This is not a previous session.'), None
241            p_session = previous_session
242            p_level = previous_level
243            p_current = False
244        else:
245            p_session = student['studycourse'].current_session
246            p_level = student['studycourse'].current_level
247            p_current = True
248        academic_session = self._getSessionConfiguration(p_session)
249        if academic_session == None:
250            return _(u'Session configuration object is not available.'), None
251        # Determine fee.
252        if category == 'transfer':
253            amount = academic_session.transfer_fee
254        elif category == 'transcript':
255            amount = academic_session.transcript_fee
256        elif category == 'gown':
257            amount = academic_session.gown_fee
258        elif category == 'jupeb':
259            amount = academic_session.jupeb_fee
260        elif category == 'clinexam':
261            amount = academic_session.clinexam_fee
262        elif category.startswith('pharmd') \
263            and student.current_mode == 'special_ft':
264            amount = 80000.0
265        #elif category == 'develop' and student.is_postgrad:
266        #    amount = academic_session.development_fee
267        elif category == 'bed_allocation':
268            p_item = self.getAccommodationDetails(student)['bt']
269            desired_hostel = student['accommodation'].desired_hostel
270            if not desired_hostel:
271                return _(u'Select your favoured hostel first.'), None
272            if desired_hostel and desired_hostel != 'no':
273                p_item = u'%s (%s)' % (p_item, desired_hostel)
274            amount = academic_session.booking_fee
275            if student.is_postgrad:
276                amount += 500
277        elif category == 'hostel_maintenance':
278            amount = 0.0
279            bedticket = student['accommodation'].get(
280                str(student.current_session), None)
281            if bedticket is not None and bedticket.bed is not None:
282                p_item = bedticket.bed_coordinates
283                if bedticket.bed.__parent__.maint_fee > 0:
284                    amount = bedticket.bed.__parent__.maint_fee
285                else:
286                    # fallback
287                    amount = academic_session.maint_fee
288            else:
289                return _(u'No bed allocated.'), None
290        #elif category == 'hostel_application':
291        #    amount = 1000.0
292        #elif category.startswith('tempmaint'):
293        #    if not self._hostelApplicationPaymentMade(
294        #        student, student.current_session):
295        #        return _(
296        #            'You have not yet paid the hostel application fee.'), None
297        #    if category == 'tempmaint_1':
298        #        amount = 8150.0
299        #    elif category == 'tempmaint_2':
300        #        amount = 12650.0
301        #    elif category == 'tempmaint_3':
302        #        amount = 9650.0
303        elif category == 'clearance':
304            p_item = student.certcode
305            if p_item is None:
306                return _('Study course data are incomplete.'), None
307            if student.is_jupeb:
308                amount = 50000.0
309            elif student.faccode.startswith('FCETA'):
310                # ASABA and AKOKA
311                amount = 35000.0
312            elif student.faccode in ('BMS', 'MED', 'DEN'):
313            #elif p_item in ('BSCANA', 'BSCMBC', 'BMLS', 'BSCNUR', 'BSCPHS', 'BDS',
314            #    'MBBSMED', 'MBBSNDU', 'BSCPTY', 'BSCPST'):
315                amount = 80000.0
316            elif student.faccode == 'DCOEM':
317                return _('Acceptance fee payment not necessary.'), None
318            else:
319                amount = 60000.0
320        elif category == 'schoolfee':
321            try:
322                certificate = student['studycourse'].certificate
323                p_item = certificate.code
324            except (AttributeError, TypeError):
325                return _('Study course data are incomplete.'), None
326            if previous_session:
327                # Students can pay for previous sessions in all workflow states.
328                # Fresh students are excluded by the update method of the
329                # PreviousPaymentAddFormPage.
330                if previous_session == student['studycourse'].entry_session:
331                    if student.is_foreigner:
332                        amount = getattr(certificate, 'school_fee_3', 0.0)
333                    else:
334                        amount = getattr(certificate, 'school_fee_1', 0.0)
335                else:
336                    if student.is_foreigner:
337                        amount = getattr(certificate, 'school_fee_4', 0.0)
338                    else:
339                        amount = getattr(certificate, 'school_fee_2', 0.0)
340                        # Old returning students might get a discount.
341                        if student.entry_session < 2017 \
342                            and certificate.custom_float_1:
343                            amount -= certificate.custom_float_1
344            else:
345                if student.state == CLEARED:
346                    if student.is_foreigner:
347                        amount = getattr(certificate, 'school_fee_3', 0.0)
348                    else:
349                        amount = getattr(certificate, 'school_fee_1', 0.0)
350                elif student.state == PAID and student.is_postgrad:
351                    p_session += 1
352                    academic_session = self._getSessionConfiguration(p_session)
353                    if academic_session == None:
354                        return _(u'Session configuration object is not available.'), None
355
356                    # Students are only allowed to pay for the next session
357                    # if current session payment
358                    # has really been made, i.e. payment object exists.
359                    #if not self._paymentMade(
360                    #    student, student.current_session):
361                    #    return _('You have not yet paid your current/active' +
362                    #             ' session. Please use the previous session' +
363                    #             ' payment form first.'), None
364
365                    if student.is_foreigner:
366                        amount = getattr(certificate, 'school_fee_4', 0.0)
367                    else:
368                        amount = getattr(certificate, 'school_fee_2', 0.0)
369                elif student.state == RETURNING:
370                    # In case of returning school fee payment the payment session
371                    # and level contain the values of the session the student
372                    # has paid for.
373                    p_session, p_level = self.getReturningData(student)
374                    academic_session = self._getSessionConfiguration(p_session)
375                    if academic_session == None:
376                        return _(u'Session configuration object is not available.'), None
377
378                    # Students are only allowed to pay for the next session
379                    # if current session payment has really been made,
380                    # i.e. payment object exists and is paid.
381                    #if not self._paymentMade(
382                    #    student, student.current_session):
383                    #    return _('You have not yet paid your current/active' +
384                    #             ' session. Please use the previous session' +
385                    #             ' payment form first.'), None
386
387                    if student.is_foreigner:
388                        amount = getattr(certificate, 'school_fee_4', 0.0)
389                    else:
390                        amount = getattr(certificate, 'school_fee_2', 0.0)
391                        # Old returning students might get a discount.
392                        if student.entry_session < 2017 \
393                            and certificate.custom_float_1:
394                            amount -= certificate.custom_float_1
395                # PHARMD school fee amount is fixed and previously paid
396                # installments in current session are deducted.
397                if student.current_mode == 'special_ft' \
398                    and student.state in (RETURNING, CLEARED):
399                    if student.is_foreigner:
400                        amount = 260000.0 - self._pharmdInstallments(student)
401                    else:
402                        amount = 160000.0 - self._pharmdInstallments(student)
403            # Give 50% school fee discount to staff members.
404            if student.is_staff:
405                amount /= 2
406        if amount in (0.0, None):
407            return _('Amount could not be determined.'), None
408        # Add session specific penalty fee.
409        if category == 'schoolfee' and student.is_postgrad:
410            amount += academic_session.penalty_pg
411            amount += academic_session.development_fee
412        elif category == 'schoolfee' and student.current_mode == ('ug_ft'):
413            amount += academic_session.penalty_ug_ft
414        elif category == 'schoolfee' and student.current_mode == ('ug_pt'):
415            amount += academic_session.penalty_ug_pt
416        elif category == 'schoolfee' and student.current_mode == ('ug_sw'):
417            amount += academic_session.penalty_sw
418        elif category == 'schoolfee' and student.current_mode in (
419            'dp_ft', 'dp_pt'):
420            amount += academic_session.penalty_dp
421        if category.startswith('tempmaint'):
422            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
423            p_item = unicode(p_item)
424            # Now we change the category because tempmaint payments
425            # will be obsolete when Uniben returns to Kofa bed allocation.
426            category = 'hostel_maintenance'
427        # Create ticket.
428        if self.samePaymentMade(student, category, p_item, p_session):
429            return _('This type of payment has already been made.'), None
430        if self._isPaymentDisabled(p_session, category, student):
431            return _('This category of payments has been disabled.'), None
432        payment = createObject(u'waeup.StudentOnlinePayment')
433        timestamp = ("%d" % int(time()*10000))[1:]
434        payment.p_id = "p%s" % timestamp
435        payment.p_category = category
436        payment.p_item = p_item
437        payment.p_session = p_session
438        payment.p_level = p_level
439        payment.p_current = p_current
440        payment.amount_auth = amount
441        return None, payment
442
443    def warnCreditsOOR(self, studylevel, course=None):
444        studycourse = studylevel.__parent__
445        certificate = getattr(studycourse,'certificate', None)
446        current_level = studycourse.current_level
447        if None in (current_level, certificate):
448            return
449        end_level = certificate.end_level
450        if studylevel.student.faccode in (
451            'MED', 'DEN', 'BMS') and studylevel.level == 200:
452            limit = 61
453        elif current_level >= end_level:
454            limit = 51
455        else:
456            limit = 50
457        if course and studylevel.total_credits + course.credits > limit:
458            return _('Maximum credits exceeded.')
459        elif studylevel.total_credits > limit:
460            return _('Maximum credits exceeded.')
461        return
462
463    def clearance_disabled_message(self, student):
464        if student.is_postgrad:
465            return None
466        try:
467            session_config = grok.getSite()[
468                'configuration'][str(student.current_session)]
469        except KeyError:
470            return _('Session configuration object is not available.')
471        if not session_config.clearance_enabled:
472            return _('Clearance is disabled for this session.')
473        return None
474
475    def renderPDFTranscript(self, view, filename='transcript.pdf',
476                  student=None,
477                  studentview=None,
478                  note=None,
479                  signatures=(),
480                  sigs_in_footer=(),
481                  digital_sigs=(),
482                  show_scans=True, topMargin=1.5,
483                  omit_fields=(),
484                  tableheader=None,
485                  no_passport=False,
486                  save_file=False):
487        """Render pdf slip of a transcripts.
488        """
489        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
490        # XXX: tell what the different parameters mean
491        style = getSampleStyleSheet()
492        creator = self.getPDFCreator(student)
493        data = []
494        doc_title = view.label
495        author = '%s (%s)' % (view.request.principal.title,
496                              view.request.principal.id)
497        footer_text = view.label.split('\n')
498        if len(footer_text) > 2:
499            # We can add a department in first line
500            footer_text = footer_text[1]
501        else:
502            # Only the first line is used for the footer
503            footer_text = footer_text[0]
504        if getattr(student, 'student_id', None) is not None:
505            footer_text = "%s - %s - " % (student.student_id, footer_text)
506
507        # Insert student data table
508        if student is not None:
509            #bd_translation = trans(_('Base Data'), portal_language)
510            #data.append(Paragraph(bd_translation, HEADING_STYLE))
511            data.append(render_student_data(
512                studentview, view.context,
513                omit_fields, lang=portal_language,
514                slipname=filename,
515                no_passport=no_passport))
516
517        transcript_data = view.context.getTranscriptData()
518        levels_data = transcript_data[0]
519
520        contextdata = []
521        f_label = trans(_('Course of Study:'), portal_language)
522        f_label = Paragraph(f_label, ENTRY1_STYLE)
523        f_text = formatted_text(view.context.certificate.longtitle)
524        f_text = Paragraph(f_text, ENTRY1_STYLE)
525        contextdata.append([f_label,f_text])
526
527        f_label = trans(_('Faculty:'), portal_language)
528        f_label = Paragraph(f_label, ENTRY1_STYLE)
529        f_text = formatted_text(
530            view.context.certificate.__parent__.__parent__.__parent__.longtitle)
531        f_text = Paragraph(f_text, ENTRY1_STYLE)
532        contextdata.append([f_label,f_text])
533
534        f_label = trans(_('Department:'), portal_language)
535        f_label = Paragraph(f_label, ENTRY1_STYLE)
536        f_text = formatted_text(
537            view.context.certificate.__parent__.__parent__.longtitle)
538        f_text = Paragraph(f_text, ENTRY1_STYLE)
539        contextdata.append([f_label,f_text])
540
541        f_label = trans(_('Entry Session:'), portal_language)
542        f_label = Paragraph(f_label, ENTRY1_STYLE)
543        f_text = formatted_text(
544            view.session_dict.get(view.context.entry_session))
545        f_text = Paragraph(f_text, ENTRY1_STYLE)
546        contextdata.append([f_label,f_text])
547
548        f_label = trans(_('Final Session:'), portal_language)
549        f_label = Paragraph(f_label, ENTRY1_STYLE)
550        f_text = formatted_text(
551            view.session_dict.get(view.context.current_session))
552        f_text = Paragraph(f_text, ENTRY1_STYLE)
553        contextdata.append([f_label,f_text])
554
555        f_label = trans(_('Entry Mode:'), portal_language)
556        f_label = Paragraph(f_label, ENTRY1_STYLE)
557        f_text = formatted_text(view.studymode_dict.get(
558            view.context.entry_mode))
559        f_text = Paragraph(f_text, ENTRY1_STYLE)
560        contextdata.append([f_label,f_text])
561
562        f_label = trans(_('Final Verdict:'), portal_language)
563        f_label = Paragraph(f_label, ENTRY1_STYLE)
564        f_text = formatted_text(view.studymode_dict.get(
565            view.context.current_verdict))
566        f_text = Paragraph(f_text, ENTRY1_STYLE)
567        contextdata.append([f_label,f_text])
568
569        f_label = trans(_('Cumulative GPA:'), portal_language)
570        f_label = Paragraph(f_label, ENTRY1_STYLE)
571        format_float = getUtility(IKofaUtils).format_float
572        cgpa = format_float(transcript_data[1], 3)
573        if student.state == GRADUATED:
574            f_text = formatted_text('%s (%s)' % (
575                cgpa, self.getClassFromCGPA(transcript_data[1], student)[1]))
576        else:
577            f_text = formatted_text('%s' % cgpa)
578        f_text = Paragraph(f_text, ENTRY1_STYLE)
579        contextdata.append([f_label,f_text])
580
581        contexttable = Table(contextdata,style=SLIP_STYLE)
582        data.append(contexttable)
583
584        transcripttables = render_transcript_data(
585            view, tableheader, levels_data, lang=portal_language)
586        data.extend(transcripttables)
587
588        # Insert signatures
589        # XXX: We are using only sigs_in_footer in waeup.kofa, so we
590        # do not have a test for the following lines.
591        if signatures and not sigs_in_footer:
592            data.append(Spacer(1, 20))
593            # Render one signature table per signature to
594            # get date and signature in line.
595            for signature in signatures:
596                signaturetables = get_signature_tables(signature)
597                data.append(signaturetables[0])
598
599        # Insert digital signatures
600        if digital_sigs:
601            data.append(Spacer(1, 20))
602            sigs = digital_sigs.split('\n')
603            for sig in sigs:
604                data.append(Paragraph(sig, NOTE_STYLE))
605
606        view.response.setHeader(
607            'Content-Type', 'application/pdf')
608        try:
609            pdf_stream = creator.create_pdf(
610                data, None, doc_title, author=author, footer=footer_text,
611                note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin)
612        except IOError:
613            view.flash(_('Error in image file.'))
614            return view.redirect(view.url(view.context))
615        if save_file:
616            self._saveTranscriptPDF(student, pdf_stream)
617            return
618        return pdf_stream
619
620    #: A tuple containing the names of registration states in which changing of
621    #: passport pictures is allowed.
622    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,)
623
624    # Uniben prefix
625    STUDENT_ID_PREFIX = u'B'
626
627    STUDENT_EXPORTER_NAMES = (
628            'students',
629            'studentstudycourses',
630            'studentstudylevels',
631            'coursetickets',
632            'studentpayments',
633            'bedtickets',
634            'trimmed',
635            'outstandingcourses',
636            'unpaidpayments',
637            'sfpaymentsoverview',
638            'sessionpaymentsoverview',
639            'studylevelsoverview',
640            'combocard',
641            'bursary',
642            'accommodationpayments',
643            'transcriptdata',
644            'trimmedpayments',
645            )
Note: See TracBrowser for help on using the repository browser.