## $Id: browser.py 17443 2023-06-20 09:12:19Z henrik $ ## ## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## import grok import csv import textwrap import pytz from cStringIO import StringIO from datetime import datetime from zope.i18n import translate from zope.component import getUtility, queryUtility from zope.schema.interfaces import TooBig, TooSmall from zope.security import checkPermission from zope.catalog.interfaces import ICatalog from zope.formlib.textwidgets import BytesDisplayWidget from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState from waeup.kofa.browser.layout import UtilityView, KofaEditFormPage, jsaction from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget from waeup.kofa.interfaces import ( IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject) from waeup.kofa.students.interfaces import ( IStudentsUtils, IStudent, IStudentRequestPW, IStudentStudyLevel) from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING, GRADUATED from waeup.kofa.students.browser import ( StartClearancePage, StudentBasePDFFormPage, CourseTicketAddFormPage, StudyLevelDisplayFormPage, StudyLevelManageFormPage, StudyLevelEditFormPage, ExportPDFTranscriptSlip, ExportPDFAdmissionSlip, StudentFilesUploadPage, PaymentsManageFormPage, CourseTicketDisplayFormPage, CourseTicketManageFormPage, EditScoresPage, ExportPDFScoresSlip, StudyCourseTranscriptPage, DownloadScoresView, StudentRequestPasswordPage, StudyCourseManageFormPage, UnregisterCoursesView, addCourseTicket, AddStudyLevelFormPage, emit_lock_message ) from kofacustom.nigeria.students.browser import ( NigeriaOnlinePaymentDisplayFormPage, NigeriaOnlinePaymentAddFormPage, NigeriaExportPDFPaymentSlip, NigeriaExportPDFCourseRegistrationSlip, NigeriaStudentPersonalDisplayFormPage, NigeriaStudentPersonalEditFormPage, NigeriaStudentPersonalManageFormPage, NigeriaStudentClearanceDisplayFormPage, NigeriaExportPDFClearanceSlip, NigeriaStudentClearanceManageFormPage, NigeriaStudentClearanceEditFormPage, NigeriaAccommodationManageFormPage, NigeriaAccommodationDisplayFormPage, NigeriaStudentBaseDisplayFormPage, NigeriaStudentBaseManageFormPage, NigeriaBedTicketAddPage ) from waeup.aaue.students.interfaces import ( ICustomStudentOnlinePayment, ICustomStudentStudyLevel, ICustomStudent, ICustomStudentPersonal, ICustomStudentPersonalEdit, ICustomUGStudentClearance, ICustomUGStudentClearanceEdit, ICustomPGStudentClearance, ICustomCourseTicket, ICustomStudentBase, ICustomStudentStudyCourse) from waeup.aaue.interfaces import MessageFactory as _ grok.context(IKofaObject) # Make IKofaObject the default context def translated_values(view): """Translate course ticket attribute values to be displayed on studylevel pages. """ lang = view.request.cookies.get('kofa.language') for value in view.context.values(): value._p_activate() value_dict = dict([i for i in value.__dict__.items()]) value_dict['url'] = view.url(value) value_dict['removable_by_student'] = value.removable_by_student value_dict['mandatory'] = translate(str(value.mandatory), 'zope', target_language=lang) value_dict['carry_over'] = translate(str(value.carry_over), 'zope', target_language=lang) value_dict['outstanding'] = translate(str(value.outstanding), 'zope', target_language=lang) value_dict['automatic'] = translate(str(value.automatic), 'zope', target_language=lang) value_dict['grade'] = value.grade value_dict['weight'] = value.weight value_dict['course_category'] = value.course_category value_dict['total_score'] = value.total_score semester_dict = getUtility(IKofaUtils).SEMESTER_DICT value_dict['semester'] = semester_dict[ value.semester].replace('mester', 'm.') # AAUE specific value_dict['formatted_total_score'] = value.total_score if getattr(value, 'imported_ts', None): value_dict['formatted_total_score'] = "%s" % value.imported_ts yield value_dict class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage): """ Page to display student base data """ form_fields = grok.AutoFields(ICustomStudentBase).omit( 'password', 'suspended', 'suspended_comment', 'flash_notice') form_fields[ 'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage): """ View to manage student base data """ form_fields = grok.AutoFields(ICustomStudentBase).omit( 'student_id', 'adm_code', 'suspended', 'financially_cleared_by', 'financial_clearance_date') class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage): """ Page to display student personal data """ form_fields = grok.AutoFields(ICustomStudentPersonal) form_fields['perm_address'].custom_widget = BytesDisplayWidget form_fields['father_address'].custom_widget = BytesDisplayWidget form_fields['mother_address'].custom_widget = BytesDisplayWidget form_fields['next_kin_address'].custom_widget = BytesDisplayWidget form_fields[ 'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le') class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage): """ Page to edit personal data """ form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated') class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage): """ Page to edit personal data """ form_fields = grok.AutoFields(ICustomStudentPersonal) form_fields['personal_updated'].for_display = True form_fields[ 'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le') class ExportExaminationScheduleSlip(UtilityView, grok.View): """Deliver a examination schedule slip. This form page is available only in Uniben and AAUE. """ grok.context(ICustomStudent) grok.name('examination_schedule_slip.pdf') grok.require('waeup.viewStudent') prefix = 'form' label = u'Examination Schedule Slip' omit_fields = ( 'suspended', 'phone', 'email', 'adm_code', 'suspended_comment', 'date_of_birth', 'current_level', 'current_mode', 'entry_session', 'flash_notice') form_fields = [] @property def note(self): return """

