## Copyright (C) 2011 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
##
"""UI components for students and related components.
"""
import grok
from time import time
from datetime import date, datetime
from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
from zope.component import createObject
from waeup.sirp.accesscodes import (
    invalidate_accesscode, get_access_code, create_accesscode)
from waeup.sirp.accesscodes.workflow import USED
from waeup.sirp.browser import (
    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
from waeup.sirp.browser.breadcrumbs import Breadcrumb
from waeup.sirp.browser.resources import datepicker, datatable, tabs
from waeup.sirp.browser.viewlets import (
    ManageActionButton, PrimaryNavTab, AddActionButton)
from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
from waeup.sirp.widgets.datewidget import (
    FriendlyDateWidget, FriendlyDateDisplayWidget,
    FriendlyDatetimeDisplayWidget)
from waeup.sirp.university.vocabularies import study_modes
from waeup.sirp.students.interfaces import (
    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
    IStudentPersonal, IStudentBase, IStudentStudyCourse,
    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
    IStudentOnlinePayment
    )
from waeup.sirp.students.catalog import search
from waeup.sirp.students.workflow import CLEARANCE, RETURNING, CLEARED
from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
from waeup.sirp.students.vocabularies import StudyLevelSource
from waeup.sirp.students.utils import getPaymentDetails
from waeup.sirp.browser.resources import toggleall
from waeup.sirp.authentication import get_principal_role_manager

def write_log_message(view, message):
    ob_class = view.__implemented__.__name__.replace('waeup.sirp.','')
    view.context.getStudent().loggerInfo(ob_class, message)
    return

# Save function used for save methods in manager pages
def msave(view, **data):
    form = view.request.form
    changed_fields = view.applyData(view.context, **data)
    # Turn list of lists into single list
    if changed_fields:
        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
    fields_string = ' + '.join(changed_fields)
    view.context._p_changed = True
    view.flash('Form has been saved.')
    if fields_string:
        write_log_message(view, 'saved: % s' % fields_string)
    return

class StudentsTab(PrimaryNavTab):
    """Students tab in primary navigation.
    """

    grok.context(IWAeUPObject)
    grok.order(4)
    grok.require('waeup.viewStudent')
    grok.template('primarynavtab')

    pnav = 4
    tab_title = u'Students'

    @property
    def link_target(self):
        return self.view.application_url('students')

class StudentsBreadcrumb(Breadcrumb):
    """A breadcrumb for the students container.
    """
    grok.context(IStudentsContainer)
    title = u'Students'

class StudentBreadcrumb(Breadcrumb):
    """A breadcrumb for the student container.
    """
    grok.context(IStudent)

    def title(self):
        return self.context.fullname

class SudyCourseBreadcrumb(Breadcrumb):
    """A breadcrumb for the student study course.
    """
    grok.context(IStudentStudyCourse)
    title = u'Study Course'

class PaymentsBreadcrumb(Breadcrumb):
    """A breadcrumb for the student payments folder.
    """
    grok.context(IStudentPaymentsContainer)
    title = u'Payments'

class OnlinePaymentBreadcrumb(Breadcrumb):
    """A breadcrumb for course lists.
    """
    grok.context(IStudentOnlinePayment)

    @property
    def title(self):
        return self.context.p_id

class AccommodationBreadcrumb(Breadcrumb):
    """A breadcrumb for the student accommodation folder.
    """
    grok.context(IStudentAccommodation)
    title = u'Accommodation'

class StudyLevelBreadcrumb(Breadcrumb):
    """A breadcrumb for course lists.
    """
    grok.context(IStudentStudyLevel)

    @property
    def title(self):
        return self.context.level_title

class StudentsContainerPage(WAeUPPage):
    """The standard view for student containers.
    """
    grok.context(IStudentsContainer)
    grok.name('index')
    grok.require('waeup.viewStudent')
    grok.template('containerpage')
    label = 'Student Section'
    title = 'Students'
    pnav = 4

    def update(self, *args, **kw):
        datatable.need()
        form = self.request.form
        self.hitlist = []
        if 'searchterm' in form and form['searchterm']:
            self.searchterm = form['searchterm']
            self.searchtype = form['searchtype']
        elif 'old_searchterm' in form:
            self.searchterm = form['old_searchterm']
            self.searchtype = form['old_searchtype']
        else:
            if 'search' in form:
                self.flash('Empty search string.')
            return
        self.hitlist = search(query=self.searchterm,
            searchtype=self.searchtype, view=self)
        if not self.hitlist:
            self.flash('No student found.')
        return

class SetPasswordPage(WAeUPPage):
    grok.context(IWAeUPObject)
    grok.name('setpassword')
    grok.require('waeup.Public')
    grok.template('setpassword')
    title = ''
    label = 'Set password for first-time login'
    ac_prefix = 'PWD'
    pnav = 0

    def update(self, SUBMIT=None):
        self.reg_number = self.request.form.get('reg_number', None)
        self.ac_series = self.request.form.get('ac_series', None)
        self.ac_number = self.request.form.get('ac_number', None)

        if SUBMIT is None:
            return
        hitlist = search(query=self.reg_number,
            searchtype='reg_number', view=self)
        if not hitlist:
            self.flash('No student found.')
            return
        if len(hitlist) != 1:   # Cannot happen but anyway
            self.flash('More than one student found.')
            return
        student = hitlist[0].context
        self.student_id = student.student_id
        student_pw = student.password
        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
        code = get_access_code(pin)
        if not code:
            self.flash('Access code is invalid.')
            return
        if student_pw and pin == student.adm_code:
            self.flash('Password has already been set. Your Student Id is %s'
                % self.student_id)
            return
        elif student_pw:
            self.flash('Password has already been set. You are using the wrong Access Code.')
            return
        # Mark pin as used (this also fires a pin related transition)
        # and set student password
        if code.state == USED:
            self.flash('Access code has already been used.')
            return
        else:
            comment = u"AC invalidated for %s" % self.student_id
            # Here we know that the ac is in state initialized so we do not
            # expect an exception
            #import pdb; pdb.set_trace()
            invalidate_accesscode(pin,comment)
            IUserAccount(student).setPassword(self.ac_number)
            student.adm_code = pin
        self.flash('Password has been set. Your Student Id is %s'
            % self.student_id)
        return

class StudentsContainerManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudentsContainer)
    grok.view(StudentsContainerPage)
    grok.require('waeup.manageStudents')
    text = 'Manage student section'


