## $Id: browser.py 15433 2019-05-29 12:05:52Z 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
from waeup.kofa.students.browser import (
    StartClearancePage,
    StudentBasePDFFormPage,
    CourseTicketAddFormPage,
    StudyLevelDisplayFormPage,
    StudyLevelManageFormPage,
    StudyLevelEditFormPage,
    ExportPDFTranscriptSlip,
    ExportPDFAdmissionSlip,
    BedTicketAddPage,
    StudentFilesUploadPage,
    PaymentsManageFormPage,
    CourseTicketDisplayFormPage,
    CourseTicketManageFormPage,
    EditScoresPage,
    ExportPDFScoresSlip,
    StudyCourseTranscriptPage,
    DownloadScoresView,
    StudentRequestPasswordPage,
    StudyCourseManageFormPage,
    UnregisterCoursesView,
    addCourseTicket,
    emit_lock_message
    )
from kofacustom.nigeria.students.browser import (
    NigeriaOnlinePaymentDisplayFormPage,
    NigeriaOnlinePaymentAddFormPage,
    NigeriaExportPDFPaymentSlip,
    NigeriaExportPDFCourseRegistrationSlip,
    NigeriaStudentPersonalDisplayFormPage,
    NigeriaStudentPersonalEditFormPage,
    NigeriaStudentPersonalManageFormPage,
    NigeriaStudentClearanceDisplayFormPage,
    NigeriaExportPDFClearanceSlip,
    NigeriaStudentClearanceManageFormPage,
    NigeriaStudentClearanceEditFormPage,
    NigeriaAccommodationManageFormPage,
    NigeriaStudentBaseDisplayFormPage,
    NigeriaStudentBaseManageFormPage
    )
from waeup.aaue.students.interfaces import (
    ICustomStudentOnlinePayment,
    ICustomStudentStudyLevel,
    ICustomStudent,
    ICustomStudentPersonal,
    ICustomStudentPersonalEdit,
    ICustomUGStudentClearance,
    ICustomUGStudentClearanceEdit,
    ICustomPGStudentClearance,
    ICustomCourseTicket,
    ICustomStudentBase,
    ICustomStudentStudyCourse)
from waeup.aaue.interswitch.browser import gateway_net_amt
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'] = "<strong>%s</strong>" % 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 """
 <br /><br />
 <strong>Instructions on CBT Venue Allocation Slip (VAS)</strong>
 <br /><br />
 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 <strong>MUST</strong> be shown to the invigilators
 before being admitted into the examination hall.
 <br /><br />
 How to start examination:<br /><br />
  * Username:  "student id" from Kofa e.g E1000000<br />
  * Password: "surname" as shown on this slip in capital letters<br />
  * Click the course and click "start exam".
 <br /><br />
 <strong>WARNING:</strong> 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.
 <br /><br />
 Your examination date, time and venue is scheduled as follows:
 <br /><br />
 <strong>%s</strong>
