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

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

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

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