class StudentsContainerManagePage(WAeUPPage):
    """The manage page for student containers.
    """
    grok.context(IStudentsContainer)
    grok.name('manage')
    grok.require('waeup.manageStudents')
    grok.template('containermanagepage')
    pnav = 4
    title = 'Manage student section'

    @property
    def label(self):
        return self.title

    def update(self, *args, **kw):
        datatable.need()
        toggleall.need()
        form = self.request.form
        self.hitlist = []
        if 'searchterm' in form and form['searchterm']:
            self.searchterm = form['searchterm']
            self.searchtype = form['searchtype']
        elif 'old_searchterm' in form:
            self.searchterm = form['old_searchterm']
            self.searchtype = form['old_searchtype']
        else:
            if 'search' in form:
                self.flash('Empty search string.')
            return
        if not 'entries' in form:
            self.hitlist = search(query=self.searchterm,
                searchtype=self.searchtype, view=self)
            if not self.hitlist:
                self.flash('No student found.')
            return
        entries = form['entries']
        if isinstance(entries, basestring):
            entries = [entries]
        deleted = []
        for entry in entries:
            if 'remove' in form:
                del self.context[entry]
                deleted.append(entry)
        self.hitlist = search(query=self.searchterm,
            searchtype=self.searchtype, view=self)
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
        return

class StudentsContainerAddActionButton(AddActionButton):
    grok.order(1)
    grok.context(IStudentsContainer)
    grok.view(StudentsContainerManagePage)
    grok.require('waeup.manageStudents')
    text = 'Add student'
    target = 'addstudent'

class StudentAddFormPage(WAeUPAddFormPage):
    """Add-form to add a student.
    """
    grok.context(IStudentsContainer)
    grok.require('waeup.manageStudents')
    grok.name('addstudent')
    grok.template('studentaddpage')
    form_fields = grok.AutoFields(IStudent)
    title = 'Students'
    label = 'Add student'
    pnav = 4

    @grok.action('Create student record')
    def addStudent(self, **data):
        student = createObject(u'waeup.Student')
        self.applyData(student, **data)
        self.context.addStudent(student)
        self.flash('Student record created.')
        self.redirect(self.url(self.context[student.student_id], 'index'))
        return

