source: main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py @ 14406

Last change on this file since 14406 was 14385, checked in by Henrik Bettermann, 8 years ago

Do not print fee amounts on payment slips.

  • Property svn:keywords set to Id
File size: 36.2 KB
Line 
1## $Id: browser.py 14385 2017-01-10 21:31:22Z henrik $
2##
3## Copyright (C) 2012 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
19import csv
20import textwrap
21from cStringIO import StringIO
22from zope.i18n import translate
23from zope.component import getUtility, queryUtility
24from zope.schema.interfaces import TooBig, TooSmall
25from zope.security import checkPermission
26from zope.catalog.interfaces import ICatalog
27from zope.formlib.textwidgets import BytesDisplayWidget
28from waeup.kofa.browser.layout import UtilityView
29from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
30from waeup.kofa.interfaces import (
31    IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject)
32from waeup.kofa.students.interfaces import (
33    IStudentsUtils, IStudent, IStudentRequestPW)
34from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
35from waeup.kofa.students.studylevel import getGradeWeightFromScore
36from waeup.kofa.students.browser import (
37    StartClearancePage,
38    StudentBasePDFFormPage,
39    CourseTicketAddFormPage,
40    StudyLevelDisplayFormPage,
41    StudyLevelManageFormPage,
42    StudyLevelEditFormPage,
43    ExportPDFTranscriptSlip,
44    ExportPDFAdmissionSlip,
45    BedTicketAddPage,
46    StudentFilesUploadPage,
47    PaymentsManageFormPage,
48    CourseTicketDisplayFormPage,
49    CourseTicketManageFormPage,
50    EditScoresPage,
51    ExportPDFScoresSlip,
52    StudyCourseTranscriptPage,
53    DownloadScoresView,
54    StudentRequestPasswordPage
55    )
56from kofacustom.nigeria.students.browser import (
57    NigeriaOnlinePaymentDisplayFormPage,
58    NigeriaOnlinePaymentAddFormPage,
59    NigeriaExportPDFPaymentSlip,
60    NigeriaExportPDFCourseRegistrationSlip,
61    NigeriaStudentPersonalDisplayFormPage,
62    NigeriaStudentPersonalEditFormPage,
63    NigeriaStudentPersonalManageFormPage,
64    NigeriaStudentClearanceDisplayFormPage,
65    NigeriaExportPDFClearanceSlip,
66    NigeriaStudentClearanceManageFormPage,
67    NigeriaStudentClearanceEditFormPage,
68    NigeriaAccommodationManageFormPage,
69    NigeriaStudentBaseDisplayFormPage,
70    NigeriaStudentBaseManageFormPage
71    )
72from waeup.aaue.students.interfaces import (
73    ICustomStudentOnlinePayment,
74    ICustomStudentStudyLevel,
75    ICustomStudent,
76    ICustomStudentPersonal,
77    ICustomStudentPersonalEdit,
78    ICustomUGStudentClearance,
79    ICustomUGStudentClearanceEdit,
80    ICustomPGStudentClearance,
81    ICustomCourseTicket,
82    ICustomStudentBase)
83from waeup.aaue.interswitch.browser import gateway_net_amt
84from waeup.aaue.interfaces import MessageFactory as _
85
86grok.context(IKofaObject)  # Make IKofaObject the default context
87
88class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
89    """ Page to display student base data
90    """
91    form_fields = grok.AutoFields(ICustomStudentBase).omit(
92        'password', 'suspended', 'suspended_comment', 'flash_notice')
93    form_fields[
94        'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
95
96class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
97    """ View to manage student base data
98    """
99    form_fields = grok.AutoFields(ICustomStudentBase).omit(
100        'student_id', 'adm_code', 'suspended',
101        'financially_cleared_by', 'financial_clearance_date')
102
103class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
104    """ Page to display student personal data
105    """
106    form_fields = grok.AutoFields(ICustomStudentPersonal)
107    form_fields['perm_address'].custom_widget = BytesDisplayWidget
108    form_fields['father_address'].custom_widget = BytesDisplayWidget
109    form_fields['mother_address'].custom_widget = BytesDisplayWidget
110    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
111    form_fields[
112        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
113
114class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
115    """ Page to edit personal data
116    """
117    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated')
118
119class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
120    """ Page to edit personal data
121    """
122    form_fields = grok.AutoFields(ICustomStudentPersonal)
123    form_fields['personal_updated'].for_display = True
124    form_fields[
125        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
126
127class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage):
128    """ Page to display student clearance data
129    """
130
131    @property
132    def form_fields(self):
133        if self.context.is_postgrad:
134            form_fields = grok.AutoFields(
135                ICustomPGStudentClearance).omit('clearance_locked')
136        else:
137            form_fields = grok.AutoFields(
138                ICustomUGStudentClearance).omit('clearance_locked')
139        if not getattr(self.context, 'officer_comment'):
140            form_fields = form_fields.omit('officer_comment')
141        else:
142            form_fields['officer_comment'].custom_widget = BytesDisplayWidget
143        form_fields = form_fields.omit('def_adm')
144        return form_fields
145
146class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage):
147    """ Page to edit student clearance data
148    """
149
150    @property
151    def form_fields(self):
152        if self.context.is_postgrad:
153            form_fields = grok.AutoFields(
154                ICustomPGStudentClearance).omit('clr_code')
155        else:
156            form_fields = grok.AutoFields(
157                ICustomUGStudentClearance).omit('clr_code')
158        form_fields = form_fields.omit('def_adm')
159        return form_fields
160
161class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
162    """ View to edit student clearance data by student
163    """
164
165    @property
166    def form_fields(self):
167        if self.context.is_postgrad:
168            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
169            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
170            'physical_clearance_date')
171        else:
172            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
173            'clearance_locked', 'clr_code', 'officer_comment',
174            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
175        form_fields = form_fields.omit('def_adm')
176        return form_fields
177
178class CustomStartClearancePage(StartClearancePage):
179    with_ac = False
180
181    @property
182    def all_required_fields_filled(self):
183        if not self.context.email:
184            return _("Email address is missing."), 'edit_base'
185        if not self.context.phone:
186            return _("Phone number is missing."), 'edit_base'
187        if not self.context.father_name:
188            return _("Personal data form is not properly filled."), 'edit_personal'
189        return
190
191class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
192    """ Page to view an online payment ticket
193    """
194    grok.context(ICustomStudentOnlinePayment)
195    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
196        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
197    form_fields[
198        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
199    form_fields[
200        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
201
202class CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
203    """ Page to add an online payment ticket
204    """
205    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
206        'p_category')
207
208    alumni_payment_cats =  {
209        'transcript_local': 'Transcript Fee Local',
210        'transcript_inter': 'Transcript Fee International',
211        }
212
213    @property
214    def selectable_categories(self):
215        if 'alumni' in self.application_url():
216            return self.alumni_payment_cats.items()
217        categories = getUtility(IKofaUtils).SELECTABLE_PAYMENT_CATEGORIES
218        return sorted(categories.items())
219
220class CustomPaymentsManageFormPage(PaymentsManageFormPage):
221    """ Page to manage the student payments.
222
223    This manage form page is for both students and students officers.
224    """
225    @property
226    def manage_payments_allowed(self):
227        return checkPermission('waeup.manageStudent', self.context)
228
229class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
230    """Deliver a PDF slip of the context.
231    """
232    grok.context(ICustomStudentOnlinePayment)
233    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
234        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
235    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
236    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
237
238    @property
239    def note(self):
240        p_session = self.context.p_session
241        try:
242            academic_session = grok.getSite()['configuration'][str(p_session)]
243        except KeyError:
244            academic_session = None
245        text =  '\n\n The Amount Authorized is inclusive of: '
246        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
247            and academic_session:
248            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
249            #union_fee = gateway_net_amt(academic_session.union_fee)
250            if self.context.student.entry_session == 2016 \
251                and self.context.student.entry_mode == 'ug_ft' \
252                and self.context.p_session == 2016:
253                # Add student id card fee to first school fee payment.
254
255                ## Attention: The payment slip does not contain any information
256                ## whether the fee was added or not.
257                ## We can only draw conclusions from from the student's entry
258                ## session whether the fee had been included.
259                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
260                #text += ('School Fee, '
261                #         '%s Naira Student ID Card Fee, '
262                #         '%s Naira Student Union Dues, '
263                #         '%s Naira Student Welfare Assurance Fee and '
264                #         % (id_card_fee, union_fee, welfare_fee))
265
266                text += ('School Fee, '
267                         'Student ID Card Fee, '
268                         'Student Union Dues, '
269                         'Student Welfare Assurance Fee and ')
270            else:
271
272                #text += ('School Fee, '
273                #         '%s Naira Student Union Dues, '
274                #         '%s Naira Student Welfare Assurance Fee and '
275                #         % (union_fee, welfare_fee))
276
277                text += ('School Fee, '
278                         'Student Union Dues, '
279                         'Student Welfare Assurance Fee and ')
280        elif self.context.p_category in (
281            'clearance_incl', 'clearance_medical_incl') and academic_session:
282
283            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
284            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
285            #text += ('Acceptance Fee, '
286            #         '%s Naira Matriculation Gown Fee, '
287            #         '%s Naira Lapel/File Fee and '
288            #         % (matric_gown_fee, lapel_fee))
289
290            text += ('Acceptance Fee, '
291                     'Matriculation Gown Fee, '
292                     'Lapel/File Fee and ')
293
294        #return text + '250.0 Naira Transaction Charge.'
295
296        return text + 'Transaction Charge.'
297
298class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
299    """ Page to display student study levels
300    """
301    grok.context(ICustomStudentStudyLevel)
302    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
303        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
304    form_fields[
305        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
306
307    @property
308    def show_results(self):
309        isStudent = getattr(
310            self.request.principal, 'user_type', None) == 'student'
311        # Temporarily disabled on 1/12/2016
312        if isStudent:
313            return False
314        #if isStudent and self.context.student.state != RETURNING \
315        #    and self.context.student.current_level == self.context.level:
316        #    return False
317        return True
318
319class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
320    """ Page to edit the student study level data
321    """
322    grok.context(ICustomStudentStudyLevel)
323
324class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
325    """ Page to edit the student study level data by students
326    """
327    grok.context(ICustomStudentStudyLevel)
328
329class CustomExportPDFCourseRegistrationSlip(
330    NigeriaExportPDFCourseRegistrationSlip):
331    """Deliver a PDF slip of the context.
332    """
333    grok.context(ICustomStudentStudyLevel)
334    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
335        'level_session', 'level_verdict',
336        'validated_by', 'validation_date', 'gpa', 'level',
337        'imported_gpa', 'imported_cgpa')
338
339    omit_fields = ('password', 'suspended', 'suspended_comment',
340        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
341        'department', 'current_mode', 'current_level', 'flash_notice')
342
343    @property
344    def show_results(self):
345        isStudent = getattr(
346            self.request.principal, 'user_type', None) == 'student'
347        # Temporarily disabled on 1/12/2016
348        if isStudent:
349            return False
350        #if isStudent and self.context.student.state != RETURNING \
351        #    and self.context.student.current_level == self.context.level:
352        #    return False
353        return True
354
355    def update(self):
356        if self.context.student.state != REGISTERED \
357            and self.context.student.current_level == self.context.level:
358            self.flash(_('Forbidden'), type="warning")
359            self.redirect(self.url(self.context))
360            return
361
362    @property
363    def label(self):
364        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
365        lang = self.request.cookies.get('kofa.language', portal_language)
366        level_title = translate(self.context.level_title, 'waeup.kofa',
367            target_language=lang)
368        line0 = ''
369        if self.context.student.is_postgrad:
370            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
371        elif self.context.student.current_mode.endswith('_pt'):
372            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
373        line1 = translate(_('Course Registration Slip'),
374            target_language=portal_language) \
375            + ' %s' % level_title
376        line2 = translate(_('Session'),
377            target_language=portal_language) \
378            + ' %s' % self.context.getSessionString
379        return '%s%s\n%s' % (line0, line1, line2)
380
381    @property
382    def title(self):
383        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
384        return translate(_('Units Registered'), target_language=portal_language)
385
386    def _signatures(self):
387        if self.context.student.current_mode.endswith('_pt') \
388            or self.context.student.current_mode == 'found':
389            return (
390                [('I have selected the course on the advise of my Head of '
391                 'Department. <br>', _('Student\'s Signature'), '<br>')],
392                [('This student has satisfied the department\'s requirements. '
393                 'I recommend to approve the course registration. <br>',
394                 _('Head of Department\'s Signature'), '<br>')],
395                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
396                [('', _('Director\'s Signature'))]
397                )
398        if self.context.student.current_mode in (
399            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
400            return ([_('Academic Adviser\'s Signature'),
401                _('Faculty Officer\'s Signature'),
402                _('Student\'s Signature')],)
403
404        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
405            return (
406                [('I declare that all items of information supplied above are correct:' ,
407                    _('Student\'s Signature'), '<br>')],
408                [('We approved the above registration:',
409                    _('Major Supervisor (Name / Signature)'), '')],
410                [('', _('Co-Supervisor (Name / Signature)'), '')],
411                [('', _('Head of Department'), '<br>')],
412                [('The student has satisfied the conditions for renewal of '
413                  'registration for graduate school programme in this university:',
414                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
415                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
416                )
417        return None
418
419
420    def render(self):
421        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
422        Sem = translate(_('Sem.'), target_language=portal_language)
423        Code = translate(_('Code'), target_language=portal_language)
424        Title = translate(_('Title'), target_language=portal_language)
425        Cred = translate(_('Cred.'), target_language=portal_language)
426        if self.show_results:
427            Score = translate(_('Score'), target_language=portal_language)
428            #CA = translate(_('CA'), target_language=portal_language)
429            Grade = translate(_('Grade'), target_language=portal_language)
430        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
431            target_language=portal_language)
432        studentview = StudentBasePDFFormPage(self.context.student,
433            self.request, self.omit_fields)
434        students_utils = getUtility(IStudentsUtils)
435
436        tabledata = []
437        tableheader = []
438        contenttitle = []
439        for i in range(1,7):
440            tabledata.append(sorted(
441                [value for value in self.context.values() if value.semester == i],
442                key=lambda value: str(value.semester) + value.code))
443            if self.show_results:
444                tableheader.append([(Code,'code', 2.0),
445                                   (Title,'title', 7),
446                                   (Cred, 'credits', 1.5),
447                                   (Score, 'score', 1.4),
448                                   #(CA, 'ca', 1.4),
449                                   (Grade, 'grade', 1.4),
450                                   (Signature, 'dummy', 3),
451                                   ])
452            else:
453                tableheader.append([(Code,'code', 2.0),
454                                   (Title,'title', 7),
455                                   (Cred, 'credits', 1.5),
456                                   (Signature, 'dummy', 3),
457                                   ])
458        if len(self.label.split('\n')) == 3:
459            topMargin = 1.9
460        elif len(self.label.split('\n')) == 2:
461            topMargin = 1.7
462        else:
463            topMargin = 1.5
464        return students_utils.renderPDF(
465            self, 'course_registration_slip.pdf',
466            self.context.student, studentview,
467            tableheader=tableheader,
468            tabledata=tabledata,
469            signatures=self._signatures(),
470            topMargin=topMargin,
471            omit_fields=self.omit_fields
472            )
473
474class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
475    """ Page to display the student's transcript.
476    """
477    grok.require('waeup.viewStudent')
478
479class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
480    """Deliver a PDF slip of the context.
481    """
482#    grok.require('waeup.viewStudent')
483
484    note = _("""
485<br /><br /><br /><br />
486<font size='10'>
487<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
488</font>
489""")
490
491    def _sigsInFooter(self):
492        return []
493
494    def _signatures(self):
495        return ([(
496            'Mrs. Uniamikogbo, S.O., mnim, manupa <br /> Prin. Asst Registrar  <br /> '
497            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
498
499    def render(self):
500        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
501        Term = translate(_('Sem.'), target_language=portal_language)
502        Code = translate(_('Code'), target_language=portal_language)
503        Title = translate(_('Title'), target_language=portal_language)
504        Cred = translate(_('Credits'), target_language=portal_language)
505        Score = translate(_('Score'), target_language=portal_language)
506        Grade = translate(_('Grade'), target_language=portal_language)
507        studentview = StudentBasePDFFormPage(self.context.student,
508            self.request, self.omit_fields)
509        students_utils = getUtility(IStudentsUtils)
510
511        tableheader = [(Code,'code', 2.5),
512                         (Title,'title', 7),
513                         (Term, 'semester', 1.5),
514                         (Cred, 'credits', 1.5),
515                         (Score, 'total_score', 1.5),
516                         (Grade, 'grade', 1.5),
517                         ]
518
519        return students_utils.renderPDFTranscript(
520            self, 'transcript.pdf',
521            self.context.student, studentview,
522            omit_fields=self.omit_fields,
523            tableheader=tableheader,
524            signatures=self._signatures(),
525            sigs_in_footer=self._sigsInFooter(),
526            note = self.note,
527            no_passport=True
528            )
529
530class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
531    """Deliver a PDF Admission slip.
532    """
533
534    @property
535    def label(self):
536        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
537        return translate(_('e-Admission Slip \n'),
538            target_language=portal_language) \
539            + ' %s' % self.context.display_fullname
540
541class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
542    """Deliver a PDF slip of the context.
543    """
544
545    @property
546    def label(self):
547        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
548        return translate(_('Verification/Clearance Slip\n'),
549            target_language=portal_language) \
550            + ' %s' % self.context.display_fullname
551
552    @property
553    def form_fields(self):
554        if self.context.is_postgrad:
555            form_fields = grok.AutoFields(
556                ICustomPGStudentClearance).omit('clearance_locked')
557        else:
558            form_fields = grok.AutoFields(
559                ICustomUGStudentClearance).omit('clearance_locked')
560        if not getattr(self.context, 'officer_comment'):
561            form_fields = form_fields.omit('officer_comment')
562        form_fields = form_fields.omit('def_adm')
563        return form_fields
564
565class StudentGetMatricNumberPage(UtilityView, grok.View):
566    """ Construct and set the matriculation number.
567    """
568    grok.context(IStudent)
569    grok.name('get_matric_number')
570    grok.require('waeup.viewStudent')
571
572    def update(self):
573        students_utils = getUtility(IStudentsUtils)
574        msg, mnumber = students_utils.setMatricNumber(self.context)
575        if msg:
576            self.flash(msg, type="danger")
577        else:
578            self.flash(_('Matriculation number %s assigned.' % mnumber))
579            self.context.writeLogMessage(self, '%s assigned' % mnumber)
580        self.redirect(self.url(self.context))
581        return
582
583    def render(self):
584        return
585
586class ExportPDFMatricNumberSlip(UtilityView, grok.View):
587    """Deliver a PDF notification slip.
588    """
589    grok.context(ICustomStudent)
590    grok.name('matric_number_slip.pdf')
591    grok.require('waeup.viewStudent')
592    prefix = 'form'
593
594    form_fields = grok.AutoFields(ICustomStudent).select(
595        'student_id', 'matric_number')
596    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
597
598    @property
599    def title(self):
600        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
601        return translate(_('Matriculation Number'), 'waeup.kofa',
602            target_language=portal_language)
603
604    @property
605    def label(self):
606        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
607        return translate(_('Matriculation Number Slip\n'),
608            target_language=portal_language) \
609            + ' %s' % self.context.display_fullname
610
611    def render(self):
612        if self.context.state not in (PAID,) or not self.context.is_fresh \
613            or not self.context.matric_number:
614            self.flash('Not allowed.', type="danger")
615            self.redirect(self.url(self.context))
616            return
617        students_utils = getUtility(IStudentsUtils)
618        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
619                     'payments have been received and your matriculation ' +
620                     'number generated with details as follows.')
621        return students_utils.renderPDFAdmissionLetter(self,
622            self.context.student, omit_fields=self.omit_fields,
623            pre_text=pre_text, post_text='')
624
625class ExportPersonalDataSlip(UtilityView, grok.View):
626    """Deliver a PDF notification slip.
627    """
628    grok.context(ICustomStudent)
629    grok.name('personal_data_slip.pdf')
630    grok.require('waeup.viewStudent')
631    prefix = 'form'
632    note = None
633
634    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
635    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
636                   'certificate', 'flash_notice')
637
638    @property
639    def title(self):
640        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
641        return translate(_('Personal Data'), 'waeup.kofa',
642            target_language=portal_language)
643
644    @property
645    def label(self):
646        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
647        return translate(_('Personal Data Slip\n'),
648            target_language=portal_language) \
649            + ' %s' % self.context.display_fullname
650
651    def render(self):
652        studentview = StudentBasePDFFormPage(self.context.student,
653            self.request, self.omit_fields)
654        students_utils = getUtility(IStudentsUtils)
655        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
656            self.context.student, studentview, note=self.note,
657            omit_fields=self.omit_fields)
658
659class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
660    """ Page to manage bed tickets.
661    This manage form page is for both students and students officers.
662    """
663    with_hostel_selection = True
664
665class CustomBedTicketAddPage(BedTicketAddPage):
666    with_ac = False
667
668class CustomStudentFilesUploadPage(StudentFilesUploadPage):
669    """ View to upload files by student. Inherit from same class in
670    base package, not from kofacustom.nigeria which
671    requires that no application slip exists.
672    """
673
674class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
675    """ Page to display course tickets
676    """
677    form_fields = grok.AutoFields(ICustomCourseTicket)
678
679class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
680    """ Page to manage course tickets
681    """
682    form_fields = grok.AutoFields(ICustomCourseTicket)
683    form_fields['title'].for_display = True
684    form_fields['fcode'].for_display = True
685    form_fields['dcode'].for_display = True
686    form_fields['semester'].for_display = True
687    form_fields['passmark'].for_display = True
688    form_fields['credits'].for_display = True
689    form_fields['mandatory'].for_display = False
690    form_fields['automatic'].for_display = True
691    form_fields['carry_over'].for_display = True
692
693class CustomEditScoresPage(EditScoresPage):
694    """Page that filters and lists students.
695    """
696    grok.template('editscorespage')
697
698
699    def _extract_uploadfile(self, uploadfile):
700        """Get a mapping of student-ids to scores.
701
702        The mapping is constructed by reading contents from `uploadfile`.
703
704        We expect uploadfile to be a regular CSV file with columns
705        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
706        """
707        result = dict()
708        data = StringIO(uploadfile.read())  # ensure we have something seekable
709        reader = csv.DictReader(data)
710        for row in reader:
711            if not ('student_id' in row and 'score' in row and 'ca' in row):
712                continue
713            result[row['student_id']] = (row['score'], row['ca'])
714        return result
715
716    def _update_scores(self, form):
717        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
718        error = ''
719        if 'UPDATE_FILE' in form:
720            if form['uploadfile']:
721                try:
722                    formvals = self._extract_uploadfile(form['uploadfile'])
723                except:
724                    self.flash(
725                        _('Uploaded file contains illegal data. Ignored'),
726                        type="danger")
727                    return False
728            else:
729                self.flash(
730                    _('No file provided.'), type="danger")
731                return False
732        else:
733            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
734        for ticket in self.editable_tickets:
735            ticket_error = False
736            score = ticket.score
737            ca = ticket.ca
738            sid = ticket.student.student_id
739            if formvals[sid][0] == '':
740                score = None
741            if formvals[sid][1] == '':
742                ca = None
743            try:
744                if formvals[sid][0]:
745                    score = int(formvals[sid][0])
746                if formvals[sid][1]:
747                    ca = int(formvals[sid][1])
748            except ValueError:
749                error += '%s, ' % ticket.student.display_fullname
750                ticket_error = True
751            if not ticket_error and ticket.score != score:
752                try:
753                    ticket.score = score
754                except TooBig:
755                    error += '%s, ' % ticket.student.display_fullname
756                    ticket_error = True
757                    pass
758                ticket.student.__parent__.logger.info(
759                    '%s - %s %s/%s score updated (%s)' %
760                    (ob_class, ticket.student.student_id,
761                     ticket.level, ticket.code, score))
762            if not ticket_error and ticket.ca != ca:
763                try:
764                    ticket.ca = ca
765                except TooBig:
766                    error += '%s, ' % ticket.student.display_fullname
767                    pass
768                ticket.student.__parent__.logger.info(
769                    '%s - %s %s/%s ca updated (%s)' %
770                    (ob_class, ticket.student.student_id,
771                     ticket.level, ticket.code, ca))
772        if error:
773            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
774              % error.strip(', ')), type="danger")
775        return True
776
777class EditPreviousSessionScoresPage(CustomEditScoresPage):
778
779    grok.name('edit_prev_scores')
780
781    def update(self,  *args, **kw):
782        form = self.request.form
783        self.current_academic_session = grok.getSite()[
784            'configuration'].current_academic_session
785        if self.context.__parent__.__parent__.score_editing_disabled:
786            self.flash(_('Score editing disabled.'), type="warning")
787            self.redirect(self.url(self.context))
788            return
789        if not self.current_academic_session:
790            self.flash(_('Current academic session not set.'), type="warning")
791            self.redirect(self.url(self.context))
792            return
793        previous_session = self.current_academic_session - 1
794        self.session_title = academic_sessions_vocab.getTerm(
795            previous_session).title
796        self.tickets = self._searchCatalog(previous_session)
797        if not self.tickets:
798            self.flash(_('No student found.'), type="warning")
799            self.redirect(self.url(self.context))
800            return
801        self.editable_tickets = [
802            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
803        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
804            return
805        if not self.editable_tickets:
806            return
807        success = self._update_scores(form)
808        if success:
809            self.flash(_('You successfully updated course results.'))
810        return
811
812class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
813    """Deliver a PDF slip of course tickets for a lecturer.
814    """
815
816    def data(self, session):
817        cat = queryUtility(ICatalog, name='coursetickets_catalog')
818        coursetickets = cat.searchResults(
819            session=(session, session),
820            code=(self.context.code, self.context.code)
821            )
822        # In AAUE only editable tickets can be printed
823        editable_tickets = [
824            ticket for ticket in coursetickets if ticket.editable_by_lecturer]
825        header = [[_(''),
826                   _('Matric No.'),
827                   _('Reg. No.'),
828                   _('Fullname'),
829                   _('Status'),
830                   _('Course of\nStudies'),
831                   _('Level'),
832                   _('Exam\nScore'),
833                   _(' CA  '),
834                   _('Total '),
835                   _('Grade'),
836                   ],]
837        sorted_tickets = sorted(editable_tickets,
838            key=lambda ticket: ticket.student.certcode +
839                ticket.student.display_fullname + str(ticket.level))
840        no = 1
841        tickets = []
842        passed = 0
843        failed = 0
844        # In AAUE only editable tickets can be printed
845        for ticket in sorted_tickets:
846            if None in (ticket.score, ticket.ca):
847                total = 'n/a'
848                grade = 'n/a'
849            else:
850                total = ticket.score + ticket.ca
851                grade = getGradeWeightFromScore(total, ticket.student)[0]
852                if grade == 'F':
853                    failed += 1
854                else:
855                    passed += 1
856            fullname = textwrap.fill(ticket.student.display_fullname, 30)
857            row = [no,
858                  ticket.student.matric_number,
859                  ticket.student.reg_number,
860                  fullname,
861                  ticket.student.translated_state,
862                  ticket.student.certcode,
863                  ticket.level,
864                  ticket.ca,
865                  ticket.score,
866                  total,
867                  grade,
868                  ]
869            tickets.append(row)
870            no += 1
871        total = passed + failed
872        passed_perc = 0
873        failed_perc = 0
874        if total:
875            passed_perc = 100 * passed / total
876            failed_perc = 100 * failed / total
877        return header + tickets, [
878            total, passed, passed_perc, failed, failed_perc]
879
880class DownloadPreviousSessionScoresView(DownloadScoresView):
881    """View that exports scores.
882    """
883    grok.name('download_prev_scores')
884
885    def update(self):
886        self.current_academic_session = grok.getSite()[
887            'configuration'].current_academic_session
888        if self.context.__parent__.__parent__.score_editing_disabled:
889            self.flash(_('Score editing disabled.'), type="warning")
890            self.redirect(self.url(self.context))
891            return
892        if not self.current_academic_session:
893            self.flash(_('Current academic session not set.'), type="warning")
894            self.redirect(self.url(self.context))
895            return
896        site = grok.getSite()
897        exporter = getUtility(ICSVExporter, name='lecturer')
898        self.csv = exporter.export_filtered(site, filepath=None,
899                                 catalog='coursetickets',
900                                 session=self.current_academic_session-1,
901                                 level=None,
902                                 code=self.context.code)
903        return
904
905class AlumniRequestPasswordPage(StudentRequestPasswordPage):
906    """Captcha'd request password page for students.
907    """
908    grok.name('alumni_requestpw')
909    grok.require('waeup.Anonymous')
910    grok.template('alumni_requestpw')
911    form_fields = grok.AutoFields(IStudentRequestPW).select(
912        'lastname','number','email')
913    label = _('Search student record and send password for first-time login')
914
915    def _redirect_no_student(self):
916        self.flash(_('No student record found.'), type="warning")
917        self.redirect(self.application_url() + '/applicants/trans2017/register')
918        return
Note: See TracBrowser for help on using the repository browser.