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

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

Adjust test and views.

  • Property svn:keywords set to Id
File size: 44.6 KB
RevLine 
[8911]1## $Id: browser.py 15205 2018-10-28 21:29:39Z 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
[13937]19import csv
[13964]20import textwrap
[14981]21import pytz
[13937]22from cStringIO import StringIO
[14981]23from datetime import datetime
[8911]24from zope.i18n import translate
[13900]25from zope.component import getUtility, queryUtility
[14113]26from zope.schema.interfaces import TooBig, TooSmall
[13523]27from zope.security import checkPermission
[13900]28from zope.catalog.interfaces import ICatalog
[13351]29from zope.formlib.textwidgets import BytesDisplayWidget
[14981]30from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[11597]31from waeup.kofa.browser.layout import UtilityView
[8911]32from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
[14288]33from waeup.kofa.interfaces import (
[14306]34    IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject)
35from waeup.kofa.students.interfaces import (
36    IStudentsUtils, IStudent, IStudentRequestPW)
[14000]37from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
[9914]38from waeup.kofa.students.browser import (
[11846]39    StartClearancePage,
[9914]40    StudentBasePDFFormPage,
41    CourseTicketAddFormPage,
42    StudyLevelDisplayFormPage,
[13834]43    StudyLevelManageFormPage,
44    StudyLevelEditFormPage,
[13059]45    ExportPDFTranscriptSlip,
46    ExportPDFAdmissionSlip,
[13353]47    BedTicketAddPage,
[13380]48    StudentFilesUploadPage,
[13523]49    PaymentsManageFormPage,
[13770]50    CourseTicketDisplayFormPage,
51    CourseTicketManageFormPage,
[13900]52    EditScoresPage,
[14165]53    ExportPDFScoresSlip,
54    StudyCourseTranscriptPage,
[14288]55    DownloadScoresView,
[14534]56    StudentRequestPasswordPage,
[14981]57    StudyCourseManageFormPage,
58    UnregisterCoursesView
[10269]59    )
[8911]60from kofacustom.nigeria.students.browser import (
61    NigeriaOnlinePaymentDisplayFormPage,
62    NigeriaOnlinePaymentAddFormPage,
[13059]63    NigeriaExportPDFPaymentSlip,
64    NigeriaExportPDFCourseRegistrationSlip,
[13351]65    NigeriaStudentPersonalDisplayFormPage,
66    NigeriaStudentPersonalEditFormPage,
67    NigeriaStudentPersonalManageFormPage,
[14084]68    NigeriaStudentClearanceDisplayFormPage,
69    NigeriaExportPDFClearanceSlip,
70    NigeriaStudentClearanceManageFormPage,
[13362]71    NigeriaStudentClearanceEditFormPage,
[13462]72    NigeriaAccommodationManageFormPage,
[13795]73    NigeriaStudentBaseDisplayFormPage,
[14165]74    NigeriaStudentBaseManageFormPage
[10269]75    )
[9496]76from waeup.aaue.students.interfaces import (
[9914]77    ICustomStudentOnlinePayment,
[11607]78    ICustomStudentStudyLevel,
[13351]79    ICustomStudent,
80    ICustomStudentPersonal,
[13362]81    ICustomStudentPersonalEdit,
[13770]82    ICustomUGStudentClearance,
[14298]83    ICustomUGStudentClearanceEdit,
[14084]84    ICustomPGStudentClearance,
[13795]85    ICustomCourseTicket,
[14534]86    ICustomStudentBase,
87    ICustomStudentStudyCourse)
[13414]88from waeup.aaue.interswitch.browser import gateway_net_amt
[9914]89from waeup.aaue.interfaces import MessageFactory as _
[8911]90
[14306]91grok.context(IKofaObject)  # Make IKofaObject the default context
92
[13795]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
[13351]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
[14661]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 """
[14674]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.
[14677]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.
[14674]166 <br /><br />
[14677]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.)
[14674]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
[14677]175 centers will be penalized and the exam of such a student will be cancelled.
[14674]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>
[14661]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
[14084]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
[14104]219        form_fields = form_fields.omit('def_adm')
[14084]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')
[14104]234        form_fields = form_fields.omit('def_adm')
[14084]235        return form_fields
236
[13362]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:
[14298]248            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
[13362]249            'clearance_locked', 'clr_code', 'officer_comment',
[14104]250            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
251        form_fields = form_fields.omit('def_adm')
[13362]252        return form_fields
253
[11846]254class CustomStartClearancePage(StartClearancePage):
[13360]255    with_ac = False
[11846]256
[13351]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
[8911]267class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
268    """ Page to view an online payment ticket
269    """
270    grok.context(ICustomStudentOnlinePayment)
[9853]271    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]272        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]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
[14527]284    ALUMNI_PAYMENT_CATS =  {
[14296]285        'transcript_local': 'Transcript Fee Local',
286        'transcript_inter': 'Transcript Fee International',
287        }
288
[14533]289    REDUCED_PAYMENT_CATS =  {
[14527]290        'clearance': 'Acceptance Fee',
291        'schoolfee': 'School Fee',
292        }
293
[14986]294    IJMBE_PAYMENT_CATS =  {
295        'clearance': 'Acceptance Fee',
296        'schoolfee': 'School Fee',
[14987]297        'schoolfee_1': 'School Fee (1st instalment)',
[14986]298        'schoolfee_2': 'School Fee (2nd instalment)',
299        }
300
[14296]301    @property
302    def selectable_categories(self):
303        if 'alumni' in self.application_url():
[14527]304            return self.ALUMNI_PAYMENT_CATS.items()
[14573]305        if self.context.student.current_mode in (
[15182]306            'special_pg_ft', 'special_pg_pt', 'found'):
[14533]307            return self.REDUCED_PAYMENT_CATS.items()
[14986]308        if self.context.student.current_mode == 'ijmbe':
309            return sorted(self.IJMBE_PAYMENT_CATS.items())
[14296]310        categories = getUtility(IKofaUtils).SELECTABLE_PAYMENT_CATEGORIES
311        return sorted(categories.items())
312
[13523]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
[13059]322class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
[8911]323    """Deliver a PDF slip of the context.
324    """
325    grok.context(ICustomStudentOnlinePayment)
[9853]326    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]327        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]328    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9496]329    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9914]330
[11625]331    @property
332    def note(self):
[13408]333        p_session = self.context.p_session
[13405]334        try:
[13408]335            academic_session = grok.getSite()['configuration'][str(p_session)]
[13405]336        except KeyError:
337            academic_session = None
[13425]338        text =  '\n\n The Amount Authorized is inclusive of: '
[13512]339        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
340            and academic_session:
[14385]341            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
342            #union_fee = gateway_net_amt(academic_session.union_fee)
[14244]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.
[14385]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
[14244]359                text += ('School Fee, '
[14385]360                         'Student ID Card Fee, '
361                         'Student Union Dues, '
362                         'Student Welfare Assurance Fee and ')
[14244]363            else:
[14385]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
[14244]370                text += ('School Fee, '
[14385]371                         'Student Union Dues, '
372                         'Student Welfare Assurance Fee and ')
[13410]373        elif self.context.p_category in (
374            'clearance_incl', 'clearance_medical_incl') and academic_session:
[14385]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
[13437]383            text += ('Acceptance Fee, '
[14385]384                     'Matriculation Gown Fee, '
385                     'Lapel/File Fee and ')
[11625]386
[14385]387        #return text + '250.0 Naira Transaction Charge.'
388
389        return text + 'Transaction Charge.'
390
[14534]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
[14981]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
[9914]438class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
439    """ Page to display student study levels
440    """
441    grok.context(ICustomStudentStudyLevel)
[10480]442    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[14206]443        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
[9914]444    form_fields[
445        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
446
[14000]447    @property
448    def show_results(self):
449        isStudent = getattr(
450            self.request.principal, 'user_type', None) == 'student'
[14944]451        try:
452            show_results = grok.getSite()[
453                'configuration'][str(self.context.level_session)].show_results
454        except KeyError:
[14000]455            return False
[14944]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
[14000]461        return True
462
[13834]463class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
464    """ Page to edit the student study level data
465    """
466    grok.context(ICustomStudentStudyLevel)
467
[14534]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
[13834]475class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
476    """ Page to edit the student study level data by students
477    """
478    grok.context(ICustomStudentStudyLevel)
479
[13059]480class CustomExportPDFCourseRegistrationSlip(
481    NigeriaExportPDFCourseRegistrationSlip):
[9914]482    """Deliver a PDF slip of the context.
483    """
484    grok.context(ICustomStudentStudyLevel)
485    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[10102]486        'level_session', 'level_verdict',
[14206]487        'validated_by', 'validation_date', 'gpa', 'level',
488        'imported_gpa', 'imported_cgpa')
[9914]489
[10269]490    omit_fields = ('password', 'suspended', 'suspended_comment',
[10689]491        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
[13713]492        'department', 'current_mode', 'current_level', 'flash_notice')
[10269]493
[14000]494    @property
495    def show_results(self):
496        isStudent = getattr(
497            self.request.principal, 'user_type', None) == 'student'
[14944]498        try:
499            show_results = grok.getSite()[
500                'configuration'][str(self.context.level_session)].show_results
501        except KeyError:
[14302]502            return False
[14944]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
[14302]508        return True
[14000]509
[13038]510    def update(self):
511        if self.context.student.state != REGISTERED \
[13051]512            and self.context.student.current_level == self.context.level:
[13038]513            self.flash(_('Forbidden'), type="warning")
514            self.redirect(self.url(self.context))
[14000]515            return
[13038]516
[9914]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 = ''
[13788]524        if self.context.student.is_postgrad:
525            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
526        elif self.context.student.current_mode.endswith('_pt'):
[9914]527            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
[13866]528        line1 = translate(_('Course Registration Slip'),
529            target_language=portal_language) \
[9914]530            + ' %s' % level_title
[13866]531        line2 = translate(_('Session'),
532            target_language=portal_language) \
[9914]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
[13866]539        return translate(_('Units Registered'), target_language=portal_language)
[9914]540
541    def _signatures(self):
[13647]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>')],
[13946]550                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
[13647]551                [('', _('Director\'s Signature'))]
552                )
553        if self.context.student.current_mode in (
554            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
[13649]555            return ([_('Academic Adviser\'s Signature'),
556                _('Faculty Officer\'s Signature'),
557                _('Student\'s Signature')],)
558
[13647]559        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
560            return (
[13676]561                [('I declare that all items of information supplied above are correct:' ,
[13680]562                    _('Student\'s Signature'), '<br>')],
[13676]563                [('We approved the above registration:',
[13680]564                    _('Major Supervisor (Name / Signature)'), '')],
565                [('', _('Co-Supervisor (Name / Signature)'), '')],
[13676]566                [('', _('Head of Department'), '<br>')],
567                [('The student has satisfied the conditions for renewal of '
568                  'registration for graduate school programme in this university:',
[13680]569                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
570                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
[13647]571                )
572        return None
[9914]573
[13647]574
[9914]575    def render(self):
576        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]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)
[14650]581        CC = translate(_('Cat.'), target_language=portal_language)
[14000]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)
[13866]586        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
[9914]587            target_language=portal_language)
588        studentview = StudentBasePDFFormPage(self.context.student,
589            self.request, self.omit_fields)
590        students_utils = getUtility(IStudentsUtils)
[10442]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))
[14000]599            if self.show_results:
600                tableheader.append([(Code,'code', 2.0),
601                                   (Title,'title', 7),
[14650]602                                   (Cred, 'credits', 1.4),
603                                   (CC, 'course_category', 1.2),
[14000]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),
[14650]613                                   (CC, 'course_category', 1.2),
[14000]614                                   (Signature, 'dummy', 3),
615                                   ])
[9914]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,
[10442]625            tableheader=tableheader,
626            tabledata=tabledata,
[9914]627            signatures=self._signatures(),
[10269]628            topMargin=topMargin,
629            omit_fields=self.omit_fields
[9914]630            )
[10566]631
[14165]632class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
633    """ Page to display the student's transcript.
634    """
635    grok.require('waeup.viewStudent')
636
[13059]637class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
[10566]638    """Deliver a PDF slip of the context.
639    """
[14315]640#    grok.require('waeup.viewStudent')
[10566]641
[14267]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
[10566]649    def _sigsInFooter(self):
650        return []
651
652    def _signatures(self):
653        return ([(
[14999]654            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
[11555]655            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
[10922]656
[13834]657    def render(self):
658        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]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)
[13834]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),
[14136]673                         (Score, 'total_score', 1.5),
[13834]674                         (Grade, 'grade', 1.5),
675                         ]
676
[15165]677        pdfstream = students_utils.renderPDFTranscript(
[13834]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(),
[15165]684            digital_sigs=self._digital_sigs(),
685            save_file=self._save_file(),
[13834]686            )
[15165]687        if not pdfstream:
688            self.redirect(self.url(self.context.student))
689            return
690        return pdfstream
[13834]691
[13059]692class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
[10922]693    """Deliver a PDF Admission slip.
694    """
695
696    @property
697    def label(self):
698        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]699        return translate(_('e-Admission Slip \n'),
700            target_language=portal_language) \
[10922]701            + ' %s' % self.context.display_fullname
[11597]702
[13059]703class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
[11606]704    """Deliver a PDF slip of the context.
705    """
706
707    @property
708    def label(self):
709        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[14114]710        return translate(_('Verification/Clearance Slip\n'),
[13866]711            target_language=portal_language) \
[11606]712            + ' %s' % self.context.display_fullname
713
[14084]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')
[14104]724        form_fields = form_fields.omit('def_adm')
[14084]725        return form_fields
726
[11597]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))
[11602]741            self.context.writeLogMessage(self, '%s assigned' % mnumber)
[11597]742        self.redirect(self.url(self.context))
743        return
744
745    def render(self):
[11607]746        return
747
[13059]748class ExportPDFMatricNumberSlip(UtilityView, grok.View):
[11607]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')
[13713]758    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
[11607]759
760    @property
[13489]761    def title(self):
762        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]763        return translate(_('Matriculation Number'), 'waeup.kofa',
[13489]764            target_language=portal_language)
765
766    @property
[11607]767    def label(self):
768        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]769        return translate(_('Matriculation Number Slip\n'),
770            target_language=portal_language) \
[11607]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)
[11609]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.')
[11607]783        return students_utils.renderPDFAdmissionLetter(self,
784            self.context.student, omit_fields=self.omit_fields,
[13353]785            pre_text=pre_text, post_text='')
786
[13489]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')
[13713]797    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
798                   'certificate', 'flash_notice')
[13489]799
800    @property
801    def title(self):
802        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]803        return translate(_('Personal Data'), 'waeup.kofa',
[13489]804            target_language=portal_language)
805
806    @property
807    def label(self):
808        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]809        return translate(_('Personal Data Slip\n'),
810            target_language=portal_language) \
[13489]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
[13462]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
[13353]827class CustomBedTicketAddPage(BedTicketAddPage):
[13360]828    with_ac = False
[13380]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.
[13770]834    """
835
836class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
837    """ Page to display course tickets
838    """
839
[14453]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
[13770]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
[15205]868    form_fields['ticket_session'].for_display = True
[13770]869
870class CustomEditScoresPage(EditScoresPage):
871    """Page that filters and lists students.
872    """
873    grok.template('editscorespage')
874
[14700]875    def _searchCatalog(self, session):
876        cat = queryUtility(ICatalog, name='coursetickets_catalog')
877        coursetickets = cat.searchResults(
878            session=(session, session),
879            code=(self.context.code, self.context.code)
880            )
881        try:
882            score_editing_enabled = grok.getSite()[
883                'configuration'][str(session)].score_editing_enabled
884        except KeyError:
885            return []
[14707]886        coursetickets_list = [courseticket for courseticket in coursetickets
887            if courseticket.student.current_mode in score_editing_enabled]
[14700]888        return coursetickets_list
[13937]889
890    def _extract_uploadfile(self, uploadfile):
891        """Get a mapping of student-ids to scores.
892
893        The mapping is constructed by reading contents from `uploadfile`.
894
895        We expect uploadfile to be a regular CSV file with columns
896        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
897        """
898        result = dict()
899        data = StringIO(uploadfile.read())  # ensure we have something seekable
900        reader = csv.DictReader(data)
901        for row in reader:
902            if not ('student_id' in row and 'score' in row and 'ca' in row):
903                continue
904            result[row['student_id']] = (row['score'], row['ca'])
905        return result
906
[14288]907    def _update_scores(self, form):
[13937]908        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
[14288]909        error = ''
[13937]910        if 'UPDATE_FILE' in form:
911            if form['uploadfile']:
912                try:
913                    formvals = self._extract_uploadfile(form['uploadfile'])
914                except:
915                    self.flash(
916                        _('Uploaded file contains illegal data. Ignored'),
917                        type="danger")
[14288]918                    return False
[13937]919            else:
920                self.flash(
921                    _('No file provided.'), type="danger")
[14288]922                return False
[13937]923        else:
924            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
[14149]925        for ticket in self.editable_tickets:
[13937]926            ticket_error = False
927            score = ticket.score
928            ca = ticket.ca
929            sid = ticket.student.student_id
930            if formvals[sid][0] == '':
931                score = None
932            if formvals[sid][1] == '':
933                ca = None
934            try:
935                if formvals[sid][0]:
936                    score = int(formvals[sid][0])
937                if formvals[sid][1]:
938                    ca = int(formvals[sid][1])
939            except ValueError:
940                error += '%s, ' % ticket.student.display_fullname
941                ticket_error = True
942            if not ticket_error and ticket.score != score:
[14113]943                try:
944                    ticket.score = score
945                except TooBig:
946                    error += '%s, ' % ticket.student.display_fullname
947                    ticket_error = True
948                    pass
[13937]949                ticket.student.__parent__.logger.info(
950                    '%s - %s %s/%s score updated (%s)' %
951                    (ob_class, ticket.student.student_id,
952                     ticket.level, ticket.code, score))
953            if not ticket_error and ticket.ca != ca:
[14113]954                try:
955                    ticket.ca = ca
956                except TooBig:
957                    error += '%s, ' % ticket.student.display_fullname
958                    pass
[13937]959                ticket.student.__parent__.logger.info(
960                    '%s - %s %s/%s ca updated (%s)' %
961                    (ob_class, ticket.student.student_id,
962                     ticket.level, ticket.code, ca))
963        if error:
964            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
[14113]965              % error.strip(', ')), type="danger")
[14288]966        return True
967
968class EditPreviousSessionScoresPage(CustomEditScoresPage):
969
970    grok.name('edit_prev_scores')
971
972    def update(self,  *args, **kw):
973        form = self.request.form
974        self.current_academic_session = grok.getSite()[
975            'configuration'].current_academic_session
976        if self.context.__parent__.__parent__.score_editing_disabled:
977            self.flash(_('Score editing disabled.'), type="warning")
978            self.redirect(self.url(self.context))
[13939]979            return
[14288]980        if not self.current_academic_session:
981            self.flash(_('Current academic session not set.'), type="warning")
982            self.redirect(self.url(self.context))
983            return
984        previous_session = self.current_academic_session - 1
985        self.session_title = academic_sessions_vocab.getTerm(
986            previous_session).title
987        self.tickets = self._searchCatalog(previous_session)
988        if not self.tickets:
989            self.flash(_('No student found.'), type="warning")
990            self.redirect(self.url(self.context))
991            return
992        self.editable_tickets = [
993            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
994        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
995            return
996        if not self.editable_tickets:
997            return
998        success = self._update_scores(form)
999        if success:
1000            self.flash(_('You successfully updated course results.'))
[13900]1001        return
1002
1003class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1004    """Deliver a PDF slip of course tickets for a lecturer.
1005    """
1006
[14315]1007    def data(self, session):
[14704]1008        #site = grok.getSite()
[13900]1009        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1010        coursetickets = cat.searchResults(
1011            session=(session, session),
1012            code=(self.context.code, self.context.code)
1013            )
[14707]1014        # Apply filter
1015        try:
1016            score_editing_enabled = grok.getSite()[
1017                'configuration'][str(session)].score_editing_enabled
1018            coursetickets_filtered = [courseticket
1019                for courseticket in coursetickets
[15006]1020                if courseticket.student.current_mode in score_editing_enabled
1021                and courseticket.total_score is not None]
[14707]1022        except KeyError:
1023            coursetickets_filtered = coursetickets
[14149]1024        # In AAUE only editable tickets can be printed
1025        editable_tickets = [
[14707]1026            ticket for ticket in coursetickets_filtered
1027            if ticket.editable_by_lecturer]
[13963]1028        header = [[_(''),
[15006]1029                   _('Student Id'),
[13963]1030                   _('Matric No.'),
[14703]1031                   #_('Reg. No.'),
1032                   #_('Fullname'),
1033                   #_('Status'),
1034                   #_('Course of\nStudies'),
[14704]1035                   _('Department'),
[13900]1036                   _('Level'),
[15201]1037                   _(' CA  '),
1038                   _('Exam\nScore'),
[13964]1039                   _('Total '),
[13963]1040                   _('Grade'),
1041                   ],]
[14288]1042        sorted_tickets = sorted(editable_tickets,
[15199]1043            key=lambda ticket: ticket.student.depcode
1044                               + ticket.student.faccode
[14704]1045                               + ticket.student.matric_number)
[14288]1046        no = 1
[13907]1047        tickets = []
[14315]1048        passed = 0
1049        failed = 0
[14149]1050        # In AAUE only editable tickets can be printed
[14288]1051        for ticket in sorted_tickets:
[15006]1052            total = ticket.total_score
1053            grade = ticket._getGradeWeightFromScore[0]
1054            if grade in ('F', '-'):
1055                failed += 1
[13963]1056            else:
[15006]1057                passed += 1
[13964]1058            fullname = textwrap.fill(ticket.student.display_fullname, 30)
[14704]1059            #deptitle = site['faculties'][ticket.student.faccode][
1060            #    ticket.student.depcode].longtitle
[13963]1061            row = [no,
[15006]1062                  ticket.student.student_id,
[13963]1063                  ticket.student.matric_number,
[14703]1064                  #ticket.student.reg_number,
1065                  #fullname,
1066                  #ticket.student.translated_state,
1067                  #ticket.student.certcode,
[14704]1068                  ticket.student.faccode + ' / ' + ticket.student.depcode,
[13900]1069                  ticket.level,
[15201]1070                  ticket.ca,
1071                  ticket.score,
[13963]1072                  total,
[13964]1073                  grade,
[13963]1074                  ]
[13907]1075            tickets.append(row)
[13963]1076            no += 1
[14317]1077        total = passed + failed
[14320]1078        passed_perc = 0
1079        failed_perc = 0
[14317]1080        if total:
[14320]1081            passed_perc = 100 * passed / total
1082            failed_perc = 100 * failed / total
[14710]1083        dep = self.context.__parent__.__parent__.longtitle
1084        fac = self.context.__parent__.__parent__.__parent__.longtitle
[14320]1085        return header + tickets, [
[14710]1086            dep, fac, total, passed, passed_perc, failed, failed_perc]
[14288]1087
[14703]1088    def render(self):
1089        session = grok.getSite()['configuration'].current_academic_session
1090        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1091                     if i['local_role'] == 'waeup.local.Lecturer']
1092        lecturers =  ', '.join(lecturers)
1093        students_utils = getUtility(IStudentsUtils)
1094        # only orientation is different
1095        return students_utils.renderPDFCourseticketsOverview(
[15198]1096            self, session, self.data(session), lecturers, '', 45)
[14703]1097
[14288]1098class DownloadPreviousSessionScoresView(DownloadScoresView):
1099    """View that exports scores.
1100    """
1101    grok.name('download_prev_scores')
1102
1103    def update(self):
1104        self.current_academic_session = grok.getSite()[
1105            'configuration'].current_academic_session
1106        if self.context.__parent__.__parent__.score_editing_disabled:
1107            self.flash(_('Score editing disabled.'), type="warning")
1108            self.redirect(self.url(self.context))
1109            return
1110        if not self.current_academic_session:
1111            self.flash(_('Current academic session not set.'), type="warning")
1112            self.redirect(self.url(self.context))
1113            return
1114        site = grok.getSite()
1115        exporter = getUtility(ICSVExporter, name='lecturer')
1116        self.csv = exporter.export_filtered(site, filepath=None,
1117                                 catalog='coursetickets',
1118                                 session=self.current_academic_session-1,
1119                                 level=None,
1120                                 code=self.context.code)
[14306]1121        return
1122
1123class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1124    """Captcha'd request password page for students.
1125    """
1126    grok.name('alumni_requestpw')
1127    grok.require('waeup.Anonymous')
1128    grok.template('alumni_requestpw')
1129    form_fields = grok.AutoFields(IStudentRequestPW).select(
1130        'lastname','number','email')
1131    label = _('Search student record and send password for first-time login')
1132
1133    def _redirect_no_student(self):
1134        self.flash(_('No student record found.'), type="warning")
1135        self.redirect(self.application_url() + '/applicants/trans2017/register')
[14288]1136        return
Note: See TracBrowser for help on using the repository browser.