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

Last change on this file since 15220 was 15205, checked in by Henrik Bettermann, 6 years ago

Adjust test and views.

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