""" % 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 CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
    """ Page to add an online payment ticket
    """
    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
        'p_category')

    ALUMNI_PAYMENT_CATS =  {
        'transcript_local': 'Transcript Fee Local',
        'transcript_inter': 'Transcript Fee International',
        }

    REDUCED_PAYMENT_CATS =  {
        'clearance': 'Acceptance Fee',
        'schoolfee': 'School Fee',
        }

    IJMBE_PAYMENT_CATS =  {
        'clearance': 'Acceptance Fee',
        'schoolfee': 'School Fee',
        'schoolfee_1': 'School Fee (1st instalment)',
        'schoolfee_2': 'School Fee (2nd instalment)',
        }

    PT_AND_DSH_PAYMENT_CATS =  {
        'clearance_incl': 'Acceptance Fee Plus',
        'schoolfee_incl': 'School Fee Plus',
        'ent_registration_1': 'Registration Fee ENT201',
        'ent_text_book_1': 'Text Book Fee ENT201',
        'gst_registration_1': 'Registration Fee GST101 GST102 GST111 GST112',
        'gst_registration_2': 'Registration Fee GST222',
        'gst_text_book_0': 'Text Book Fee GST101 GST102 GST111 GST112',
        'gst_text_book_1': 'Text Book Fee GST101 GST102',
        'gst_text_book_2': 'Text Book Fee GST111 GST112',
        'gst_text_book_3': 'Text Book Fee GST222',
        }

    @property
    def selectable_categories(self):
        if 'alumni' in self.application_url():
            return sorted(
                self.ALUMNI_PAYMENT_CATS.items(), key=lambda value: value[1])
        if self.context.student.current_mode in (
            'special_pg_ft', 'special_pg_pt', 'found', 'bridge'):
            return sorted(
                self.REDUCED_PAYMENT_CATS.items(), key=lambda value: value[1])
        if self.context.student.current_mode in (
            'ug_pt', 'de_pt','dp_pt', 'de_dsh', 'ug_dsh'):
            return sorted(
                self.PT_AND_DSH_PAYMENT_CATS.items(),
                key=lambda value: value[1])
        if self.context.student.current_mode == 'ijmbe':
            return sorted(
                self.IJMBE_PAYMENT_CATS.items(), key=lambda value: value[1])
        student = self.context.__parent__
        categories = getUtility(
            IKofaUtils).selectable_payment_categories(student)
        return sorted(categories.items(), key=lambda value: value[1])

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)
            if self.context.student.entry_session == 2016 \
                and self.context.student.entry_mode == 'ug_ft' \
                and self.context.p_session == 2016:
                # Add student id card fee to first school fee payment.

                ## Attention: The payment slip does not contain any information
                ## whether the fee was added or not.
                ## We can only draw conclusions from from the student's entry
                ## session whether the fee had been included.
                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
                #text += ('School Fee, '
                #         '%s Naira Student ID Card Fee, '
                #         '%s Naira Student Union Dues, '
                #         '%s Naira Student Welfare Assurance Fee and '
                #         % (id_card_fee, union_fee, welfare_fee))

                text += ('School Fee, '
                         'Student ID Card Fee, '
                         'Student Union Dues, '
                         'Sports Fee, '
                         'Library Levy, '
                         'Student Welfare Assurance Fee and ')
            else:

                #text += ('School Fee, '
                #         '%s Naira Student Union Dues, '
                #         '%s Naira Student Welfare Assurance Fee and '
                #         % (union_fee, welfare_fee))

                text += ('School Fee, '
                         'Student Union Dues, '
                         'Sports Development Fee, '
                         'Library Development Levy, '
                         'Student Welfare Assurance Fee and ')
        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))

            text += ('Acceptance Fee, '
                     'Matriculation Gown Fee, '
                     'Lapel/File Fee and ')

        #return text + '250.0 Naira Transaction Charge.'

        return text + 'Transaction Charge.'

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:
#            deadline = grok.getSite()['configuration'][
#                str(self.context.level_session)].coursereg_deadline
#        except (TypeError, KeyError):
#            deadline = None
#        # In AAUE fresh students are allowed to "unregister their course"
#        # aside the deadline
#        if deadline and not self.context.student.is_fresh \
#            and deadline < datetime.now(pytz.utc):
#            self.flash(_(
#                "Course registration has ended. "
#                "Unregistration is disabled."), type="warning")
#        elif 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')

    omit_fields = ('password', 'suspended', 'suspended_comment',
        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
        'department', 'current_mode', 'current_level', 'flash_notice',
        'transcript_remark')

    @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.endswith('_pt') \
            or self.context.student.current_mode == 'found':
            return (
                [('I have selected the course on the advise of my Head of '
                 'Department. <br>', _('Student\'s Signature'), '<br>')],
                [('This student has satisfied the department\'s requirements. '
                 'I recommend to approve the course registration. <br>',
                 _('Head of Department\'s Signature'), '<br>')],
                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
                [('', _('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'), '<br>')],
                [('We approved the above registration:',
                    _('Major Supervisor (Name / Signature)'), '')],
                [('', _('Co-Supervisor (Name / Signature)'), '')],
                [('', _('Head of Department'), '<br>')],
                [('The student has satisfied the conditions for renewal of '
                  'registration for graduate school programme in this university:',
                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
                [('', _('Dean <br /> (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)
        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],
                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.
    """
#    grok.require('waeup.viewStudent')

    note = _("""
<br /><br /><br /><br />
<font size='10'>
<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
</font>
""")

    def _sigsInFooter(self):
        return []

    def _signatures(self):
        return ([(
            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
            'Exams, Records and Data Processing Division <br /> 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)
        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 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
    """

    @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_tss'])))
        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 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 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
            coursetickets_filtered = [courseticket
                for courseticket in coursetickets
                if 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
        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(editable_tickets,
            key=lambda ticket: ticket.student.depcode
                               + ticket.student.faccode
                               + ticket.student.matric_number)
        no = 1
        tickets = []
        passed = 0
        failed = 0
        with_ca = False
        # In AAUE only editable tickets can be printed
        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 ('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,
                  ticket.student.faccode + ' / ' + 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]

    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 =  ', '.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