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

Last change on this file since 14697 was 14697, checked in by Henrik Bettermann, 7 years ago

Change column titles.

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