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

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

Adjust payment category title.

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