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

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

Show imported total scores in bold.

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