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

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

Use and show course categories.

  • Property svn:keywords set to Id
File size: 37.8 KB
Line 
1## $Id: browser.py 14650 2017-03-23 06:09:02Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
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        CC = translate(_('Cat.'), target_language=portal_language)
458        if self.show_results:
459            Score = translate(_('Score'), target_language=portal_language)
460            #CA = translate(_('CA'), target_language=portal_language)
461            Grade = translate(_('Grade'), target_language=portal_language)
462        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
463            target_language=portal_language)
464        studentview = StudentBasePDFFormPage(self.context.student,
465            self.request, self.omit_fields)
466        students_utils = getUtility(IStudentsUtils)
467
468        tabledata = []
469        tableheader = []
470        contenttitle = []
471        for i in range(1,7):
472            tabledata.append(sorted(
473                [value for value in self.context.values() if value.semester == i],
474                key=lambda value: str(value.semester) + value.code))
475            if self.show_results:
476                tableheader.append([(Code,'code', 2.0),
477                                   (Title,'title', 7),
478                                   (Cred, 'credits', 1.4),
479                                   (CC, 'course_category', 1.2),
480                                   (Score, 'score', 1.4),
481                                   #(CA, 'ca', 1.4),
482                                   (Grade, 'grade', 1.4),
483                                   (Signature, 'dummy', 3),
484                                   ])
485            else:
486                tableheader.append([(Code,'code', 2.0),
487                                   (Title,'title', 7),
488                                   (Cred, 'credits', 1.5),
489                                   (CC, 'course_category', 1.2),
490                                   (Signature, 'dummy', 3),
491                                   ])
492        if len(self.label.split('\n')) == 3:
493            topMargin = 1.9
494        elif len(self.label.split('\n')) == 2:
495            topMargin = 1.7
496        else:
497            topMargin = 1.5
498        return students_utils.renderPDF(
499            self, 'course_registration_slip.pdf',
500            self.context.student, studentview,
501            tableheader=tableheader,
502            tabledata=tabledata,
503            signatures=self._signatures(),
504            topMargin=topMargin,
505            omit_fields=self.omit_fields
506            )
507
508class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
509    """ Page to display the student's transcript.
510    """
511    grok.require('waeup.viewStudent')
512
513class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
514    """Deliver a PDF slip of the context.
515    """
516#    grok.require('waeup.viewStudent')
517
518    note = _("""
519<br /><br /><br /><br />
520<font size='10'>
521<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
522</font>
523""")
524
525    def _sigsInFooter(self):
526        return []
527
528    def _signatures(self):
529        return ([(
530            'Mrs. Uniamikogbo, S.O., mnim, manupa <br /> Prin. Asst Registrar  <br /> '
531            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
532
533    def render(self):
534        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
535        Term = translate(_('Sem.'), target_language=portal_language)
536        Code = translate(_('Code'), target_language=portal_language)
537        Title = translate(_('Title'), target_language=portal_language)
538        Cred = translate(_('Credits'), target_language=portal_language)
539        Score = translate(_('Score'), target_language=portal_language)
540        Grade = translate(_('Grade'), target_language=portal_language)
541        studentview = StudentBasePDFFormPage(self.context.student,
542            self.request, self.omit_fields)
543        students_utils = getUtility(IStudentsUtils)
544
545        tableheader = [(Code,'code', 2.5),
546                         (Title,'title', 7),
547                         (Term, 'semester', 1.5),
548                         (Cred, 'credits', 1.5),
549                         (Score, 'total_score', 1.5),
550                         (Grade, 'grade', 1.5),
551                         ]
552
553        return students_utils.renderPDFTranscript(
554            self, 'transcript.pdf',
555            self.context.student, studentview,
556            omit_fields=self.omit_fields,
557            tableheader=tableheader,
558            signatures=self._signatures(),
559            sigs_in_footer=self._sigsInFooter(),
560            note = self.note,
561            no_passport=True
562            )
563
564class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
565    """Deliver a PDF Admission slip.
566    """
567
568    @property
569    def label(self):
570        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
571        return translate(_('e-Admission Slip \n'),
572            target_language=portal_language) \
573            + ' %s' % self.context.display_fullname
574
575class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
576    """Deliver a PDF slip of the context.
577    """
578
579    @property
580    def label(self):
581        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
582        return translate(_('Verification/Clearance Slip\n'),
583            target_language=portal_language) \
584            + ' %s' % self.context.display_fullname
585
586    @property
587    def form_fields(self):
588        if self.context.is_postgrad:
589            form_fields = grok.AutoFields(
590                ICustomPGStudentClearance).omit('clearance_locked')
591        else:
592            form_fields = grok.AutoFields(
593                ICustomUGStudentClearance).omit('clearance_locked')
594        if not getattr(self.context, 'officer_comment'):
595            form_fields = form_fields.omit('officer_comment')
596        form_fields = form_fields.omit('def_adm')
597        return form_fields
598
599class StudentGetMatricNumberPage(UtilityView, grok.View):
600    """ Construct and set the matriculation number.
601    """
602    grok.context(IStudent)
603    grok.name('get_matric_number')
604    grok.require('waeup.viewStudent')
605
606    def update(self):
607        students_utils = getUtility(IStudentsUtils)
608        msg, mnumber = students_utils.setMatricNumber(self.context)
609        if msg:
610            self.flash(msg, type="danger")
611        else:
612            self.flash(_('Matriculation number %s assigned.' % mnumber))
613            self.context.writeLogMessage(self, '%s assigned' % mnumber)
614        self.redirect(self.url(self.context))
615        return
616
617    def render(self):
618        return
619
620class ExportPDFMatricNumberSlip(UtilityView, grok.View):
621    """Deliver a PDF notification slip.
622    """
623    grok.context(ICustomStudent)
624    grok.name('matric_number_slip.pdf')
625    grok.require('waeup.viewStudent')
626    prefix = 'form'
627
628    form_fields = grok.AutoFields(ICustomStudent).select(
629        'student_id', 'matric_number')
630    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
631
632    @property
633    def title(self):
634        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
635        return translate(_('Matriculation Number'), 'waeup.kofa',
636            target_language=portal_language)
637
638    @property
639    def label(self):
640        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
641        return translate(_('Matriculation Number Slip\n'),
642            target_language=portal_language) \
643            + ' %s' % self.context.display_fullname
644
645    def render(self):
646        if self.context.state not in (PAID,) or not self.context.is_fresh \
647            or not self.context.matric_number:
648            self.flash('Not allowed.', type="danger")
649            self.redirect(self.url(self.context))
650            return
651        students_utils = getUtility(IStudentsUtils)
652        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
653                     'payments have been received and your matriculation ' +
654                     'number generated with details as follows.')
655        return students_utils.renderPDFAdmissionLetter(self,
656            self.context.student, omit_fields=self.omit_fields,
657            pre_text=pre_text, post_text='')
658
659class ExportPersonalDataSlip(UtilityView, grok.View):
660    """Deliver a PDF notification slip.
661    """
662    grok.context(ICustomStudent)
663    grok.name('personal_data_slip.pdf')
664    grok.require('waeup.viewStudent')
665    prefix = 'form'
666    note = None
667
668    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
669    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
670                   'certificate', 'flash_notice')
671
672    @property
673    def title(self):
674        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
675        return translate(_('Personal Data'), 'waeup.kofa',
676            target_language=portal_language)
677
678    @property
679    def label(self):
680        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
681        return translate(_('Personal Data Slip\n'),
682            target_language=portal_language) \
683            + ' %s' % self.context.display_fullname
684
685    def render(self):
686        studentview = StudentBasePDFFormPage(self.context.student,
687            self.request, self.omit_fields)
688        students_utils = getUtility(IStudentsUtils)
689        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
690            self.context.student, studentview, note=self.note,
691            omit_fields=self.omit_fields)
692
693class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
694    """ Page to manage bed tickets.
695    This manage form page is for both students and students officers.
696    """
697    with_hostel_selection = True
698
699class CustomBedTicketAddPage(BedTicketAddPage):
700    with_ac = False
701
702class CustomStudentFilesUploadPage(StudentFilesUploadPage):
703    """ View to upload files by student. Inherit from same class in
704    base package, not from kofacustom.nigeria which
705    requires that no application slip exists.
706    """
707
708class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
709    """ Page to display course tickets
710    """
711
712    @property
713    def show_results(self):
714        isStudent = getattr(
715            self.request.principal, 'user_type', None) == 'student'
716        if isStudent:
717            return False
718        return True
719
720    @property
721    def form_fields(self):
722        if self.show_results:
723            return grok.AutoFields(ICustomCourseTicket)
724        else:
725            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
726
727class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
728    """ Page to manage course tickets
729    """
730    form_fields = grok.AutoFields(ICustomCourseTicket)
731    form_fields['title'].for_display = True
732    form_fields['fcode'].for_display = True
733    form_fields['dcode'].for_display = True
734    form_fields['semester'].for_display = True
735    form_fields['passmark'].for_display = True
736    form_fields['credits'].for_display = True
737    form_fields['mandatory'].for_display = False
738    form_fields['automatic'].for_display = True
739    form_fields['carry_over'].for_display = True
740
741class CustomEditScoresPage(EditScoresPage):
742    """Page that filters and lists students.
743    """
744    grok.template('editscorespage')
745
746
747    def _extract_uploadfile(self, uploadfile):
748        """Get a mapping of student-ids to scores.
749
750        The mapping is constructed by reading contents from `uploadfile`.
751
752        We expect uploadfile to be a regular CSV file with columns
753        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
754        """
755        result = dict()
756        data = StringIO(uploadfile.read())  # ensure we have something seekable
757        reader = csv.DictReader(data)
758        for row in reader:
759            if not ('student_id' in row and 'score' in row and 'ca' in row):
760                continue
761            result[row['student_id']] = (row['score'], row['ca'])
762        return result
763
764    def _update_scores(self, form):
765        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
766        error = ''
767        if 'UPDATE_FILE' in form:
768            if form['uploadfile']:
769                try:
770                    formvals = self._extract_uploadfile(form['uploadfile'])
771                except:
772                    self.flash(
773                        _('Uploaded file contains illegal data. Ignored'),
774                        type="danger")
775                    return False
776            else:
777                self.flash(
778                    _('No file provided.'), type="danger")
779                return False
780        else:
781            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
782        for ticket in self.editable_tickets:
783            ticket_error = False
784            score = ticket.score
785            ca = ticket.ca
786            sid = ticket.student.student_id
787            if formvals[sid][0] == '':
788                score = None
789            if formvals[sid][1] == '':
790                ca = None
791            try:
792                if formvals[sid][0]:
793                    score = int(formvals[sid][0])
794                if formvals[sid][1]:
795                    ca = int(formvals[sid][1])
796            except ValueError:
797                error += '%s, ' % ticket.student.display_fullname
798                ticket_error = True
799            if not ticket_error and ticket.score != score:
800                try:
801                    ticket.score = score
802                except TooBig:
803                    error += '%s, ' % ticket.student.display_fullname
804                    ticket_error = True
805                    pass
806                ticket.student.__parent__.logger.info(
807                    '%s - %s %s/%s score updated (%s)' %
808                    (ob_class, ticket.student.student_id,
809                     ticket.level, ticket.code, score))
810            if not ticket_error and ticket.ca != ca:
811                try:
812                    ticket.ca = ca
813                except TooBig:
814                    error += '%s, ' % ticket.student.display_fullname
815                    pass
816                ticket.student.__parent__.logger.info(
817                    '%s - %s %s/%s ca updated (%s)' %
818                    (ob_class, ticket.student.student_id,
819                     ticket.level, ticket.code, ca))
820        if error:
821            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
822              % error.strip(', ')), type="danger")
823        return True
824
825class EditPreviousSessionScoresPage(CustomEditScoresPage):
826
827    grok.name('edit_prev_scores')
828
829    def update(self,  *args, **kw):
830        form = self.request.form
831        self.current_academic_session = grok.getSite()[
832            'configuration'].current_academic_session
833        if self.context.__parent__.__parent__.score_editing_disabled:
834            self.flash(_('Score editing disabled.'), type="warning")
835            self.redirect(self.url(self.context))
836            return
837        if not self.current_academic_session:
838            self.flash(_('Current academic session not set.'), type="warning")
839            self.redirect(self.url(self.context))
840            return
841        previous_session = self.current_academic_session - 1
842        self.session_title = academic_sessions_vocab.getTerm(
843            previous_session).title
844        self.tickets = self._searchCatalog(previous_session)
845        if not self.tickets:
846            self.flash(_('No student found.'), type="warning")
847            self.redirect(self.url(self.context))
848            return
849        self.editable_tickets = [
850            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
851        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
852            return
853        if not self.editable_tickets:
854            return
855        success = self._update_scores(form)
856        if success:
857            self.flash(_('You successfully updated course results.'))
858        return
859
860class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
861    """Deliver a PDF slip of course tickets for a lecturer.
862    """
863
864    def data(self, session):
865        cat = queryUtility(ICatalog, name='coursetickets_catalog')
866        coursetickets = cat.searchResults(
867            session=(session, session),
868            code=(self.context.code, self.context.code)
869            )
870        # In AAUE only editable tickets can be printed
871        editable_tickets = [
872            ticket for ticket in coursetickets if ticket.editable_by_lecturer]
873        header = [[_(''),
874                   _('Matric No.'),
875                   _('Reg. No.'),
876                   _('Fullname'),
877                   _('Status'),
878                   _('Course of\nStudies'),
879                   _('Level'),
880                   _('Exam\nScore'),
881                   _(' CA  '),
882                   _('Total '),
883                   _('Grade'),
884                   ],]
885        sorted_tickets = sorted(editable_tickets,
886            key=lambda ticket: ticket.student.certcode +
887                ticket.student.display_fullname + str(ticket.level))
888        no = 1
889        tickets = []
890        passed = 0
891        failed = 0
892        # In AAUE only editable tickets can be printed
893        for ticket in sorted_tickets:
894            if ticket.total_score is None:
895                total = 'n/a'
896                grade = 'n/a'
897            else:
898                total = ticket.total_score
899                grade = ticket._getGradeWeightFromScore[0]
900                if grade in ('F', '-'):
901                    failed += 1
902                else:
903                    passed += 1
904            fullname = textwrap.fill(ticket.student.display_fullname, 30)
905            row = [no,
906                  ticket.student.matric_number,
907                  ticket.student.reg_number,
908                  fullname,
909                  ticket.student.translated_state,
910                  ticket.student.certcode,
911                  ticket.level,
912                  ticket.ca,
913                  ticket.score,
914                  total,
915                  grade,
916                  ]
917            tickets.append(row)
918            no += 1
919        total = passed + failed
920        passed_perc = 0
921        failed_perc = 0
922        if total:
923            passed_perc = 100 * passed / total
924            failed_perc = 100 * failed / total
925        return header + tickets, [
926            total, passed, passed_perc, failed, failed_perc]
927
928class DownloadPreviousSessionScoresView(DownloadScoresView):
929    """View that exports scores.
930    """
931    grok.name('download_prev_scores')
932
933    def update(self):
934        self.current_academic_session = grok.getSite()[
935            'configuration'].current_academic_session
936        if self.context.__parent__.__parent__.score_editing_disabled:
937            self.flash(_('Score editing disabled.'), type="warning")
938            self.redirect(self.url(self.context))
939            return
940        if not self.current_academic_session:
941            self.flash(_('Current academic session not set.'), type="warning")
942            self.redirect(self.url(self.context))
943            return
944        site = grok.getSite()
945        exporter = getUtility(ICSVExporter, name='lecturer')
946        self.csv = exporter.export_filtered(site, filepath=None,
947                                 catalog='coursetickets',
948                                 session=self.current_academic_session-1,
949                                 level=None,
950                                 code=self.context.code)
951        return
952
953class AlumniRequestPasswordPage(StudentRequestPasswordPage):
954    """Captcha'd request password page for students.
955    """
956    grok.name('alumni_requestpw')
957    grok.require('waeup.Anonymous')
958    grok.template('alumni_requestpw')
959    form_fields = grok.AutoFields(IStudentRequestPW).select(
960        'lastname','number','email')
961    label = _('Search student record and send password for first-time login')
962
963    def _redirect_no_student(self):
964        self.flash(_('No student record found.'), type="warning")
965        self.redirect(self.application_url() + '/applicants/trans2017/register')
966        return
Note: See TracBrowser for help on using the repository browser.