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

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

IJMBE students shall see only two payment categories.

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