Instructions on CBT Venue Allocation Slip (VAS)

You should login with your student id from Kofa and surname as password. Download and print two copies of this slip and bring them to the allocated CBT examination center. The copies MUST be shown to the invigilators before being admitted into the examination hall.

How to start examination:

* Username: "student id" from Kofa e.g E1000000
* Password: "surname" as shown on this slip in capital letters
* Click the course and click "start exam".

WARNING: Electronic devices (phones, tablets, laptops etc.) are not allowed in the examination hall. Any electronics seized will not be returned. Any student caught charging his/her mobile phone at the CBT centers will be penalized and the exam of such a student will be cancelled. Bags and any foreign materials are not allowed at the venue of the CBT exams. Any omission/other complaints should be reported to the CBT committee through the HoD before the date of examination.

Your examination date, time and venue is scheduled as follows:

%s """ % self.context.flash_notice return def update(self): if not self.context.flash_notice \ or not 'exam' in self.context.flash_notice.lower(): self.flash(_('Forbidden'), type="warning") self.redirect(self.url(self.context)) def render(self): studentview = StudentBasePDFFormPage(self.context.student, self.request, self.omit_fields) students_utils = getUtility(IStudentsUtils) return students_utils.renderPDF( self, 'examination_schedule_slip', self.context.student, studentview, omit_fields=self.omit_fields, note=self.note) class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage): """ Page to display student clearance data """ @property def form_fields(self): if self.context.is_postgrad: form_fields = grok.AutoFields( ICustomPGStudentClearance).omit('clearance_locked') else: form_fields = grok.AutoFields( ICustomUGStudentClearance).omit('clearance_locked') if not getattr(self.context, 'officer_comment'): form_fields = form_fields.omit('officer_comment') else: form_fields['officer_comment'].custom_widget = BytesDisplayWidget form_fields = form_fields.omit('def_adm') return form_fields class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage): """ Page to edit student clearance data """ @property def form_fields(self): if self.context.is_postgrad: form_fields = grok.AutoFields( ICustomPGStudentClearance).omit('clr_code') else: form_fields = grok.AutoFields( ICustomUGStudentClearance).omit('clr_code') form_fields = form_fields.omit('def_adm') return form_fields class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage): """ View to edit student clearance data by student """ @property def form_fields(self): if self.context.is_postgrad: form_fields = grok.AutoFields(ICustomPGStudentClearance).omit( 'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment', 'physical_clearance_date') else: form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit( 'clearance_locked', 'clr_code', 'officer_comment', 'physical_clearance_date', 'date_of_birth', 'nationality', 'lga') form_fields = form_fields.omit('def_adm') return form_fields class CustomStartClearancePage(StartClearancePage): with_ac = False @property def all_required_fields_filled(self): if not self.context.email: return _("Email address is missing."), 'edit_base' if not self.context.phone: return _("Phone number is missing."), 'edit_base' if not self.context.father_name: return _("Personal data form is not properly filled."), 'edit_personal' return class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage): """ Page to view an online payment ticket """ grok.context(ICustomStudentOnlinePayment) form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit( 'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item') form_fields[ 'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') form_fields[ 'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') class CustomPaymentsManageFormPage(PaymentsManageFormPage): """ Page to manage the student payments. This manage form page is for both students and students officers. """ @property def manage_payments_allowed(self): return checkPermission('waeup.manageStudent', self.context) class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip): """Deliver a PDF slip of the context. """ grok.context(ICustomStudentOnlinePayment) form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit( 'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item', 'p_split_data', 'p_combi', 'r_card_num', 'r_payment_link', 'p_option') form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') @property def note(self): p_session = self.context.p_session try: academic_session = grok.getSite()['configuration'][str(p_session)] except KeyError: academic_session = None text = '\nThe Amount Authorized is inclusive of: %s Interswitch surcharge (NGN %d).' % ( getattr(self.context, 'p_split_data'), self.context.gateway_amt) return text class CustomStudyCourseManageFormPage(StudyCourseManageFormPage): """ Page to edit the student study course data """ grok.context(ICustomStudentStudyCourse) @property def form_fields(self): if self.context.is_postgrad: form_fields = grok.AutoFields(ICustomStudentStudyCourse).omit( 'previous_verdict') else: form_fields = grok.AutoFields(ICustomStudentStudyCourse) form_fields['imported_cgpa'].for_display = True return form_fields class CustomUnregisterCoursesView(UnregisterCoursesView): """Unregister course list by student """ grok.context(ICustomStudentStudyLevel) def update(self): if not self.context.__parent__.is_current: emit_lock_message(self) return #try: # academic_session = grok.getSite()['configuration'][ # str(self.context.level_session)] # if self.context.student.is_postgrad: # deadline = academic_session.coursereg_deadline_pg # elif self.context.student.current_mode.startswith('dp'): # deadline = academic_session.coursereg_deadline_dp # elif self.context.student.current_mode in ( # 'ug_pt', 'de_pt', 'de_dsh', 'ug_dsh'): # deadline = academic_session.coursereg_deadline_pt # elif self.context.student.current_mode == 'found': # deadline = academic_session.coursereg_deadline_found # elif self.context.student.current_mode == 'bridge': # deadline = academic_session.coursereg_deadline_bridge # else: # deadline = academic_session.coursereg_deadline #except (TypeError, KeyError): # deadline = None #if deadline and deadline < datetime.now(pytz.utc): # self.flash(_( # "Course registration has ended. " # "Unregistration is disabled."), type="danger") if str(self.context.__parent__.current_level) != self.context.__name__: self.flash(_('This is not your current level.'), type="danger") elif self.context.student.state == REGISTERED: IWorkflowInfo(self.context.student).fireTransition('reset7') message = _('Course list has been unregistered.') self.flash(message) else: self.flash(_('You are in the wrong state.'), type="warning") self.redirect(self.url(self.context)) return class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage): """ Page to display student study levels """ grok.context(ICustomStudentStudyLevel) form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit( 'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa') form_fields[ 'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') @property def translated_values(self): return translated_values(self) @property def show_results(self): isStudent = getattr( self.request.principal, 'user_type', None) == 'student' try: show_results = grok.getSite()[ 'configuration'][str(self.context.level_session)].show_results except KeyError: return False if isStudent and self.context.student.current_mode not in show_results: return False #if isStudent and self.context.student.state != RETURNING \ # and self.context.student.current_level == self.context.level: # return False return True class CustomStudyLevelManageFormPage(StudyLevelManageFormPage): """ Page to edit the student study level data """ grok.context(ICustomStudentStudyLevel) form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit( 'validation_date', 'validated_by', 'total_credits', 'gpa', 'level', 'total_credits_s1', 'total_credits_s2') form_fields['imported_gpa'].for_display = True form_fields['imported_cgpa'].for_display = True class CustomStudyLevelEditFormPage(StudyLevelEditFormPage): """ Page to edit the student study level data by students """ grok.context(ICustomStudentStudyLevel) class StudyLevelRepairFormPage(KofaEditFormPage): """ Page to repair the student study level data by students """ grok.context(IStudentStudyLevel) grok.name('repair') grok.require('waeup.editStudyLevel') grok.template('studylevelrepairpage') pnav = 4 placeholder = _('Enter valid course code') def update(self, ADD=None, course=None): if not self.context.__parent__.is_current \ or self.context.student.studycourse_locked: emit_lock_message(self) return try: studylevel_repair_enabled = grok.getSite()['configuration'][ str(self.context.level_session)].studylevel_repair_enabled except KeyError: emit_lock_message(self) return if not studylevel_repair_enabled: emit_lock_message(self) return super(StudyLevelRepairFormPage, self).update() if ADD is not None: if not course: self.flash(_('No valid course code entered.'), type="warning") return cat = queryUtility(ICatalog, name='courses_catalog') result = cat.searchResults(code=(course, course)) if len(result) != 1: self.flash(_('Course not found.'), type="warning") return course = list(result)[0] addCourseTicket(self, course) return @property def label(self): # Here we know that the cookie has been set lang = self.request.cookies.get('kofa.language') level_title = translate(self.context.level_title, 'waeup.kofa', target_language=lang) return _('Repair course list of ${a}', mapping = {'a':level_title}) @property def translated_values(self): return translated_values(self) def _delCourseTicket(self, **data): form = self.request.form if 'val_id' in form: child_id = form['val_id'] else: self.flash(_('No ticket selected.'), type="warning") self.redirect(self.url(self.context, '@@edit')) return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: # Students are not allowed to remove core tickets if id in self.context and \ self.context[id].removable_by_student: del self.context[id] deleted.append(id) if len(deleted): self.flash(_('Successfully removed: ${a}', mapping = {'a':', '.join(deleted)})) self.context.writeLogMessage( self,'removed: %s at %s' % (', '.join(deleted), self.context.level)) self.redirect(self.url(self.context, u'@@repair')) return @jsaction(_('Remove selected tickets')) def delCourseTicket(self, **data): self._delCourseTicket(**data) return class CustomExportPDFCourseRegistrationSlip( NigeriaExportPDFCourseRegistrationSlip): """Deliver a PDF slip of the context. """ grok.context(ICustomStudentStudyLevel) form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit( 'level_session', 'level_verdict', 'validated_by', 'validation_date', 'gpa', 'level', 'imported_gpa', 'imported_cgpa', 'transcript_remark') omit_fields = ('password', 'suspended', 'suspended_comment', 'phone', 'adm_code', 'sex', 'email', 'date_of_birth', 'department', 'current_mode', 'current_level', 'flash_notice', ) @property def show_results(self): isStudent = getattr( self.request.principal, 'user_type', None) == 'student' try: show_results = grok.getSite()[ 'configuration'][str(self.context.level_session)].show_results except KeyError: return False if isStudent and self.context.student.current_mode not in show_results: return False if isStudent and self.context.student.state != RETURNING \ and self.context.student.current_level == self.context.level: return False return True def update(self): if self.context.student.state != REGISTERED \ and self.context.student.current_level == self.context.level: self.flash(_('Forbidden'), type="warning") self.redirect(self.url(self.context)) return @property def label(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE lang = self.request.cookies.get('kofa.language', portal_language) level_title = translate(self.context.level_title, 'waeup.kofa', target_language=lang) line0 = '' if self.context.student.is_postgrad: line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n' elif self.context.student.current_mode.endswith('_pt'): line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n' line1 = translate(_('Course Registration Slip'), target_language=portal_language) \ + ' %s' % level_title line2 = translate(_('Session'), target_language=portal_language) \ + ' %s' % self.context.getSessionString return '%s%s\n%s' % (line0, line1, line2) @property def title(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Units Registered'), target_language=portal_language) def _signatures(self): if self.context.student.current_mode in ( 'ug_pt', 'de_pt', 'ug_dsh', 'de_dsh', 'found'): return ( [('I have selected the course on the advise of my Head of ' 'Department.
', _('Student\'s Signature'), '
')], [('This student has satisfied the department\'s requirements. ' 'I recommend to approve the course registration.
', _('Head of Department\'s Signature'), '
')], [('' , _('Deputy Registrar\'s Signature'), '
')], [('', _('Director\'s Signature'))] ) if self.context.student.current_mode in ( 'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'): return ([_('Academic Adviser\'s Signature'), _('Faculty Officer\'s Signature'), _('Student\'s Signature')],) if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'): return ( [('I declare that all items of information supplied above are correct:' , _('Student\'s Signature'), '
')], [('We approved the above registration:', _('Major Supervisor (Name / Signature)'), '')], [('', _('Co-Supervisor (Name / Signature)'), '')], [('', _('Head of Department'), '
')], [('The student has satisfied the conditions for renewal of ' 'registration for graduate school programme in this university:', _('Secretary
(School of Postgraduate Studies)'), '')], [('', _('Dean
(School of Postgraduate Studies)'), '')], ) return None def render(self): if not self.context.student.current_mode: self.flash('No certificate assigned.', type="danger") self.redirect(self.url(self.context)) return portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE Sem = translate(_('Term'), target_language=portal_language) Code = translate(_('Code'), target_language=portal_language) Title = translate(_('Title'), target_language=portal_language) Cred = translate(_('Cred.'), target_language=portal_language) CC = translate(_('Cat.'), target_language=portal_language) if self.show_results: TotalScore = translate(_('Total Score'), target_language=portal_language) #CA = translate(_('CA'), target_language=portal_language) Grade = translate(_('Grade'), target_language=portal_language) Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue', target_language=portal_language) studentview = StudentBasePDFFormPage(self.context.student, self.request, self.omit_fields) students_utils = getUtility(IStudentsUtils) tabledata = [] tableheader = [] contenttitle = [] for i in range(1,7): tabledata.append(sorted( [value for value in self.context.values() if value.semester == i and not value.outstanding], key=lambda value: str(value.semester) + value.code)) if self.show_results: tableheader.append([(Code,'code', 2.0), (Title,'title', 7), (Cred, 'credits', 1.4), (CC, 'course_category', 1.2), (TotalScore, 'total_score', 1.4), #(CA, 'ca', 1.4), (Grade, 'grade', 1.4), (Signature, 'dummy', 3), ]) else: tableheader.append([(Code,'code', 2.0), (Title,'title', 7), (Cred, 'credits', 1.5), (CC, 'course_category', 1.2), (Signature, 'dummy', 3), ]) if len(self.label.split('\n')) == 3: topMargin = 1.9 elif len(self.label.split('\n')) == 2: topMargin = 1.7 else: topMargin = 1.5 return students_utils.renderPDF( self, 'course_registration_slip.pdf', self.context.student, studentview, tableheader=tableheader, tabledata=tabledata, signatures=self._signatures(), topMargin=topMargin, omit_fields=self.omit_fields ) class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage): """ Page to display the student's transcript. """ grok.require('waeup.viewStudent') class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip): """Deliver a PDF slip of the context. """ note = _("""