class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display student base data
    """
    grok.context(IStudent)
    grok.name('index')
    grok.require('waeup.viewStudent')
    grok.template('basepage')
    form_fields = grok.AutoFields(IStudentBase).omit('password')
    pnav = 4
    title = 'Base Data'

    @property
    def label(self):
        return '%s: Base Data' % self.context.fullname

    @property
    def hasPassword(self):
        if self.context.password:
            return 'set'
        return 'unset'

class StudentBaseManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentBaseDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'edit_base'

class StudentBaseManageFormPage(WAeUPEditFormPage):
    """ View to edit student base data
    """
    grok.context(IStudent)
    grok.name('edit_base')
    grok.require('waeup.manageStudents')
    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
    grok.template('basemanagepage')
    label = 'Manage base data'
    title = 'Base Data'
    pnav = 4

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        super(StudentBaseManageFormPage, self).update()
        self.wf_info = IWorkflowInfo(self.context)
        return

    def getTransitions(self):
        """Return a list of dicts of allowed transition ids and titles.

        Each list entry provides keys ``name`` and ``title`` for
        internal name and (human readable) title of a single
        transition.
        """
        allowed_transitions = self.wf_info.getManualTransitions()
        return [dict(name='', title='No transition')] +[
            dict(name=x, title=y) for x, y in allowed_transitions]

    @grok.action('Save')
    def save(self, **data):
        form = self.request.form
        password = form.get('password', None)
        password_ctl = form.get('control_password', None)
        if password:
            if (password != password_ctl):
                self.flash('Passwords do not match.')
            else:
                # XXX: This is too early. PW should only be saved if there
                #      are no (other) errors left in form.
                IUserAccount(self.context).setPassword(password)
                write_log_message(self, 'password changed')

        #self.reg_number = form.get('form.reg_number', None)
        #if self.reg_number:
        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
        #    if hitlist and hitlist[0].student_id != self.context.student_id:
        #        self.flash('Registration number exists.')
        #        return
        #self.matric_number = form.get('form.matric_number', None)
        #if self.matric_number:
        #    hitlist = search(query=self.matric_number,
        #        searchtype='matric_number', view=self)
        #    if hitlist and hitlist[0].student_id != self.context.student_id:
        #        self.flash('Matriculation number exists.')
        #        return

        # Turn list of lists into single list
        changed_fields = self.applyData(self.context, **data)
        if changed_fields:
            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
        fields_string = ' + '.join(changed_fields)
        self.context._p_changed = True
        if form.has_key('transition') and form['transition']:
            transition_id = form['transition']
            self.wf_info.fireTransition(transition_id)
        self.flash('Form has been saved.')
        if fields_string:
            write_log_message(self, 'saved: % s' % fields_string)
        return

class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display student clearance data
    """
    grok.context(IStudent)
    grok.name('view_clearance')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    title = 'Clearance Data'
    pnav = 4

    @property
    def label(self):
        return '%s: Clearance Data' % self.context.fullname

class StudentClearanceManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentClearanceDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'edit_clearance'

class StudentClearanceManageFormPage(WAeUPEditFormPage):
    """ Page to edit student clearance data
    """
    grok.context(IStudent)
    grok.name('edit_clearance')
    grok.require('waeup.manageStudents')
    form_fields = grok.AutoFields(IStudentClearance)
    label = 'Manage clearance data'
    title = 'Clearance Data'
    pnav = 4

    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        return super(StudentClearanceManageFormPage, self).update()

    @grok.action('Save')
    def save(self, **data):
        msave(self, **data)
        return

class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display student personal data
    """
    grok.context(IStudent)
    grok.name('view_personal')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentPersonal)
    title = 'Personal Data'
    pnav = 4

    @property
    def label(self):
        return '%s: Personal Data' % self.context.fullname

class StudentPersonalManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentPersonalDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'edit_personal'

class StudentPersonalManageFormPage(WAeUPEditFormPage):
    """ Page to edit student clearance data
    """
    grok.context(IStudent)
    grok.name('edit_personal')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentPersonal)
    label = 'Manage personal data'
    title = 'Personal Data'
    pnav = 4

    @grok.action('Save')
    def save(self, **data):
        msave(self, **data)
        return

class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display the student study course data
    """
    grok.context(IStudentStudyCourse)
    grok.name('index')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentStudyCourse)
    grok.template('studycoursepage')
    title = 'Study Course'
    pnav = 4

    @property
    def label(self):
        return '%s: Study Course' % self.context.__parent__.fullname

    @property
    def current_mode(self):
        return study_modes.getTermByToken(
            self.context.certificate.study_mode).title

class StudyCourseManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudentStudyCourse)
    grok.view(StudyCourseDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'manage'

class StudyCourseManageFormPage(WAeUPEditFormPage):
    """ Page to edit the student study course data
    """
    grok.context(IStudentStudyCourse)
    grok.name('manage')
    grok.require('waeup.manageStudents')
    grok.template('studycoursemanagepage')
    form_fields = grok.AutoFields(IStudentStudyCourse)
    title = 'Study Course'
    label = 'Manage study course'
    pnav = 4
    taboneactions = ['Save','Cancel']
    tabtwoactions = ['Remove selected levels','Cancel']
    tabthreeactions = ['Add study level']

    def update(self):
        super(StudyCourseManageFormPage, self).update()
        tabs.need()
        datatable.need()
        return 

    @grok.action('Save')
    def save(self, **data):
        msave(self, **data)
        return

    @property
    def level_dict(self):
        studylevelsource = StudyLevelSource().factory
        for code in studylevelsource.getValues(self.context):
            title = studylevelsource.getTitle(self.context, code)
            yield(dict(code=code, title=title))

    @grok.action('Add study level')
    def addStudyLevel(self, **data):
        level_code = self.request.form.get('addlevel', None)
        studylevel = StudentStudyLevel()
        studylevel.level = int(level_code)
        try:
            self.context.addStudentStudyLevel(
                self.context.certificate,studylevel)
        except KeyError:
            self.flash('This level exists.')
        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
        return

    @grok.action('Remove selected levels')
    def delStudyLevels(self, **data):
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash('No study level selected.')
            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            try:
                del self.context[id]
                deleted.append(id)
            except:
                self.flash('Could not delete %s: %s: %s' % (
                        id, sys.exc_info()[0], sys.exc_info()[1]))
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
        return

class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display student study levels
    """
    grok.context(IStudentStudyLevel)
    grok.name('index')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentStudyLevel)
    grok.template('studylevelpage')
    pnav = 4

    @property
    def title(self):
        return 'Study Level %s' % self.context.level_title

    @property
    def label(self):
        return '%s: Study Level %s' % (
            self.context.getStudent().fullname,self.context.level_title)

    @property
    def total_credits(self):
        total_credits = 0
        for key, val in self.context.items():
            total_credits += val.credits
        return total_credits

class StudyLevelManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudentStudyLevel)
    grok.view(StudyLevelDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'manage'

class StudyLevelManageFormPage(WAeUPEditFormPage):
    """ Page to edit the student study level data
    """
    grok.context(IStudentStudyLevel)
    grok.name('manage')
    grok.require('waeup.manageStudents')
    grok.template('studylevelmanagepage')
    form_fields = grok.AutoFields(IStudentStudyLevel)
    pnav = 4
    taboneactions = ['Save','Cancel']
    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']

    def update(self):
        super(StudyLevelManageFormPage, self).update()
        tabs.need()
        datatable.need()
        return

    @property
    def title(self):
        return 'Study Level %s' % self.context.level_title

    @property
    def label(self):
        return 'Manage study level %s' % self.context.level_title

    @grok.action('Save')
    def save(self, **data):
        msave(self, **data)
        return

    @grok.action('Add course ticket')
    def addCourseTicket(self, **data):
        self.redirect(self.url(self.context, '@@add'))

    @grok.action('Remove selected tickets')
    def delCourseTicket(self, **data):
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash('No ticket selected.')
            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            try:
                del self.context[id]
                deleted.append(id)
            except:
                self.flash('Could not delete %s: %s: %s' % (
                        id, sys.exc_info()[0], sys.exc_info()[1]))
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
        return

class CourseTicketAddFormPage(WAeUPAddFormPage):
    """Add a course ticket.
    """
    grok.context(IStudentStudyLevel)
    grok.name('add')
    grok.require('waeup.manageStudents')
    label = 'Add course ticket'
    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
        'grade', 'score', 'automatic')
    pnav = 4

    @property
    def title(self):
        return 'Study Level %s' % self.context.level_title

    @grok.action('Add course ticket')
    def addCourseTicket(self, **data):
        ticket = CourseTicket()
        course = data['course']
        ticket.core_or_elective = data['core_or_elective']
        ticket.automatic = False
        ticket.code = course.code
        ticket.title = course.title
        ticket.faculty = course.__parent__.__parent__.__parent__.title
        ticket.department = course.__parent__.__parent__.title
        ticket.credits = course.credits
        ticket.passmark = course.passmark
        ticket.semester = course.semester
        try:
            self.context.addCourseTicket(ticket)
        except KeyError:
            self.flash('The ticket exists.')
            return
        self.flash('Successfully added %s.' % ticket.code)
        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
        return

    @grok.action('Cancel')
    def cancel(self, **data):
        self.redirect(self.url(self.context))

class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display course tickets
    """
    grok.context(ICourseTicket)
    grok.name('index')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(ICourseTicket)
    grok.template('courseticketpage')
    pnav = 4

    @property
    def title(self):
        return 'Course Ticket %s' % self.context.code

    @property
    def label(self):
        return '%s: Course Ticket %s' % (
            self.context.getStudent().fullname,self.context.code)

class CourseTicketManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(ICourseTicket)
    grok.view(CourseTicketDisplayFormPage)
    grok.require('waeup.manageStudents')
    text = 'Manage'
    target = 'manage'

class CourseTicketManageFormPage(WAeUPEditFormPage):
    """ Page to manage course tickets
    """
    grok.context(ICourseTicket)
    grok.name('manage')
    grok.require('waeup.manageStudents')
    form_fields = grok.AutoFields(ICourseTicket)
    grok.template('courseticketmanagepage')
    pnav = 4

    @property
    def title(self):
        return 'Course Ticket %s' % self.context.code

    @property
    def label(self):
        return 'Manage course ticket %s' % self.context.code

    @grok.action('Save')
    def save(self, **data):
        msave(self, **data)
        return

# We don't need the display form page yet
#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
#    """ Page to display the student payments
#    """
#    grok.context(IStudentPaymentsContainer)
#    grok.name('view')
#    grok.require('waeup.viewStudent')
#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
#    grok.template('paymentspage')
#    title = 'Payments'
#    pnav = 4

#    def formatDatetime(self,datetimeobj):
#        if isinstance(datetimeobj, datetime):
#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
#        else:
#            return None

#    @property
#    def label(self):
#        return '%s: Payments' % self.context.__parent__.fullname

#    def update(self):
#        super(PaymentsDisplayFormPage, self).update()
#        datatable.need()
#        return

# This manage form page is for both students and students officers.
class PaymentsManageFormPage(WAeUPEditFormPage):
    """ Page to manage the student payments
    """
    grok.context(IStudentPaymentsContainer)
    grok.name('index')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(IStudentPaymentsContainer)
    grok.template('paymentsmanagepage')
    title = 'Payments'
    pnav = 4

    def unremovable(self, ticket):
        prm = get_principal_role_manager()
        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
        return ('waeup.Student' in roles and ticket.r_code)

    def formatDatetime(self,datetimeobj):
        if isinstance(datetimeobj, datetime):
            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
        else:
            return None

    @property
    def label(self):
        return '%s: Payments' % self.context.__parent__.fullname

    def update(self):
        super(PaymentsManageFormPage, self).update()
        datatable.need()
        return

    @grok.action('Remove selected tickets')
    def delPaymentTicket(self, **data):
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash('No payment selected.')
            self.redirect(self.url(self.context))
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            # Students are not allowed to remove payment used tickets
            if not self.unremovable(self.context[id]):
                try:
                    del self.context[id]
                    deleted.append(id)
                except:
                    self.flash('Could not delete %s: %s: %s' % (
                            id, sys.exc_info()[0], sys.exc_info()[1]))
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
            write_log_message(self,'removed: % s' % ', '.join(deleted))
        self.redirect(self.url(self.context))
        return

    @grok.action('Add online payment ticket')
    def addPaymentTicket(self, **data):
        self.redirect(self.url(self.context, '@@addop'))

#class OnlinePaymentManageActionButton(ManageActionButton):
#    grok.order(1)
#    grok.context(IStudentPaymentsContainer)
#    grok.view(PaymentsDisplayFormPage)
#    grok.require('waeup.manageStudents')
#    text = 'Manage payments'
#    target = 'manage'

class OnlinePaymentAddFormPage(WAeUPAddFormPage):
    """ Page to add an online payment ticket
    """
    grok.context(IStudentPaymentsContainer)
    grok.name('addop')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
        'p_category')
    #zzgrok.template('addpaymentpage')
    label = 'Add online payment'
    title = 'Payments'
    pnav = 4
    
    # To be sepezified in customization packages
    def getPaymentDetails(self, category, student):
        return getPaymentDetails(category, student)

    @grok.action('Create ticket')
    def createTicket(self, **data):
        payment = createObject(u'waeup.StudentOnlinePayment')
        self.applyData(payment, **data)
        timestamp = "%d" % int(time()*1000)
        #order_id = "%s%s" % (student_id[1:],timestamp)
        payment.p_id = "p%s" % timestamp
        (payment.amount_auth,
            payment.p_item, payment.p_session,
            payment.surcharge_1,
            payment.surcharge_2,
            payment.surcharge_3,
            error)  = self.getPaymentDetails(
            data['p_category'],self.context.__parent__)
        if error:
            self.flash(error)
            self.redirect(self.url(self.context))
            return
        self.context[payment.p_id] = payment
        self.flash('Payment ticket created.')
        self.redirect(self.url(self.context))
        return

