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

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

Implement ExportExaminationScheduleSlip?.

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