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

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

Provide faculty and department title.

  • Property svn:keywords set to Id
File size: 42.2 KB
Line 
1## $Id: browser.py 14710 2017-07-05 05:45:16Z 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    def _searchCatalog(self, session):
818        cat = queryUtility(ICatalog, name='coursetickets_catalog')
819        coursetickets = cat.searchResults(
820            session=(session, session),
821            code=(self.context.code, self.context.code)
822            )
823        try:
824            score_editing_enabled = grok.getSite()[
825                'configuration'][str(session)].score_editing_enabled
826        except KeyError:
827            return []
828        coursetickets_list = [courseticket for courseticket in coursetickets
829            if courseticket.student.current_mode in score_editing_enabled]
830        return coursetickets_list
831
832    def _extract_uploadfile(self, uploadfile):
833        """Get a mapping of student-ids to scores.
834
835        The mapping is constructed by reading contents from `uploadfile`.
836
837        We expect uploadfile to be a regular CSV file with columns
838        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
839        """
840        result = dict()
841        data = StringIO(uploadfile.read())  # ensure we have something seekable
842        reader = csv.DictReader(data)
843        for row in reader:
844            if not ('student_id' in row and 'score' in row and 'ca' in row):
845                continue
846            result[row['student_id']] = (row['score'], row['ca'])
847        return result
848
849    def _update_scores(self, form):
850        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
851        error = ''
852        if 'UPDATE_FILE' in form:
853            if form['uploadfile']:
854                try:
855                    formvals = self._extract_uploadfile(form['uploadfile'])
856                except:
857                    self.flash(
858                        _('Uploaded file contains illegal data. Ignored'),
859                        type="danger")
860                    return False
861            else:
862                self.flash(
863                    _('No file provided.'), type="danger")
864                return False
865        else:
866            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
867        for ticket in self.editable_tickets:
868            ticket_error = False
869            score = ticket.score
870            ca = ticket.ca
871            sid = ticket.student.student_id
872            if formvals[sid][0] == '':
873                score = None
874            if formvals[sid][1] == '':
875                ca = None
876            try:
877                if formvals[sid][0]:
878                    score = int(formvals[sid][0])
879                if formvals[sid][1]:
880                    ca = int(formvals[sid][1])
881            except ValueError:
882                error += '%s, ' % ticket.student.display_fullname
883                ticket_error = True
884            if not ticket_error and ticket.score != score:
885                try:
886                    ticket.score = score
887                except TooBig:
888                    error += '%s, ' % ticket.student.display_fullname
889                    ticket_error = True
890                    pass
891                ticket.student.__parent__.logger.info(
892                    '%s - %s %s/%s score updated (%s)' %
893                    (ob_class, ticket.student.student_id,
894                     ticket.level, ticket.code, score))
895            if not ticket_error and ticket.ca != ca:
896                try:
897                    ticket.ca = ca
898                except TooBig:
899                    error += '%s, ' % ticket.student.display_fullname
900                    pass
901                ticket.student.__parent__.logger.info(
902                    '%s - %s %s/%s ca updated (%s)' %
903                    (ob_class, ticket.student.student_id,
904                     ticket.level, ticket.code, ca))
905        if error:
906            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
907              % error.strip(', ')), type="danger")
908        return True
909
910class EditPreviousSessionScoresPage(CustomEditScoresPage):
911
912    grok.name('edit_prev_scores')
913
914    def update(self,  *args, **kw):
915        form = self.request.form
916        self.current_academic_session = grok.getSite()[
917            'configuration'].current_academic_session
918        if self.context.__parent__.__parent__.score_editing_disabled:
919            self.flash(_('Score editing disabled.'), type="warning")
920            self.redirect(self.url(self.context))
921            return
922        if not self.current_academic_session:
923            self.flash(_('Current academic session not set.'), type="warning")
924            self.redirect(self.url(self.context))
925            return
926        previous_session = self.current_academic_session - 1
927        self.session_title = academic_sessions_vocab.getTerm(
928            previous_session).title
929        self.tickets = self._searchCatalog(previous_session)
930        if not self.tickets:
931            self.flash(_('No student found.'), type="warning")
932            self.redirect(self.url(self.context))
933            return
934        self.editable_tickets = [
935            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
936        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
937            return
938        if not self.editable_tickets:
939            return
940        success = self._update_scores(form)
941        if success:
942            self.flash(_('You successfully updated course results.'))
943        return
944
945class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
946    """Deliver a PDF slip of course tickets for a lecturer.
947    """
948
949    def data(self, session):
950        #site = grok.getSite()
951        cat = queryUtility(ICatalog, name='coursetickets_catalog')
952        coursetickets = cat.searchResults(
953            session=(session, session),
954            code=(self.context.code, self.context.code)
955            )
956        # Apply filter
957        try:
958            score_editing_enabled = grok.getSite()[
959                'configuration'][str(session)].score_editing_enabled
960            coursetickets_filtered = [courseticket
961                for courseticket in coursetickets
962                if courseticket.student.current_mode in score_editing_enabled]
963        except KeyError:
964            coursetickets_filtered = coursetickets
965        # In AAUE only editable tickets can be printed
966        editable_tickets = [
967            ticket for ticket in coursetickets_filtered
968            if ticket.editable_by_lecturer]
969        header = [[_(''),
970                   _('Matric No.'),
971                   #_('Reg. No.'),
972                   #_('Fullname'),
973                   #_('Status'),
974                   #_('Course of\nStudies'),
975                   _('Department'),
976                   _('Level'),
977                   _(' CA  '),
978                   _('Exam\nScore'),
979                   _('Total '),
980                   _('Grade'),
981                   ],]
982        sorted_tickets = sorted(editable_tickets,
983            key=lambda ticket: ticket.student.faccode
984                               + ticket.student.depcode
985                               + ticket.student.matric_number)
986        no = 1
987        tickets = []
988        passed = 0
989        failed = 0
990        # In AAUE only editable tickets can be printed
991        for ticket in sorted_tickets:
992            if ticket.total_score is None:
993                total = 'n/a'
994                grade = 'n/a'
995            else:
996                total = ticket.total_score
997                grade = ticket._getGradeWeightFromScore[0]
998                if grade in ('F', '-'):
999                    failed += 1
1000                else:
1001                    passed += 1
1002            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1003            #deptitle = site['faculties'][ticket.student.faccode][
1004            #    ticket.student.depcode].longtitle
1005            row = [no,
1006                  ticket.student.matric_number,
1007                  #ticket.student.reg_number,
1008                  #fullname,
1009                  #ticket.student.translated_state,
1010                  #ticket.student.certcode,
1011                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1012                  ticket.level,
1013                  ticket.ca,
1014                  ticket.score,
1015                  total,
1016                  grade,
1017                  ]
1018            tickets.append(row)
1019            no += 1
1020        total = passed + failed
1021        passed_perc = 0
1022        failed_perc = 0
1023        if total:
1024            passed_perc = 100 * passed / total
1025            failed_perc = 100 * failed / total
1026        dep = self.context.__parent__.__parent__.longtitle
1027        fac = self.context.__parent__.__parent__.__parent__.longtitle
1028        return header + tickets, [
1029            dep, fac, total, passed, passed_perc, failed, failed_perc]
1030
1031    def render(self):
1032        session = grok.getSite()['configuration'].current_academic_session
1033        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1034                     if i['local_role'] == 'waeup.local.Lecturer']
1035        lecturers =  ', '.join(lecturers)
1036        students_utils = getUtility(IStudentsUtils)
1037        # only orientation is different
1038        return students_utils.renderPDFCourseticketsOverview(
1039            self, session, self.data(session), lecturers, '')
1040
1041class DownloadPreviousSessionScoresView(DownloadScoresView):
1042    """View that exports scores.
1043    """
1044    grok.name('download_prev_scores')
1045
1046    def update(self):
1047        self.current_academic_session = grok.getSite()[
1048            'configuration'].current_academic_session
1049        if self.context.__parent__.__parent__.score_editing_disabled:
1050            self.flash(_('Score editing disabled.'), type="warning")
1051            self.redirect(self.url(self.context))
1052            return
1053        if not self.current_academic_session:
1054            self.flash(_('Current academic session not set.'), type="warning")
1055            self.redirect(self.url(self.context))
1056            return
1057        site = grok.getSite()
1058        exporter = getUtility(ICSVExporter, name='lecturer')
1059        self.csv = exporter.export_filtered(site, filepath=None,
1060                                 catalog='coursetickets',
1061                                 session=self.current_academic_session-1,
1062                                 level=None,
1063                                 code=self.context.code)
1064        return
1065
1066class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1067    """Captcha'd request password page for students.
1068    """
1069    grok.name('alumni_requestpw')
1070    grok.require('waeup.Anonymous')
1071    grok.template('alumni_requestpw')
1072    form_fields = grok.AutoFields(IStudentRequestPW).select(
1073        'lastname','number','email')
1074    label = _('Search student record and send password for first-time login')
1075
1076    def _redirect_no_student(self):
1077        self.flash(_('No student record found.'), type="warning")
1078        self.redirect(self.application_url() + '/applicants/trans2017/register')
1079        return
Note: See TracBrowser for help on using the repository browser.