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

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

Enable instalment payments for IJMBE students.

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