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

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

Implement 'switch' to enable and disable displaying course results.

  • Property svn:keywords set to Id
File size: 42.6 KB
Line 
1## $Id: browser.py 14944 2018-02-01 11:57:38Z 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        try:
407            show_results = grok.getSite()[
408                'configuration'][str(self.context.level_session)].show_results
409        except KeyError:
410            return False
411        if isStudent and self.context.student.current_mode not in show_results:
412            return False
413        if isStudent and self.context.student.state != RETURNING \
414            and self.context.student.current_level == self.context.level:
415            return False
416        return True
417
418class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
419    """ Page to edit the student study level data
420    """
421    grok.context(ICustomStudentStudyLevel)
422
423    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
424        'validation_date', 'validated_by', 'total_credits', 'gpa', 'level',
425        'total_credits_s1', 'total_credits_s2')
426
427    form_fields['imported_gpa'].for_display = True
428    form_fields['imported_cgpa'].for_display = True
429
430class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
431    """ Page to edit the student study level data by students
432    """
433    grok.context(ICustomStudentStudyLevel)
434
435class CustomExportPDFCourseRegistrationSlip(
436    NigeriaExportPDFCourseRegistrationSlip):
437    """Deliver a PDF slip of the context.
438    """
439    grok.context(ICustomStudentStudyLevel)
440    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
441        'level_session', 'level_verdict',
442        'validated_by', 'validation_date', 'gpa', 'level',
443        'imported_gpa', 'imported_cgpa')
444
445    omit_fields = ('password', 'suspended', 'suspended_comment',
446        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
447        'department', 'current_mode', 'current_level', 'flash_notice')
448
449    @property
450    def show_results(self):
451        isStudent = getattr(
452            self.request.principal, 'user_type', None) == 'student'
453        try:
454            show_results = grok.getSite()[
455                'configuration'][str(self.context.level_session)].show_results
456        except KeyError:
457            return False
458        if isStudent and self.context.student.current_mode not in show_results:
459            return False
460        if isStudent and self.context.student.state != RETURNING \
461            and self.context.student.current_level == self.context.level:
462            return False
463        return True
464
465    def update(self):
466        if self.context.student.state != REGISTERED \
467            and self.context.student.current_level == self.context.level:
468            self.flash(_('Forbidden'), type="warning")
469            self.redirect(self.url(self.context))
470            return
471
472    @property
473    def label(self):
474        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
475        lang = self.request.cookies.get('kofa.language', portal_language)
476        level_title = translate(self.context.level_title, 'waeup.kofa',
477            target_language=lang)
478        line0 = ''
479        if self.context.student.is_postgrad:
480            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
481        elif self.context.student.current_mode.endswith('_pt'):
482            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
483        line1 = translate(_('Course Registration Slip'),
484            target_language=portal_language) \
485            + ' %s' % level_title
486        line2 = translate(_('Session'),
487            target_language=portal_language) \
488            + ' %s' % self.context.getSessionString
489        return '%s%s\n%s' % (line0, line1, line2)
490
491    @property
492    def title(self):
493        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
494        return translate(_('Units Registered'), target_language=portal_language)
495
496    def _signatures(self):
497        if self.context.student.current_mode.endswith('_pt') \
498            or self.context.student.current_mode == 'found':
499            return (
500                [('I have selected the course on the advise of my Head of '
501                 'Department. <br>', _('Student\'s Signature'), '<br>')],
502                [('This student has satisfied the department\'s requirements. '
503                 'I recommend to approve the course registration. <br>',
504                 _('Head of Department\'s Signature'), '<br>')],
505                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
506                [('', _('Director\'s Signature'))]
507                )
508        if self.context.student.current_mode in (
509            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
510            return ([_('Academic Adviser\'s Signature'),
511                _('Faculty Officer\'s Signature'),
512                _('Student\'s Signature')],)
513
514        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
515            return (
516                [('I declare that all items of information supplied above are correct:' ,
517                    _('Student\'s Signature'), '<br>')],
518                [('We approved the above registration:',
519                    _('Major Supervisor (Name / Signature)'), '')],
520                [('', _('Co-Supervisor (Name / Signature)'), '')],
521                [('', _('Head of Department'), '<br>')],
522                [('The student has satisfied the conditions for renewal of '
523                  'registration for graduate school programme in this university:',
524                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
525                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
526                )
527        return None
528
529
530    def render(self):
531        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
532        Sem = translate(_('Sem.'), target_language=portal_language)
533        Code = translate(_('Code'), target_language=portal_language)
534        Title = translate(_('Title'), target_language=portal_language)
535        Cred = translate(_('Cred.'), target_language=portal_language)
536        CC = translate(_('Cat.'), target_language=portal_language)
537        if self.show_results:
538            Score = translate(_('Score'), target_language=portal_language)
539            #CA = translate(_('CA'), target_language=portal_language)
540            Grade = translate(_('Grade'), target_language=portal_language)
541        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
542            target_language=portal_language)
543        studentview = StudentBasePDFFormPage(self.context.student,
544            self.request, self.omit_fields)
545        students_utils = getUtility(IStudentsUtils)
546
547        tabledata = []
548        tableheader = []
549        contenttitle = []
550        for i in range(1,7):
551            tabledata.append(sorted(
552                [value for value in self.context.values() if value.semester == i],
553                key=lambda value: str(value.semester) + value.code))
554            if self.show_results:
555                tableheader.append([(Code,'code', 2.0),
556                                   (Title,'title', 7),
557                                   (Cred, 'credits', 1.4),
558                                   (CC, 'course_category', 1.2),
559                                   (Score, 'score', 1.4),
560                                   #(CA, 'ca', 1.4),
561                                   (Grade, 'grade', 1.4),
562                                   (Signature, 'dummy', 3),
563                                   ])
564            else:
565                tableheader.append([(Code,'code', 2.0),
566                                   (Title,'title', 7),
567                                   (Cred, 'credits', 1.5),
568                                   (CC, 'course_category', 1.2),
569                                   (Signature, 'dummy', 3),
570                                   ])
571        if len(self.label.split('\n')) == 3:
572            topMargin = 1.9
573        elif len(self.label.split('\n')) == 2:
574            topMargin = 1.7
575        else:
576            topMargin = 1.5
577        return students_utils.renderPDF(
578            self, 'course_registration_slip.pdf',
579            self.context.student, studentview,
580            tableheader=tableheader,
581            tabledata=tabledata,
582            signatures=self._signatures(),
583            topMargin=topMargin,
584            omit_fields=self.omit_fields
585            )
586
587class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
588    """ Page to display the student's transcript.
589    """
590    grok.require('waeup.viewStudent')
591
592class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
593    """Deliver a PDF slip of the context.
594    """
595#    grok.require('waeup.viewStudent')
596
597    note = _("""
598<br /><br /><br /><br />
599<font size='10'>
600<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
601</font>
602""")
603
604    def _sigsInFooter(self):
605        return []
606
607    def _signatures(self):
608        return ([(
609            'Mrs. Uniamikogbo, S.O., mnim, manupa <br /> Prin. Asst Registrar  <br /> '
610            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
611
612    def render(self):
613        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
614        Term = translate(_('Sem.'), target_language=portal_language)
615        Code = translate(_('Code'), target_language=portal_language)
616        Title = translate(_('Title'), target_language=portal_language)
617        Cred = translate(_('Credits'), target_language=portal_language)
618        Score = translate(_('Score'), target_language=portal_language)
619        Grade = translate(_('Grade'), target_language=portal_language)
620        studentview = StudentBasePDFFormPage(self.context.student,
621            self.request, self.omit_fields)
622        students_utils = getUtility(IStudentsUtils)
623
624        tableheader = [(Code,'code', 2.5),
625                         (Title,'title', 7),
626                         (Term, 'semester', 1.5),
627                         (Cred, 'credits', 1.5),
628                         (Score, 'total_score', 1.5),
629                         (Grade, 'grade', 1.5),
630                         ]
631
632        return students_utils.renderPDFTranscript(
633            self, 'transcript.pdf',
634            self.context.student, studentview,
635            omit_fields=self.omit_fields,
636            tableheader=tableheader,
637            signatures=self._signatures(),
638            sigs_in_footer=self._sigsInFooter(),
639            note = self.note,
640            no_passport=True
641            )
642
643class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
644    """Deliver a PDF Admission slip.
645    """
646
647    @property
648    def label(self):
649        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
650        return translate(_('e-Admission Slip \n'),
651            target_language=portal_language) \
652            + ' %s' % self.context.display_fullname
653
654class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
655    """Deliver a PDF slip of the context.
656    """
657
658    @property
659    def label(self):
660        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
661        return translate(_('Verification/Clearance Slip\n'),
662            target_language=portal_language) \
663            + ' %s' % self.context.display_fullname
664
665    @property
666    def form_fields(self):
667        if self.context.is_postgrad:
668            form_fields = grok.AutoFields(
669                ICustomPGStudentClearance).omit('clearance_locked')
670        else:
671            form_fields = grok.AutoFields(
672                ICustomUGStudentClearance).omit('clearance_locked')
673        if not getattr(self.context, 'officer_comment'):
674            form_fields = form_fields.omit('officer_comment')
675        form_fields = form_fields.omit('def_adm')
676        return form_fields
677
678class StudentGetMatricNumberPage(UtilityView, grok.View):
679    """ Construct and set the matriculation number.
680    """
681    grok.context(IStudent)
682    grok.name('get_matric_number')
683    grok.require('waeup.viewStudent')
684
685    def update(self):
686        students_utils = getUtility(IStudentsUtils)
687        msg, mnumber = students_utils.setMatricNumber(self.context)
688        if msg:
689            self.flash(msg, type="danger")
690        else:
691            self.flash(_('Matriculation number %s assigned.' % mnumber))
692            self.context.writeLogMessage(self, '%s assigned' % mnumber)
693        self.redirect(self.url(self.context))
694        return
695
696    def render(self):
697        return
698
699class ExportPDFMatricNumberSlip(UtilityView, grok.View):
700    """Deliver a PDF notification slip.
701    """
702    grok.context(ICustomStudent)
703    grok.name('matric_number_slip.pdf')
704    grok.require('waeup.viewStudent')
705    prefix = 'form'
706
707    form_fields = grok.AutoFields(ICustomStudent).select(
708        'student_id', 'matric_number')
709    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
710
711    @property
712    def title(self):
713        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
714        return translate(_('Matriculation Number'), 'waeup.kofa',
715            target_language=portal_language)
716
717    @property
718    def label(self):
719        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
720        return translate(_('Matriculation Number Slip\n'),
721            target_language=portal_language) \
722            + ' %s' % self.context.display_fullname
723
724    def render(self):
725        if self.context.state not in (PAID,) or not self.context.is_fresh \
726            or not self.context.matric_number:
727            self.flash('Not allowed.', type="danger")
728            self.redirect(self.url(self.context))
729            return
730        students_utils = getUtility(IStudentsUtils)
731        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
732                     'payments have been received and your matriculation ' +
733                     'number generated with details as follows.')
734        return students_utils.renderPDFAdmissionLetter(self,
735            self.context.student, omit_fields=self.omit_fields,
736            pre_text=pre_text, post_text='')
737
738class ExportPersonalDataSlip(UtilityView, grok.View):
739    """Deliver a PDF notification slip.
740    """
741    grok.context(ICustomStudent)
742    grok.name('personal_data_slip.pdf')
743    grok.require('waeup.viewStudent')
744    prefix = 'form'
745    note = None
746
747    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
748    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
749                   'certificate', 'flash_notice')
750
751    @property
752    def title(self):
753        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
754        return translate(_('Personal Data'), 'waeup.kofa',
755            target_language=portal_language)
756
757    @property
758    def label(self):
759        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
760        return translate(_('Personal Data Slip\n'),
761            target_language=portal_language) \
762            + ' %s' % self.context.display_fullname
763
764    def render(self):
765        studentview = StudentBasePDFFormPage(self.context.student,
766            self.request, self.omit_fields)
767        students_utils = getUtility(IStudentsUtils)
768        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
769            self.context.student, studentview, note=self.note,
770            omit_fields=self.omit_fields)
771
772class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
773    """ Page to manage bed tickets.
774    This manage form page is for both students and students officers.
775    """
776    with_hostel_selection = True
777
778class CustomBedTicketAddPage(BedTicketAddPage):
779    with_ac = False
780
781class CustomStudentFilesUploadPage(StudentFilesUploadPage):
782    """ View to upload files by student. Inherit from same class in
783    base package, not from kofacustom.nigeria which
784    requires that no application slip exists.
785    """
786
787class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
788    """ Page to display course tickets
789    """
790
791    @property
792    def show_results(self):
793        isStudent = getattr(
794            self.request.principal, 'user_type', None) == 'student'
795        if isStudent:
796            return False
797        return True
798
799    @property
800    def form_fields(self):
801        if self.show_results:
802            return grok.AutoFields(ICustomCourseTicket)
803        else:
804            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
805
806class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
807    """ Page to manage course tickets
808    """
809    form_fields = grok.AutoFields(ICustomCourseTicket)
810    form_fields['title'].for_display = True
811    form_fields['fcode'].for_display = True
812    form_fields['dcode'].for_display = True
813    form_fields['semester'].for_display = True
814    form_fields['passmark'].for_display = True
815    form_fields['credits'].for_display = True
816    form_fields['mandatory'].for_display = False
817    form_fields['automatic'].for_display = True
818    form_fields['carry_over'].for_display = True
819
820class CustomEditScoresPage(EditScoresPage):
821    """Page that filters and lists students.
822    """
823    grok.template('editscorespage')
824
825    def _searchCatalog(self, session):
826        cat = queryUtility(ICatalog, name='coursetickets_catalog')
827        coursetickets = cat.searchResults(
828            session=(session, session),
829            code=(self.context.code, self.context.code)
830            )
831        try:
832            score_editing_enabled = grok.getSite()[
833                'configuration'][str(session)].score_editing_enabled
834        except KeyError:
835            return []
836        coursetickets_list = [courseticket for courseticket in coursetickets
837            if courseticket.student.current_mode in score_editing_enabled]
838        return coursetickets_list
839
840    def _extract_uploadfile(self, uploadfile):
841        """Get a mapping of student-ids to scores.
842
843        The mapping is constructed by reading contents from `uploadfile`.
844
845        We expect uploadfile to be a regular CSV file with columns
846        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
847        """
848        result = dict()
849        data = StringIO(uploadfile.read())  # ensure we have something seekable
850        reader = csv.DictReader(data)
851        for row in reader:
852            if not ('student_id' in row and 'score' in row and 'ca' in row):
853                continue
854            result[row['student_id']] = (row['score'], row['ca'])
855        return result
856
857    def _update_scores(self, form):
858        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
859        error = ''
860        if 'UPDATE_FILE' in form:
861            if form['uploadfile']:
862                try:
863                    formvals = self._extract_uploadfile(form['uploadfile'])
864                except:
865                    self.flash(
866                        _('Uploaded file contains illegal data. Ignored'),
867                        type="danger")
868                    return False
869            else:
870                self.flash(
871                    _('No file provided.'), type="danger")
872                return False
873        else:
874            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
875        for ticket in self.editable_tickets:
876            ticket_error = False
877            score = ticket.score
878            ca = ticket.ca
879            sid = ticket.student.student_id
880            if formvals[sid][0] == '':
881                score = None
882            if formvals[sid][1] == '':
883                ca = None
884            try:
885                if formvals[sid][0]:
886                    score = int(formvals[sid][0])
887                if formvals[sid][1]:
888                    ca = int(formvals[sid][1])
889            except ValueError:
890                error += '%s, ' % ticket.student.display_fullname
891                ticket_error = True
892            if not ticket_error and ticket.score != score:
893                try:
894                    ticket.score = score
895                except TooBig:
896                    error += '%s, ' % ticket.student.display_fullname
897                    ticket_error = True
898                    pass
899                ticket.student.__parent__.logger.info(
900                    '%s - %s %s/%s score updated (%s)' %
901                    (ob_class, ticket.student.student_id,
902                     ticket.level, ticket.code, score))
903            if not ticket_error and ticket.ca != ca:
904                try:
905                    ticket.ca = ca
906                except TooBig:
907                    error += '%s, ' % ticket.student.display_fullname
908                    pass
909                ticket.student.__parent__.logger.info(
910                    '%s - %s %s/%s ca updated (%s)' %
911                    (ob_class, ticket.student.student_id,
912                     ticket.level, ticket.code, ca))
913        if error:
914            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
915              % error.strip(', ')), type="danger")
916        return True
917
918class EditPreviousSessionScoresPage(CustomEditScoresPage):
919
920    grok.name('edit_prev_scores')
921
922    def update(self,  *args, **kw):
923        form = self.request.form
924        self.current_academic_session = grok.getSite()[
925            'configuration'].current_academic_session
926        if self.context.__parent__.__parent__.score_editing_disabled:
927            self.flash(_('Score editing disabled.'), type="warning")
928            self.redirect(self.url(self.context))
929            return
930        if not self.current_academic_session:
931            self.flash(_('Current academic session not set.'), type="warning")
932            self.redirect(self.url(self.context))
933            return
934        previous_session = self.current_academic_session - 1
935        self.session_title = academic_sessions_vocab.getTerm(
936            previous_session).title
937        self.tickets = self._searchCatalog(previous_session)
938        if not self.tickets:
939            self.flash(_('No student found.'), type="warning")
940            self.redirect(self.url(self.context))
941            return
942        self.editable_tickets = [
943            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
944        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
945            return
946        if not self.editable_tickets:
947            return
948        success = self._update_scores(form)
949        if success:
950            self.flash(_('You successfully updated course results.'))
951        return
952
953class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
954    """Deliver a PDF slip of course tickets for a lecturer.
955    """
956
957    def data(self, session):
958        #site = grok.getSite()
959        cat = queryUtility(ICatalog, name='coursetickets_catalog')
960        coursetickets = cat.searchResults(
961            session=(session, session),
962            code=(self.context.code, self.context.code)
963            )
964        # Apply filter
965        try:
966            score_editing_enabled = grok.getSite()[
967                'configuration'][str(session)].score_editing_enabled
968            coursetickets_filtered = [courseticket
969                for courseticket in coursetickets
970                if courseticket.student.current_mode in score_editing_enabled]
971        except KeyError:
972            coursetickets_filtered = coursetickets
973        # In AAUE only editable tickets can be printed
974        editable_tickets = [
975            ticket for ticket in coursetickets_filtered
976            if ticket.editable_by_lecturer]
977        header = [[_(''),
978                   _('Matric No.'),
979                   #_('Reg. No.'),
980                   #_('Fullname'),
981                   #_('Status'),
982                   #_('Course of\nStudies'),
983                   _('Department'),
984                   _('Level'),
985                   _(' CA  '),
986                   _('Exam\nScore'),
987                   _('Total '),
988                   _('Grade'),
989                   ],]
990        sorted_tickets = sorted(editable_tickets,
991            key=lambda ticket: ticket.student.faccode
992                               + ticket.student.depcode
993                               + ticket.student.matric_number)
994        no = 1
995        tickets = []
996        passed = 0
997        failed = 0
998        # In AAUE only editable tickets can be printed
999        for ticket in sorted_tickets:
1000            if ticket.total_score is None:
1001                total = 'n/a'
1002                grade = 'n/a'
1003            else:
1004                total = ticket.total_score
1005                grade = ticket._getGradeWeightFromScore[0]
1006                if grade in ('F', '-'):
1007                    failed += 1
1008                else:
1009                    passed += 1
1010            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1011            #deptitle = site['faculties'][ticket.student.faccode][
1012            #    ticket.student.depcode].longtitle
1013            row = [no,
1014                  ticket.student.matric_number,
1015                  #ticket.student.reg_number,
1016                  #fullname,
1017                  #ticket.student.translated_state,
1018                  #ticket.student.certcode,
1019                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1020                  ticket.level,
1021                  ticket.ca,
1022                  ticket.score,
1023                  total,
1024                  grade,
1025                  ]
1026            tickets.append(row)
1027            no += 1
1028        total = passed + failed
1029        passed_perc = 0
1030        failed_perc = 0
1031        if total:
1032            passed_perc = 100 * passed / total
1033            failed_perc = 100 * failed / total
1034        dep = self.context.__parent__.__parent__.longtitle
1035        fac = self.context.__parent__.__parent__.__parent__.longtitle
1036        return header + tickets, [
1037            dep, fac, total, passed, passed_perc, failed, failed_perc]
1038
1039    def render(self):
1040        session = grok.getSite()['configuration'].current_academic_session
1041        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1042                     if i['local_role'] == 'waeup.local.Lecturer']
1043        lecturers =  ', '.join(lecturers)
1044        students_utils = getUtility(IStudentsUtils)
1045        # only orientation is different
1046        return students_utils.renderPDFCourseticketsOverview(
1047            self, session, self.data(session), lecturers, '')
1048
1049class DownloadPreviousSessionScoresView(DownloadScoresView):
1050    """View that exports scores.
1051    """
1052    grok.name('download_prev_scores')
1053
1054    def update(self):
1055        self.current_academic_session = grok.getSite()[
1056            'configuration'].current_academic_session
1057        if self.context.__parent__.__parent__.score_editing_disabled:
1058            self.flash(_('Score editing disabled.'), type="warning")
1059            self.redirect(self.url(self.context))
1060            return
1061        if not self.current_academic_session:
1062            self.flash(_('Current academic session not set.'), type="warning")
1063            self.redirect(self.url(self.context))
1064            return
1065        site = grok.getSite()
1066        exporter = getUtility(ICSVExporter, name='lecturer')
1067        self.csv = exporter.export_filtered(site, filepath=None,
1068                                 catalog='coursetickets',
1069                                 session=self.current_academic_session-1,
1070                                 level=None,
1071                                 code=self.context.code)
1072        return
1073
1074class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1075    """Captcha'd request password page for students.
1076    """
1077    grok.name('alumni_requestpw')
1078    grok.require('waeup.Anonymous')
1079    grok.template('alumni_requestpw')
1080    form_fields = grok.AutoFields(IStudentRequestPW).select(
1081        'lastname','number','email')
1082    label = _('Search student record and send password for first-time login')
1083
1084    def _redirect_no_student(self):
1085        self.flash(_('No student record found.'), type="warning")
1086        self.redirect(self.application_url() + '/applicants/trans2017/register')
1087        return
Note: See TracBrowser for help on using the repository browser.