class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to view an online payment ticket
    """
    grok.context(IStudentOnlinePayment)
    grok.name('index')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentOnlinePayment)
    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    pnav = 4

    @property
    def title(self):
        return 'Online Payment Ticket %s' % self.context.p_id

    @property
    def label(self):
        return '%s: Online Payment Ticket %s' % (
            self.context.__parent__.__parent__.fullname,self.context.p_id)

class OnlinePaymentCallbackPage(grok.View):
    """ Callback view
    """
    grok.context(IStudentOnlinePayment)
    grok.name('callback')
    grok.require('waeup.payStudent')

    # This update method simulates a valid callback und must be
    # specified in the customization package. The parameters must be taken
    # from the incoming request.
    def update(self):
        student = self.context.getStudent()
        write_log_message(self,'valid callback: %s' % self.context.p_id)
        self.context.r_amount_approved = self.context.amount_auth
        self.context.r_card_num = u'0000'
        self.context.r_code = u'00'
        self.context.p_state = 'paid'
        self.context.payment_date = datetime.now()
        if self.context.p_category == 'clearance':
            # Create CLR access code
            pin, error = create_accesscode('CLR',0,student.student_id)
            if error:
                self.flash('Valid callback received. ' + error)
                return
            self.context.ac = pin
        elif self.context.p_category == 'schoolfee':
            # Create SFE access code
            pin, error = create_accesscode('SFE',0,student.student_id)
            if error:
                self.flash('Valid callback received. ' + error)
                return
            self.context.ac = pin
        self.flash('Valid callback received.')
        return

    def render(self):
        self.redirect(self.url(self.context, '@@index'))
        return

class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
    """ Page to display the student accommodation data
    """
    grok.context(IStudentAccommodation)
    grok.name('index')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentAccommodation)
    #grok.template('accommodationpage')
    title = 'Accommodation'
    pnav = 4

    @property
    def label(self):
        return '%s: Accommodation Data' % self.context.__parent__.fullname

class StudentHistoryPage(WAeUPPage):
    """ Page to display student clearance data
    """
    grok.context(IStudent)
    grok.name('history')
    grok.require('waeup.viewStudent')
    grok.template('studenthistory')
    title = 'History'
    pnav = 4

    @property
    def label(self):
        return '%s: History' % self.context.fullname

# Pages for students only

class StudentBaseEditActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentBaseDisplayFormPage)
    grok.require('waeup.handleStudent')
    text = 'Change password'
    target = 'bedit'

class StudentPasswordSetting(grok.Adapter):
    """Adapt IStudent to data needed for password settings.

    We provide password getters/setters for the attached context (an
    IStudent object) that cooperate seamless with the usual
    formlib/form techniques.
    """
    grok.context(IStudent)
    grok.provides(IStudentPasswordSetting)

    def __init__(self, context):
        self.name = context.fullname
        self.password_repeat = context.password
        self.context = context
        return

    def getPassword(self):
        return self.context.password

    def setPassword(self, password):
        IUserAccount(self.context).setPassword(password)
        return

    password = property(getPassword, setPassword)

class StudentBaseEditFormPage(WAeUPEditFormPage):
    """ View to edit student base data by student
    """
    grok.context(IStudent)
    grok.name('bedit')
    grok.require('waeup.handleStudent')
    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
    #    'student_id', 'reg_number', 'matric_number')
    form_fields = grok.AutoFields(IStudentPasswordSetting)
    grok.template('baseeditpage')
    label = 'Change password'
    title = 'Base Data'
    pnav = 4

    def update(self):
        super(StudentBaseEditFormPage, self).update()
        self.wf_info = IWorkflowInfo(self.context)
        return

    def onFailure(self, action, data, errors):
        new_status = []
        other_errors = False
        for error in errors:
            msg = getattr(error, 'message', '')
            if isinstance(msg, basestring) and msg != '':
                new_status.append(msg)
            else:
                other_errors = True
        if other_errors:
            if new_status:
                new_status.append('see below for further errors')
            else:
                new_status.append('See below for details.')
        if new_status:
            self.status = u'There were errors: %s' % ', '.join(new_status)
        return

    @grok.action('Save', failure=onFailure)
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash('Form has been saved.')
        return

class StudentClearanceStartActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentClearanceDisplayFormPage)
    grok.require('waeup.handleStudent')
    icon = 'actionicon_start.png'
    text = 'Start clearance'
    target = 'start_clearance'

    @property
    def target_url(self):
        if self.context.state != 'admitted':
            return ''
        return self.view.url(self.view.context, self.target)

class StartClearancePage(WAeUPPage):
    grok.context(IStudent)
    grok.name('start_clearance')
    grok.require('waeup.handleStudent')
    grok.template('enterpin')
    title = 'Start clearance'
    label = 'Start clearance'
    ac_prefix = 'CLR'
    notice = ''
    pnav = 4
    buttonname = 'Start clearance now'

    def update(self, SUBMIT=None):
        if not self.context.state == 'admitted':
            self.flash("Wrong state.")
            self.redirect(self.url(self.context))
            return
        self.ac_series = self.request.form.get('ac_series', None)
        self.ac_number = self.request.form.get('ac_number', None)

        if SUBMIT is None:
            return
        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
        code = get_access_code(pin)
        if not code:
            self.flash('Activation code is invalid.')
            return
        # Mark pin as used (this also fires a pin related transition)
        # and fire transition start_clearance
        if code.state == USED:
            self.flash('Activation code has already been used.')
            return
        else:
            comment = u"AC invalidated for %s" % self.context.student_id
            # Here we know that the ac is in state initialized so we do not
            # expect an exception, but the owner might be different
            if not invalidate_accesscode(pin,comment,self.context.student_id):
                self.flash('You are not the owner of this access code.')
                return
            self.context.clr_code = pin
        IWorkflowInfo(self.context).fireTransition('start_clearance')
        self.flash('Clearance process has been started.')
        self.redirect(self.url(self.context,'cedit'))
        return

class StudentClearanceEditActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudent)
    grok.view(StudentClearanceDisplayFormPage)
    grok.require('waeup.handleStudent')
    text = 'Edit'
    target = 'cedit'

    @property
    def target_url(self):
        if self.context.clearance_locked:
            return ''
        return self.view.url(self.view.context, self.target)

class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
    """ View to edit student clearance data by student
    """
    grok.context(IStudent)
    grok.name('cedit')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(
        IStudentClearanceEdit).omit('clearance_locked')
    label = 'Edit clearance data'
    title = 'Clearance Data'
    pnav = 4
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')

    def emitLockMessage(self):
        self.flash('The requested form is locked (read-only).')
        self.redirect(self.url(self.context))
        return

    def update(self):
        if self.context.clearance_locked:
            self.emitLockMessage()
            return
        datepicker.need()
        return super(StudentClearanceEditFormPage, self).update()

    @grok.action('Save')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash('Clearance form has been saved.')
        return

    @grok.action('Save and request clearance')
    def requestclearance(self, **data):
        self.applyData(self.context, **data)
        self.context._p_changed = True
        #if self.dataNotComplete():
        #    self.flash(self.dataNotComplete())
        #    return
        self.flash('Clearance form has been saved.')
        self.redirect(self.url(self.context,'request_clearance'))
        return

class RequestClearancePage(WAeUPPage):
    grok.context(IStudent)
    grok.name('request_clearance')
    grok.require('waeup.handleStudent')
    grok.template('enterpin')
    title = 'Request clearance'
    label = 'Request clearance'
    notice = 'Enter the CLR access code used for starting clearance.'
    ac_prefix = 'CLR'
    pnav = 4
    buttonname = 'Request clearance now'

    def update(self, SUBMIT=None):
        self.ac_series = self.request.form.get('ac_series', None)
        self.ac_number = self.request.form.get('ac_number', None)
        if SUBMIT is None:
            return
        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
        if self.context.clr_code != pin:
            self.flash("This isn't your CLR access code.")
            return
        state = IWorkflowState(self.context).getState()
        # This shouldn't happen, but the application officer
        # might have forgotten to lock the form after changing the state
        if state != CLEARANCE:
            self.flash('This form cannot be submitted. Wrong state!')
            return
        IWorkflowInfo(self.context).fireTransition('request_clearance')
        self.flash('Clearance has been requested.')
        self.redirect(self.url(self.context))
        return

class CourseRegistrationStartActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudentStudyCourse)
    grok.view(StudyCourseDisplayFormPage)
    grok.require('waeup.handleStudent')
    icon = 'actionicon_start.png'
    text = 'Start course registration'
    target = 'start_course_registration'

    @property
    def target_url(self):
        if not self.context.getStudent().state in (CLEARED,RETURNING):
            return ''
        return self.view.url(self.view.context, self.target)

class StartCourseRegistrationPage(WAeUPPage):
    grok.context(IStudentStudyCourse)
    grok.name('start_course_registration')
    grok.require('waeup.handleStudent')
    grok.template('enterpin')
    title = 'Start course registration'
    label = 'Start course registration'
    ac_prefix = 'SFE'
    notice = ''
    pnav = 4
    buttonname = 'Start course registration now'

    def update(self, SUBMIT=None):
        if not self.context.getStudent().state in (CLEARED,RETURNING):
            self.flash("Wrong state.")
            self.redirect(self.url(self.context))
            return
        self.ac_series = self.request.form.get('ac_series', None)
        self.ac_number = self.request.form.get('ac_number', None)

        if SUBMIT is None:
            return
        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
        code = get_access_code(pin)
        if not code:
            self.flash('Activation code is invalid.')
            return
        # Mark pin as used (this also fires a pin related transition)
        # and fire transition start_clearance
        if code.state == USED:
            self.flash('Activation code has already been used.')
            return
        else:
            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
            # Here we know that the ac is in state initialized so we do not
            # expect an exception, but the owner might be different
            if not invalidate_accesscode(
                pin,comment,self.context.getStudent().student_id):
                self.flash('You are not the owner of this access code.')
                return
        if self.context.getStudent().state == CLEARED:
            IWorkflowInfo(self.context.getStudent()).fireTransition(
                'pay_first_school_fee')
        elif self.context.getStudent().state == RETURNING:
            IWorkflowInfo(self.context.getStudent()).fireTransition(
                'pay_school_fee')
        self.flash('Course registration has been started.')
        self.redirect(self.url(self.context))
        return


class AddStudyLevelActionButton(AddActionButton):
    grok.order(1)
    grok.context(IStudentStudyCourse)
    grok.view(StudyCourseDisplayFormPage)
    grok.require('waeup.handleStudent')
    text = 'Add course list'
    target = 'add'

    @property
    def target_url(self):
        student = self.view.context.getStudent()
        condition1 = student.state != 'school fee paid'
        condition2 = str(student['studycourse'].current_level) in \
            self.view.context.keys()
        if condition1 or condition2:
            return ''
        return self.view.url(self.view.context, self.target)

class AddStudyLevelFormPage(WAeUPEditFormPage):
    """ Page for students to add current study levels
    """
    grok.context(IStudentStudyCourse)
    grok.name('add')
    grok.require('waeup.handleStudent')
    grok.template('studyleveladdpage')
    form_fields = grok.AutoFields(IStudentStudyCourse)
    title = 'Study Course'
    pnav = 4

    @property
    def label(self):
        studylevelsource = StudyLevelSource().factory
        code = self.context.current_level
        title = studylevelsource.getTitle(self.context, code)
        return 'Add current level %s' % title

    def emitLockMessage(self):
        self.flash('The requested form is locked (read-only).')
        self.redirect(self.url(self.context))
        return

    def update(self):
        if self.context.getStudent().state != 'school fee paid':
            self.emitLockMessage()
            return
        super(AddStudyLevelFormPage, self).update()
        return

    @grok.action('Create course list now')
    def addStudyLevel(self, **data):
        studylevel = StudentStudyLevel()
        studylevel.level = self.context.current_level
        studylevel.level_session = self.context.current_session
        try:
            self.context.addStudentStudyLevel(
                self.context.certificate,studylevel)
        except KeyError:
            self.flash('This level exists.')
        self.redirect(self.url(self.context))
        return

class StudyLevelEditActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IStudentStudyLevel)
    grok.view(StudyLevelDisplayFormPage)
    grok.require('waeup.handleStudent')
    text = 'Add and remove courses'
    target = 'edit'

    @property
    def target_url(self):
        student = self.view.context.getStudent()
        condition1 = student.state != 'school fee paid'
        condition2 = student[
            'studycourse'].current_level != self.view.context.level
        if condition1 or condition2:
            return ''
        return self.view.url(self.view.context, self.target)

class StudyLevelEditFormPage(WAeUPEditFormPage):
    """ Page to edit the student study level data by students
    """
    grok.context(IStudentStudyLevel)
    grok.name('edit')
    grok.require('waeup.handleStudent')
    grok.template('studyleveleditpage')
    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
        'level_session', 'level_verdict')
    pnav = 4

    def update(self):
        super(StudyLevelEditFormPage, self).update()
    #    tabs.need()
        datatable.need()
        return

    @property
    def title(self):
        return 'Study Level %s' % self.context.level_title

    @property
    def label(self):
        return 'Add and remove course tickets of study level %s' % self.context.level_title

    @property
    def total_credits(self):
        total_credits = 0
        for key, val in self.context.items():
            total_credits += val.credits
        return total_credits

    @grok.action('Add course ticket')
    def addCourseTicket(self, **data):
        self.redirect(self.url(self.context, 'ctadd'))

    @grok.action('Remove selected tickets')
    def delCourseTicket(self, **data):
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash('No ticket selected.')
            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 not self.context[id].core_or_elective:
                try:
                    del self.context[id]
                    deleted.append(id)
                except:
                    self.flash('Could not delete %s: %s: %s' % (
                            id, sys.exc_info()[0], sys.exc_info()[1]))
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context, u'@@edit'))
        return

    @grok.action('Register course list')
    def register_courses(self, **data):
        state = IWorkflowState(self.context.getStudent()).getState()
        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
        self.flash('Course list has been registered.')
        self.redirect(self.url(self.context))
        return

class CourseTicketAddFormPage2(CourseTicketAddFormPage):
    """Add a course ticket by student.
    """
    grok.name('ctadd')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
        'grade', 'score', 'core_or_elective', 'automatic')

    @grok.action('Add course ticket')
    def addCourseTicket(self, **data):
        ticket = CourseTicket()
        course = data['course']
        ticket.automatic = False
        ticket.code = course.code
        ticket.title = course.title
        ticket.faculty = course.__parent__.__parent__.__parent__.title
        ticket.department = course.__parent__.__parent__.title
        ticket.credits = course.credits
        ticket.passmark = course.passmark
        ticket.semester = course.semester
        try:
            self.context.addCourseTicket(ticket)
        except KeyError:
            self.flash('The ticket exists.')
            return
        self.flash('Successfully added %s.' % ticket.code)
        self.redirect(self.url(self.context, u'@@edit'))
        return