Note: This copy is subject to correction for typographical errors and ratification by the departmental board. """) def _sigsInFooter(self): return [] def _signatures(self): #if self.context.student.state == GRADUATED: # return ([( # 'O.O OHIKHENA (Manupa)
' # 'Principal Asst. Registrar
' # 'Exams, Records and Data Processing Division
' # 'For: Registrar')],) #elif self.context.certificate is not None: # dep = self.context.certificate.__parent__.__parent__ # if dep.officer_1: # return ([(dep.officer_1)],) #return return ([( 'F.O. Omoigui, MANUPA
' 'Principal Assistant Registrar
' 'Exams, Records and Data Processing Division
' 'For: Registrar')],) def render(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE Term = translate(_('Term'), target_language=portal_language) Code = translate(_('Code'), target_language=portal_language) Title = translate(_('Title'), target_language=portal_language) Cred = translate(_('Credits'), target_language=portal_language) Score = translate(_('Score'), target_language=portal_language) Grade = translate(_('Grade'), target_language=portal_language) studentview = StudentBasePDFFormPage(self.context.student, self.request, self.omit_fields) students_utils = getUtility(IStudentsUtils) tableheader = [(Code,'code', 2.5), (Title,'title', 7), (Term, 'semester', 1.5), (Cred, 'credits', 1.5), (Score, 'total_score', 1.5), (Grade, 'grade', 1.5), ] pdfstream = students_utils.renderPDFTranscript( self, 'transcript.pdf', self.context.student, studentview, omit_fields=self.omit_fields, tableheader=tableheader, signatures=self._signatures(), sigs_in_footer=self._sigsInFooter(), digital_sigs=self._digital_sigs(), save_file=self._save_file(), ) if not pdfstream: self.redirect(self.url(self.context.student)) return return pdfstream class ExportPDFTranscriptStudViewSlip(CustomExportPDFTranscriptSlip): """Deliver a PDF slip of the context. """ grok.name('transcript_studview.pdf') def render(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE Term = translate(_('Term'), target_language=portal_language) Code = translate(_('Code'), target_language=portal_language) Title = translate(_('Title'), target_language=portal_language) Cred = translate(_('Credits'), target_language=portal_language) #Score = translate(_('Score'), target_language=portal_language) Grade = translate(_('Grade'), target_language=portal_language) studentview = StudentBasePDFFormPage(self.context.student, self.request, self.omit_fields) students_utils = getUtility(IStudentsUtils) tableheader = [(Code,'code', 2.5), (Title,'title', 7), (Term, 'semester', 1.5), (Cred, 'credits', 1.5), #(Score, 'total_score', 1.5), (Grade, 'grade', 1.5), ] pdfstream = students_utils.renderPDFTranscript( self, 'transcript.pdf', self.context.student, studentview, omit_fields=self.omit_fields, tableheader=tableheader, signatures=self._signatures(), sigs_in_footer=self._sigsInFooter(), digital_sigs=self._digital_sigs(), save_file=self._save_file(), ) if not pdfstream: self.redirect(self.url(self.context.student)) return return pdfstream class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip): """Deliver a PDF Admission slip. """ @property def label(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('e-Admission Slip \n'), target_language=portal_language) \ + ' %s' % self.context.display_fullname class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip): """Deliver a PDF slip of the context. """ @property def label(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Verification/Clearance Slip\n'), target_language=portal_language) \ + ' %s' % self.context.display_fullname @property def form_fields(self): if self.context.is_postgrad: form_fields = grok.AutoFields( ICustomPGStudentClearance).omit('clearance_locked') else: form_fields = grok.AutoFields( ICustomUGStudentClearance).omit('clearance_locked') if not getattr(self.context, 'officer_comment'): form_fields = form_fields.omit('officer_comment') form_fields = form_fields.omit('def_adm') return form_fields class StudentGetMatricNumberPage(UtilityView, grok.View): """ Construct and set the matriculation number. """ grok.context(IStudent) grok.name('get_matric_number') grok.require('waeup.viewStudent') def update(self): students_utils = getUtility(IStudentsUtils) msg, mnumber = students_utils.setMatricNumber(self.context) if msg: self.flash(msg, type="danger") else: self.flash(_('Matriculation number %s assigned.' % mnumber)) self.context.writeLogMessage(self, '%s assigned' % mnumber) self.redirect(self.url(self.context)) return def render(self): return class ExportPDFMatricNumberSlip(UtilityView, grok.View): """Deliver a PDF notification slip. """ grok.context(ICustomStudent) grok.name('matric_number_slip.pdf') grok.require('waeup.viewStudent') prefix = 'form' form_fields = grok.AutoFields(ICustomStudent).select( 'student_id', 'matric_number') omit_fields = ('date_of_birth', 'current_level', 'flash_notice') @property def title(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Matriculation Number'), 'waeup.kofa', target_language=portal_language) @property def label(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Matriculation Number Slip\n'), target_language=portal_language) \ + ' %s' % self.context.display_fullname def render(self): if self.context.state not in (PAID,) or not self.context.is_fresh \ or not self.context.matric_number: self.flash('Not allowed.', type="danger") self.redirect(self.url(self.context)) return students_utils = getUtility(IStudentsUtils) pre_text = _('Congratulations! Your acceptance fee and school fees ' + 'payments have been received and your matriculation ' + 'number generated with details as follows.') return students_utils.renderPDFAdmissionLetter(self, self.context.student, omit_fields=self.omit_fields, pre_text=pre_text, post_text='') class ExportPersonalDataSlip(UtilityView, grok.View): """Deliver a PDF notification slip. """ grok.context(ICustomStudent) grok.name('personal_data_slip.pdf') grok.require('waeup.viewStudent') prefix = 'form' note = None form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated') omit_fields = ('suspended', 'suspended_comment', 'adm_code', 'certificate', 'flash_notice') @property def title(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Personal Data'), 'waeup.kofa', target_language=portal_language) @property def label(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE return translate(_('Personal Data Slip\n'), target_language=portal_language) \ + ' %s' % self.context.display_fullname def render(self): studentview = StudentBasePDFFormPage(self.context.student, self.request, self.omit_fields) students_utils = getUtility(IStudentsUtils) return students_utils.renderPDF(self, 'personal_data_slip.pdf', self.context.student, studentview, note=self.note, omit_fields=self.omit_fields) class CustomAccommodationDisplayFormPage(NigeriaAccommodationDisplayFormPage): """ Page to view bed tickets. """ with_hostel_selection = True class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage): """ Page to manage bed tickets. This manage form page is for both students and students officers. """ with_hostel_selection = True class CustomBedTicketAddPage(NigeriaBedTicketAddPage): with_ac = False class CustomStudentFilesUploadPage(StudentFilesUploadPage): """ View to upload files by student. Inherit from same class in base package, not from kofacustom.nigeria which requires that no application slip exists. """ class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage): """ Page to display course tickets """ @property def show_results(self): isStudent = getattr( self.request.principal, 'user_type', None) == 'student' if isStudent: return False return True @property def form_fields(self): if self.show_results: return grok.AutoFields(ICustomCourseTicket) else: return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca') class CustomCourseTicketManageFormPage(CourseTicketManageFormPage): """ Page to manage course tickets """ form_fields = grok.AutoFields(ICustomCourseTicket) form_fields['title'].for_display = True form_fields['fcode'].for_display = True form_fields['dcode'].for_display = True form_fields['semester'].for_display = True form_fields['passmark'].for_display = True form_fields['credits'].for_display = True form_fields['mandatory'].for_display = False form_fields['automatic'].for_display = True form_fields['carry_over'].for_display = True form_fields['ticket_session'].for_display = True form_fields['imported_ts'].for_display = True class CustomEditScoresPage(EditScoresPage): """Page that filters and lists students. """ grok.template('editscorespage') def _searchCatalog(self, session): cat = queryUtility(ICatalog, name='coursetickets_catalog') coursetickets = cat.searchResults( session=(session, session), code=(self.context.code, self.context.code) ) try: score_editing_enabled = grok.getSite()[ 'configuration'][str(session)].score_editing_enabled except KeyError: return [] coursetickets_list = [courseticket for courseticket in coursetickets if courseticket.student.current_mode in score_editing_enabled] return coursetickets_list def _extract_uploadfile(self, uploadfile): """Get a mapping of student-ids to scores. The mapping is constructed by reading contents from `uploadfile`. We expect uploadfile to be a regular CSV file with columns ``student_id``, ``score``, ``imported_ts`` and ``ca`` (other cols are ignored). """ result = dict() data = StringIO(uploadfile.read()) # ensure we have something seekable reader = csv.DictReader(data) for row in reader: if not ('student_id' in row and 'score' in row and 'ca' in row and 'imported_ts' in row): continue result[row['student_id']] = ( row['score'], row['ca'], row['imported_ts']) return result def _update_scores(self, form): ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '') error = '' if 'UPDATE_FILE' in form: if form['uploadfile']: try: formvals = self._extract_uploadfile(form['uploadfile']) except: self.flash( _('Uploaded file contains illegal data. Ignored'), type="danger") return False else: self.flash( _('No file provided.'), type="danger") return False else: formvals = dict(zip(form['sids'], zip( form['scores'], form['cas'], form['imported_ts']))) for ticket in self.editable_tickets: ticket_error = False score = ticket.score ca = ticket.ca imported_ts = ticket.imported_ts sid = ticket.student.student_id if sid not in formvals: continue if formvals[sid][0] == '': score = None if formvals[sid][1] == '': ca = None if formvals[sid][2] == '': imported_ts = None try: if formvals[sid][0]: score = int(formvals[sid][0]) if formvals[sid][1]: ca = int(formvals[sid][1]) if formvals[sid][2]: imported_ts = int(formvals[sid][2]) except ValueError: error += '%s, ' % ticket.student.display_fullname ticket_error = True if score and ca and not ticket_error and score + ca > 100: error += '%s, ' % ticket.student.display_fullname ticket_error = True if not ticket_error and ticket.score != score: try: ticket.score = score except TooBig: error += '%s, ' % ticket.student.display_fullname ticket_error = True pass ticket.student.__parent__.logger.info( '%s - %s %s/%s score updated (%s)' % (ob_class, ticket.student.student_id, ticket.level, ticket.code, score)) if not ticket_error and ticket.ca != ca: try: ticket.ca = ca except TooBig: error += '%s, ' % ticket.student.display_fullname pass ticket.student.__parent__.logger.info( '%s - %s %s/%s ca updated (%s)' % (ob_class, ticket.student.student_id, ticket.level, ticket.code, ca)) if not ticket_error and ticket.imported_ts != imported_ts: try: ticket.imported_ts = imported_ts except TooBig: error += '%s, ' % ticket.student.display_fullname pass ticket.student.__parent__.logger.info( '%s - %s %s/%s imported_ts updated (%s)' % (ob_class, ticket.student.student_id, ticket.level, ticket.code, imported_ts)) if error: self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. ' % error.strip(', ')), type="danger") return True class EditPreviousSessionScoresPage(CustomEditScoresPage): grok.name('edit_prev_scores') def update(self, *args, **kw): form = self.request.form self.current_academic_session = grok.getSite()[ 'configuration'].current_academic_session if self.context.__parent__.__parent__.score_editing_disabled: self.flash(_('Score editing disabled.'), type="warning") self.redirect(self.url(self.context)) return if not self.current_academic_session: self.flash(_('Current academic session not set.'), type="warning") self.redirect(self.url(self.context)) return previous_session = self.current_academic_session - 1 self.session_title = academic_sessions_vocab.getTerm( previous_session).title self.tickets = self._searchCatalog(previous_session) if not self.tickets: self.flash(_('No student found.'), type="warning") self.redirect(self.url(self.context)) return self.editable_tickets = [ ticket for ticket in self.tickets if ticket.editable_by_lecturer] if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form: return if not self.editable_tickets: return success = self._update_scores(form) if success: self.flash(_('You successfully updated course results.')) return class CustomExportPDFScoresSlip(ExportPDFScoresSlip): """Deliver a PDF slip of course tickets for a lecturer. """ note = u'\nUpgraded scores are with asterisks.' def data(self, session): #site = grok.getSite() cat = queryUtility(ICatalog, name='coursetickets_catalog') coursetickets = cat.searchResults( session=(session, session), code=(self.context.code, self.context.code) ) # Apply filter try: score_editing_enabled = grok.getSite()[ 'configuration'][str(session)].score_editing_enabled if checkPermission('waeup.manageAcademics', self.context): score_editing_enabled = True coursetickets_filtered = [courseticket for courseticket in coursetickets if (checkPermission('waeup.manageAcademics', self.context) or (courseticket.student.current_mode in score_editing_enabled)) and courseticket.total_score is not None and courseticket.__parent__.__parent__.is_current] except KeyError: coursetickets_filtered = coursetickets # In AAUE only editable tickets can be printed (deactivated on 02/10/19) #editable_tickets = [ # ticket for ticket in coursetickets_filtered # if ticket.editable_by_lecturer] header = [[_(''), _('Student Id'), _('Matric No.'), #_('Reg. No.'), #_('Fullname'), #_('Status'), #_('Course of\nStudies'), _('Department'), _('Level'), _(' CA '), _('Exam\nScore'), _('Total '), _('Grade'), ],] sorted_tickets = sorted(coursetickets_filtered, # editable_tickets, key=lambda ticket: str(ticket.student.depcode) + str(ticket.student.faccode) + str(ticket.student.matric_number) ) no = 1 tickets = [] passed = 0 failed = 0 with_ca = False grade_stats = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0, 'F':0, } for ticket in sorted_tickets: if ticket.ca > 0: with_ca = True total = ticket.total_score if getattr(ticket, 'imported_ts', None): total = "**%s**" % ticket.imported_ts grade = ticket._getGradeWeightFromScore[0] if grade in grade_stats.keys(): grade_stats[grade] += 1 if grade in ('F', '-'): failed += 1 else: passed += 1 fullname = textwrap.fill(ticket.student.display_fullname, 30) #deptitle = site['faculties'][ticket.student.faccode][ # ticket.student.depcode].longtitle row = [str(no), ticket.student.student_id, ticket.student.matric_number, #ticket.student.reg_number, #fullname, #ticket.student.translated_state, #ticket.student.certcode, str(ticket.student.faccode) + ' / ' + str(ticket.student.depcode), ticket.level, ticket.ca, ticket.score, total, grade, ] tickets.append(row) no += 1 total = passed + failed passed_perc = 0 failed_perc = 0 if total: passed_perc = round(100.0 * passed / total) failed_perc = round(100.0 * failed / total) dep = self.context.__parent__.__parent__.longtitle fac = self.context.__parent__.__parent__.__parent__.longtitle # remove CA column if not necessary if not with_ca: header = [[_(''), _('Student Id'), _('Matric No.'), #_('Reg. No.'), #_('Fullname'), #_('Status'), #_('Course of\nStudies'), _('Department'), _('Level'), #_(' CA '), _('Exam\nScore'), _('Total '), _('Grade'), ],] for ticket in tickets: del(ticket[5]) return header + tickets, [ dep, fac, total, passed, passed_perc, failed, failed_perc, grade_stats] def render(self): session = grok.getSite()['configuration'].current_academic_session lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles() if i['local_role'] == 'waeup.local.Lecturer'] lecturers = sorted(lecturers) lecturers = ', '.join(lecturers) students_utils = getUtility(IStudentsUtils) # only orientation is different return students_utils.renderPDFCourseticketsOverview( self, 'coursetickets', session, self.data(session), lecturers, '', 45, self.note) class DownloadPreviousSessionScoresView(DownloadScoresView): """View that exports scores. """ grok.name('download_prev_scores') def update(self): self.current_academic_session = grok.getSite()[ 'configuration'].current_academic_session if self.context.__parent__.__parent__.score_editing_disabled: self.flash(_('Score editing disabled.'), type="warning") self.redirect(self.url(self.context)) return if not self.current_academic_session: self.flash(_('Current academic session not set.'), type="warning") self.redirect(self.url(self.context)) return site = grok.getSite() exporter = getUtility(ICSVExporter, name='lecturer') self.csv = exporter.export_filtered(site, filepath=None, catalog='coursetickets', session=self.current_academic_session-1, level=None, code=self.context.code) return class AlumniRequestPasswordPage(StudentRequestPasswordPage): """Captcha'd request password page for students. """ grok.name('alumni_requestpw') grok.require('waeup.Anonymous') grok.template('alumni_requestpw') form_fields = grok.AutoFields(IStudentRequestPW).select( 'lastname','number','email') label = _('Search student record and send password for first-time login') def _redirect_no_student(self): self.flash(_('No student record found.'), type="warning") self.redirect(self.application_url() + '/applicants/trans2017/register') return class CustomAddStudyLevelFormPage(AddStudyLevelFormPage): """ This page is temporarily locked. """ def update(self): if self.context.student.current_mode == 'ug_ft' \ and self.context.student.current_session == 2021 \ and not self.context.student.entry_session == 2021: emit_lock_message(self) return super(CustomAddStudyLevelFormPage, self).update() return