## $Id: browser.py 13966 2016-06-21 10:37:04Z 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 from cStringIO import StringIO from zope.i18n import translate from zope.component import getUtility, queryUtility from zope.security import checkPermission from zope.catalog.interfaces import ICatalog from zope.formlib.textwidgets import BytesDisplayWidget from waeup.kofa.browser.layout import UtilityView from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget from waeup.kofa.interfaces import IKofaUtils, academic_sessions_vocab from waeup.kofa.students.interfaces import IStudentsUtils, IStudent from waeup.kofa.students.workflow import PAID, REGISTERED from waeup.kofa.students.studylevel import getGradeWeightFromScore from waeup.kofa.students.browser import ( StartClearancePage, StudentBasePDFFormPage, CourseTicketAddFormPage, StudyLevelDisplayFormPage, StudyLevelManageFormPage, StudyLevelEditFormPage, ExportPDFTranscriptSlip, ExportPDFAdmissionSlip, BedTicketAddPage, StudentFilesUploadPage, PaymentsManageFormPage, CourseTicketDisplayFormPage, CourseTicketManageFormPage, EditScoresPage, ExportPDFScoresSlip ) from kofacustom.nigeria.students.browser import ( NigeriaOnlinePaymentDisplayFormPage, NigeriaOnlinePaymentAddFormPage, NigeriaExportPDFPaymentSlip, NigeriaExportPDFCourseRegistrationSlip, NigeriaExportPDFClearanceSlip, NigeriaStudentPersonalDisplayFormPage, NigeriaStudentPersonalEditFormPage, NigeriaStudentPersonalManageFormPage, NigeriaStudentClearanceEditFormPage, NigeriaAccommodationManageFormPage, NigeriaStudentBaseDisplayFormPage, NigeriaStudentBaseManageFormPage ) from waeup.aaue.students.interfaces import ( ICustomStudentOnlinePayment, ICustomStudentStudyLevel, ICustomStudent, ICustomStudentPersonal, ICustomStudentPersonalEdit, ICustomUGStudentClearance, ICustomCourseTicket, ICustomStudentBase) from waeup.aaue.interswitch.browser import gateway_net_amt from waeup.aaue.interfaces import MessageFactory as _ 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 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(ICustomUGStudentClearance).omit( 'clearance_locked', 'clr_code', 'officer_comment', 'physical_clearance_date') 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 CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage): """ Page to add an online payment ticket """ form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select( 'p_category') 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') 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 = '\n\n The Amount Authorized is inclusive of: ' if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \ and academic_session: welfare_fee = gateway_net_amt(academic_session.welfare_fee) union_fee = gateway_net_amt(academic_session.union_fee) text += ('School Fee, ' '%s Naira Student Union Dues, ' '%s Naira Student Welfare Assurance Fee and ' % (union_fee, welfare_fee)) elif self.context.p_category in ( 'clearance_incl', 'clearance_medical_incl') and academic_session: matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee) lapel_fee = gateway_net_amt(academic_session.lapel_fee) text += ('Acceptance Fee, ' '%s Naira Matriculation Gown Fee, ' '%s Naira Lapel/File Fee and ' % (matric_gown_fee, lapel_fee)) return text + '250.0 Naira Transaction Charge.' class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage): """ Page to display student study levels """ grok.context(ICustomStudentStudyLevel) form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit( 'total_credits', 'gpa', 'level') form_fields[ 'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') class CustomStudyLevelManageFormPage(StudyLevelManageFormPage): """ Page to edit the student study level data """ grok.context(ICustomStudentStudyLevel) class CustomStudyLevelEditFormPage(StudyLevelEditFormPage): """ Page to edit the student study level data by students """ grok.context(ICustomStudentStudyLevel) 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') omit_fields = ('password', 'suspended', 'suspended_comment', 'phone', 'adm_code', 'sex', 'email', 'date_of_birth', 'department', 'current_mode', 'current_level', 'flash_notice') 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)) @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.endswith('_pt') \ or self.context.student.current_mode == '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'): 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): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE Sem = translate(_('Sem.'), 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) Score = translate(_('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], key=lambda value: str(value.semester) + value.code)) tableheader.append([(Code,'code', 2.0), (Title,'title', 7), (Cred, 'credits', 1.5), (Score, 'score', 1.4), #(CA, 'ca', 1.4), (Grade, 'grade', 1.4), (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 CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip): """Deliver a PDF slip of the context. """ def _sigsInFooter(self): return [] def _signatures(self): return ([( 'Akhimien Felicia O. (MANUPA)
Principal Assistant Registrar
' 'Exams, Records and Data Processing Division
For: Registrar')],) def render(self): portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE Term = translate(_('Sem.'), 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) #CA = translate(_('CA'), 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, 'score', 1.5), #(CA, 'ca', 1.5), (Grade, 'grade', 1.5), ] return 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(), ) 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(_('Clearance Slip\n'), target_language=portal_language) \ + ' %s' % self.context.display_fullname 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 CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage): """ Page to manage bed tickets. This manage form page is for both students and students officers. """ with_hostel_selection = True class CustomBedTicketAddPage(BedTicketAddPage): 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 """ form_fields = grok.AutoFields(ICustomCourseTicket) 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 class CustomEditScoresPage(EditScoresPage): """Page that filters and lists students. """ grok.template('editscorespage') 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`` 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): continue result[row['student_id']] = (row['score'], row['ca']) return result def update(self, *args, **kw): form = self.request.form ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '') 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 self.session_title = academic_sessions_vocab.getTerm( self.current_academic_session).title self.tickets = self._searchCatalog(self.current_academic_session) editable_tickets = [ ticket for ticket in self.tickets if ticket.editable_by_lecturer] if not self.tickets: self.flash(_('No student found.'), type="warning") self.redirect(self.url(self.context)) return if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form: return if not editable_tickets: return 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 else: self.flash( _('No file provided.'), type="danger") return else: formvals = dict(zip(form['sids'], zip(form['scores'], form['cas']))) error = '' for ticket in editable_tickets: ticket_error = False score = ticket.score ca = ticket.ca sid = ticket.student.student_id if formvals[sid][0] == '': score = None if formvals[sid][1] == '': ca = None try: if formvals[sid][0]: score = int(formvals[sid][0]) if formvals[sid][1]: ca = int(formvals[sid][1]) except ValueError: error += '%s, ' % ticket.student.display_fullname ticket_error = True if not ticket_error and ticket.score != score: ticket.score = score 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: ticket.ca = ca ticket.student.__parent__.logger.info( '%s - %s %s/%s ca updated (%s)' % (ob_class, ticket.student.student_id, ticket.level, ticket.code, ca)) if error: self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. ' 'Only integers are allowed.' % error.strip(', ')), type="danger") return self.flash(_('You successfully updated course results.')) return class CustomExportPDFScoresSlip(ExportPDFScoresSlip): """Deliver a PDF slip of course tickets for a lecturer. """ def table_data(self, session): cat = queryUtility(ICatalog, name='coursetickets_catalog') coursetickets = cat.searchResults( session=(session, session), code=(self.context.code, self.context.code) ) header = [[_(''), _('Matric No.'), _('Reg. No.'), _('Fullname'), _('Status'), _('Course of\nStudies'), _('Level'), _('Exam\nScore'), _(' CA '), _('Total '), _('Grade'), ],] tickets = [] no = 1 for ticket in list(coursetickets): if None in (ticket.score, ticket.ca): total = 'n/a' grade = 'n/a' else: total = ticket.score + ticket.ca grade = getGradeWeightFromScore(total)[0] fullname = textwrap.fill(ticket.student.display_fullname, 30) row = [no, ticket.student.matric_number, ticket.student.reg_number, fullname, ticket.student.translated_state, ticket.student.certcode, ticket.level, ticket.ca, ticket.score, total, grade, ] tickets.append(row) no += 1 return header + sorted(tickets, key=lambda value: value[5] + value[3])