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

Last change on this file since 14651 was 14650, checked in by Henrik Bettermann, 8 years ago

Use and show course categories.

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