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

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

Add 'found'.

Replace 'extension' by 'Extension'.

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