## $Id: browser.py 7745 2012-03-02 06:53:59Z henrik $
##
## 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 sys
import grok
from urllib import urlencode
from time import time
from datetime import datetime
from zope.event import notify
from zope.i18n import translate
from zope.catalog.interfaces import ICatalog
from zope.component import queryUtility, getUtility, createObject
from zope.formlib.textwidgets import BytesDisplayWidget
from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
from waeup.sirp.accesscodes import (
    invalidate_accesscode, get_access_code, create_accesscode)
from waeup.sirp.accesscodes.workflow import USED
from waeup.sirp.browser import (
    SIRPPage, SIRPEditFormPage, SIRPAddFormPage, SIRPDisplayFormPage,
    ContactAdminForm, SIRPForm)
from waeup.sirp.browser.interfaces import ICaptchaManager
from waeup.sirp.browser.breadcrumbs import Breadcrumb
from waeup.sirp.browser.resources import datepicker, datatable, tabs, warning
from waeup.sirp.browser.layout import jsaction, action, UtilityView
from waeup.sirp.interfaces import (
    ISIRPObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm,
    ISIRPUtils, IUniversity)
from waeup.sirp.interfaces import MessageFactory as _
from waeup.sirp.widgets.datewidget import (
    FriendlyDateWidget, FriendlyDateDisplayWidget,
    FriendlyDatetimeDisplayWidget)
from waeup.sirp.students.interfaces import (
    IStudentsContainer, IStudent, IStudentClearance,
    IStudentPersonal, IStudentBase, IStudentStudyCourse,
    IStudentAccommodation, IStudentStudyLevel,
    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
    IStudentOnlinePayment, IBedTicket, IStudentsUtils, IStudentChangePassword
    )
from waeup.sirp.students.catalog import search
from waeup.sirp.students.workflow import (ADMITTED, PAID,
    CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED)
from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
from waeup.sirp.students.vocabularies import StudyLevelSource
from waeup.sirp.browser.resources import toggleall
from waeup.sirp.hostels.hostel import NOT_OCCUPIED
from waeup.sirp.utils.helpers import get_current_principal

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 pages
def msave(view, **data):
    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())
    # Inform catalog if certificate has changed
    # (applyData does this only for the context)
    if 'certificate' in changed_fields:
        notify(grok.ObjectModifiedEvent(view.context.getStudent()))
    fields_string = ' + '.join(changed_fields)
    view.flash(_('Form has been saved.'))
    if fields_string:
        write_log_message(view, 'saved: %s' % fields_string)
    return

def emit_lock_message(view):
    """Flash a lock message.
    """
    view.flash(_('The requested form is locked (read-only).'))
    view.redirect(view.url(view.context))
    return

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

    @property
    def target(self):
        user = get_current_principal()
        if getattr(user, 'user_type', None) == 'student':
            return None
        return self.viewname

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

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

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

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

class OnlinePaymentBreadcrumb(Breadcrumb):
    """A breadcrumb for payments.
    """
    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 = _('Accommodation')

class BedTicketBreadcrumb(Breadcrumb):
    """A breadcrumb for bed tickets.
    """
    grok.context(IBedTicket)

    @property
    def title(self):
        return _('Bed Ticket ${a}',
            mapping = {'a':self.context.getSessionString()})

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

    @property
    def title(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        # There is no request attribute, thus we can only translate
        # to the default (portal) language
        return translate(self.context.level_title, 'waeup.sirp',
            target_language=portal_language)

class StudentsContainerPage(SIRPPage):
    """The standard view for student containers.
    """
    grok.context(IStudentsContainer)
    grok.name('index')
    grok.require('waeup.viewStudentsContainer')
    grok.template('containerpage')
    label = _('Student Section')
    search_button = _('Search')
    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
        if self.searchtype == 'current_session':
            self.searchterm = int(self.searchterm)
        self.hitlist = search(query=self.searchterm,
            searchtype=self.searchtype, view=self)
        if not self.hitlist:
            self.flash('No student found.')
        return

class StudentsContainerManagePage(SIRPPage):
    """The manage page for student containers.
    """
    grok.context(IStudentsContainer)
    grok.name('manage')
    grok.require('waeup.manageStudent')
    grok.template('containermanagepage')
    pnav = 4
    label = _('Manage student section')
    search_button = _('Search')
    remove_button = _('Remove selected')

    def update(self, *args, **kw):
        datatable.need()
        toggleall.need()
        warning.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.'))
            if 'remove' in form:
                self.flash(_('No item selected.'))
            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: ${a}',
                mapping = {'a':', '.join(deleted)}))
        return

class StudentAddFormPage(SIRPAddFormPage):
    """Add-form to add a student.
    """
    grok.context(IStudentsContainer)
    grok.require('waeup.manageStudent')
    grok.name('addstudent')
    form_fields = grok.AutoFields(IStudent).select(
        'firstname', 'middlename', 'lastname', 'reg_number')
    label = _('Add student')
    pnav = 4

    @action(_('Create student record'), style='primary')
    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(SIRPDisplayFormPage):
    """ 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

    @property
    def label(self):
        return _('${a}: Base Data',
            mapping = {'a':self.context.display_fullname})

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

class ContactStudentForm(ContactAdminForm):
    grok.context(IStudent)
    grok.name('contactstudent')
    grok.require('waeup.viewStudent')
    pnav = 4
    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')

    def update(self, subject=u''):
        self.form_fields.get('subject').field.default = subject
        self.subject = subject
        return

    def label(self):
        return _(u'Send message to ${a}',
            mapping = {'a':self.context.display_fullname})

    @action('Send message now', style='primary')
    def send(self, *args, **data):
        try:
            email = self.request.principal.email
        except AttributeError:
            email = self.config.email_admin
        usertype = getattr(self.request.principal,
                           'user_type', 'system').title()
        sirp_utils = getUtility(ISIRPUtils)
        success = sirp_utils.sendContactForm(
                self.request.principal.title,email,
                self.context.display_fullname,self.context.email,
                self.request.principal.id,usertype,
                self.config.name,
                data['body'],data['subject'])
        if success:
            self.flash(_('Your message has been sent.'))
        else:
            self.flash(_('An smtp server error occurred.'))
        return

class StudentBaseManageFormPage(SIRPEditFormPage):
    """ View to manage student base data
    """
    grok.context(IStudent)
    grok.name('manage_base')
    grok.require('waeup.manageStudent')
    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
    grok.template('basemanagepage')
    label = _('Manage base data')
    pnav = 4

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        tabs.need()
        self.tab1 = self.tab2 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        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]

    @action(_('Save'), style='primary')
    def save(self, **data):
        form = self.request.form
        password = form.get('password', None)
        password_ctl = form.get('control_password', None)
        if password:
            validator = getUtility(IPasswordValidator)
            errors = validator.validate_password(password, password_ctl)
            if errors:
                self.flash( ' '.join(errors))
                return
        changed_fields = self.applyData(self.context, **data)
        # Turn list of lists into single list
        if changed_fields:
            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
        else:
            changed_fields = []
        if password:
            # Now we know that the form has no errors and can set password ...
            IUserAccount(self.context).setPassword(password)
            changed_fields.append('password')
        # ... and execute transition
        if form.has_key('transition') and form['transition']:
            transition_id = form['transition']
            self.wf_info.fireTransition(transition_id)
        fields_string = ' + '.join(changed_fields)
        self.flash(_('Form has been saved.'))
        if fields_string:
            write_log_message(self, 'saved: % s' % fields_string)
        return

class StudentClearanceDisplayFormPage(SIRPDisplayFormPage):
    """ 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')
    pnav = 4

    @property
    def label(self):
        return _('${a}: Clearance Data',
            mapping = {'a':self.context.display_fullname})

class ExportPDFClearanceSlipPage(grok.View):
    """Deliver a PDF slip of the context.
    """
    grok.context(IStudent)
    grok.name('clearance.pdf')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    prefix = 'form'

    @property
    def title(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Clearance Data'), 'waeup.sirp',
            target_language=portal_language)

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Clearance Slip of '),
            'waeup.sirp', target_language=portal_language) \
            + ' %s' % self.context.display_fullname

    def render(self):
        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
            self.request)
        students_utils = getUtility(IStudentsUtils)
        return students_utils.renderPDF(
            self, 'clearance.pdf',
            self.context.getStudent(), studentview)

class StudentClearanceManageFormPage(SIRPEditFormPage):
    """ Page to edit student clearance data
    """
    grok.context(IStudent)
    grok.name('edit_clearance')
    grok.require('waeup.manageStudent')
    grok.template('clearanceeditpage')
    form_fields = grok.AutoFields(IStudentClearance)
    label = _('Manage 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.
        tabs.need()
        self.tab1 = self.tab2 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        return super(StudentClearanceManageFormPage, self).update()

    @action(_('Save'), style='primary')
    def save(self, **data):
        msave(self, **data)
        return

class StudentClearPage(UtilityView, grok.View):
    """ Clear student by clearance officer
    """
    grok.context(IStudent)
    grok.name('clear')
    grok.require('waeup.clearStudent')

    def update(self):
        if self.context.state == REQUESTED:
            IWorkflowInfo(self.context).fireTransition('clear')
            self.flash(_('Student has been cleared.'))
        else:
            self.flash(_('Student is in wrong state.'))
        self.redirect(self.url(self.context,'view_clearance'))
        return

    def render(self):
        return

class StudentRejectClearancePage(UtilityView, grok.View):
    """ Reject clearance by clearance officers
    """
    grok.context(IStudent)
    grok.name('reject_clearance')
    grok.require('waeup.clearStudent')

    def update(self):
        if self.context.state == CLEARED:
            IWorkflowInfo(self.context).fireTransition('reset4')
            message = _('Clearance has been annulled.')
            self.flash(message)
        elif self.context.state == REQUESTED:
            IWorkflowInfo(self.context).fireTransition('reset3')
            message = _('Clearance request has been rejected.')
            self.flash(message)
        else:
            self.flash(_('Student is in wrong state.'))
            self.redirect(self.url(self.context,'view_clearance'))
            return
        args = {'subject':message}
        self.redirect(self.url(self.context) +
            '/contactstudent?%s' % urlencode(args))
        return

    def render(self):
        return

class StudentPersonalDisplayFormPage(SIRPDisplayFormPage):
    """ Page to display student personal data
    """
    grok.context(IStudent)
    grok.name('view_personal')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentPersonal)
    form_fields['perm_address'].custom_widget = BytesDisplayWidget
    pnav = 4

    @property
    def label(self):
        return _('${a}: Personal Data',
            mapping = {'a':self.context.display_fullname})

class StudentPersonalManageFormPage(SIRPEditFormPage):
    """ 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')
    pnav = 4

    @action(_('Save'), style='primary')
    def save(self, **data):
        msave(self, **data)
        return

class StudyCourseDisplayFormPage(SIRPDisplayFormPage):
    """ 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')
    pnav = 4

    @property
    def label(self):
        return _('${a}: Study Course',
            mapping = {'a':self.context.__parent__.display_fullname})

    @property
    def current_mode(self):
        if self.context.certificate is not None:
            studymodes_dict = getUtility(ISIRPUtils).getStudyModesDict()
            return studymodes_dict[self.context.certificate.study_mode]
        return

    @property
    def department(self):
        if self.context.certificate is not None:
            return self.context.certificate.__parent__.__parent__
        return

    @property
    def faculty(self):
        if self.context.certificate is not None:
            return self.context.certificate.__parent__.__parent__.__parent__
        return

class StudyCourseManageFormPage(SIRPEditFormPage):
    """ Page to edit the student study course data
    """
    grok.context(IStudentStudyCourse)
    grok.name('manage')
    grok.require('waeup.manageStudent')
    grok.template('studycoursemanagepage')
    form_fields = grok.AutoFields(IStudentStudyCourse)
    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()
        self.tab1 = self.tab2 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()
        return

    @action(_('Save'), style='primary')
    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))

    @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)
            self.flash(_('Study level has been added.'))
        except KeyError:
            self.flash(_('This level exists.'))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
        return

    @jsaction(_('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')+'?tab2')
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            del self.context[id]
            deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a':', '.join(deleted)}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
        return

class StudyLevelDisplayFormPage(SIRPDisplayFormPage):
    """ 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

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

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        level_title = translate(self.context.level_title, 'waeup.sirp',
            target_language=lang)
        return _('${a}: Study Level ${b}', mapping = {
            'a':self.context.getStudent().display_fullname,
            'b':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 ExportPDFCourseRegistrationSlipPage(UtilityView, grok.View):
    """Deliver a PDF slip of the context.
    """
    grok.context(IStudentStudyLevel)
    grok.name('course_registration.pdf')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentStudyLevel)
    prefix = 'form'

    @property
    def title(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Level Data'), 'waeup.sirp',
            target_language=portal_language)

    @property
    def content_title(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Course List'), 'waeup.sirp',
            target_language=portal_language)

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        level_title = translate(self.context.level_title, 'waeup.sirp',
            target_language=lang)
        return translate(_('Course Registration Slip  '),
            'waeup.sirp', target_language=portal_language) \
            + ' %s' % level_title

    def render(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        Sem = translate(_('Sem.'), 'waeup.sirp', target_language=portal_language)
        Code = translate(_('Code'), 'waeup.sirp', target_language=portal_language)
        Title = translate(_('Title'), 'waeup.sirp', target_language=portal_language)
        Dept = translate(_('Dept.'), 'waeup.sirp', target_language=portal_language)
        Faculty = translate(_('Faculty'), 'waeup.sirp', target_language=portal_language)
        Cred = translate(_('Cred.'), 'waeup.sirp', target_language=portal_language)
        Mand = translate(_('Mand.'), 'waeup.sirp', target_language=portal_language)
        Score = translate(_('Score'), 'waeup.sirp', target_language=portal_language)
        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
            self.request)
        students_utils = getUtility(IStudentsUtils)
        tabledata = sorted(self.context.values(),
            key=lambda value: str(value.semester) + value.code)
        return students_utils.renderPDF(
            self, 'course_registration.pdf',
            self.context.getStudent(), studentview,
            tableheader=[(Sem,'semester', 1.5),(Code,'code', 2.5),
                         (Title,'title', 5),
                         (Dept,'dcode', 1.5), (Faculty,'fcode', 1.5),
                         (Cred, 'credits', 1.5),
                         (Mand, 'mandatory', 1.5),
                         (Score, 'score', 1.5),('Auto', 'automatic', 1.5)
                         ],
            tabledata=tabledata)

class StudyLevelManageFormPage(SIRPEditFormPage):
    """ Page to edit the student study level data
    """
    grok.context(IStudentStudyLevel)
    grok.name('manage')
    grok.require('waeup.manageStudent')
    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()
        self.tab1 = self.tab2 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()
        return

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        level_title = translate(self.context.level_title, 'waeup.sirp',
            target_language=lang)
        return _('Manage study level ${a}',
            mapping = {'a':level_title})

    @action(_('Save'), style='primary')
    def save(self, **data):
        msave(self, **data)
        return

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

    @jsaction(_('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')+'?tab2')
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            del self.context[id]
            deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a':', '.join(deleted)}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
        return

class ValidateCoursesPage(UtilityView, grok.View):
    """ Validate course list by course adviser
    """
    grok.context(IStudentStudyLevel)
    grok.name('validate_courses')
    grok.require('waeup.validateStudent')

    def update(self):
        if str(self.context.__parent__.current_level) != self.context.__name__:
            self.flash(_('This level does not correspond current level.'))
        elif self.context.getStudent().state == REGISTERED:
            IWorkflowInfo(self.context.getStudent()).fireTransition(
                'validate_courses')
            self.flash(_('Course list has been validated.'))
        else:
            self.flash(_('Student is in the wrong state.'))
        self.redirect(self.url(self.context))
        return

    def render(self):
        return

class RejectCoursesPage(UtilityView, grok.View):
    """ Reject course list by course adviser
    """
    grok.context(IStudentStudyLevel)
    grok.name('reject_courses')
    grok.require('waeup.validateStudent')

    def update(self):
        if str(self.context.__parent__.current_level) != self.context.__name__:
            self.flash(_('This level does not correspond current level.'))
            self.redirect(self.url(self.context))
            return
        elif self.context.getStudent().state == VALIDATED:
            IWorkflowInfo(self.context.getStudent()).fireTransition('reset8')
            message = _('Course list request has been annulled.')
            self.flash(message)
        elif self.context.getStudent().state == REGISTERED:
            IWorkflowInfo(self.context.getStudent()).fireTransition('reset7')
            message = _('Course list request has been rejected:')
            self.flash(message)
        else:
            self.flash(_('Student is in the wrong state.'))
            self.redirect(self.url(self.context))
            return
        args = {'subject':message}
        self.redirect(self.url(self.context.getStudent()) +
            '/contactstudent?%s' % urlencode(args))
        return

    def render(self):
        return

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

    @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.fcode = course.__parent__.__parent__.__parent__.code
        ticket.dcode = course.__parent__.__parent__.code
        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 ${a}.',
            mapping = {'a':ticket.code}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
        return

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

class CourseTicketDisplayFormPage(SIRPDisplayFormPage):
    """ 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 label(self):
        return _('${a}: Course Ticket ${b}', mapping = {
            'a':self.context.getStudent().display_fullname,
            'b':self.context.code})

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

    @property
    def label(self):
        return _('Manage course ticket ${a}', mapping = {'a':self.context.code})

    @action('Save', style='primary')
    def save(self, **data):
        msave(self, **data)
        return

class PaymentsManageFormPage(SIRPEditFormPage):
    """ Page to manage the student payments

    This manage form page is for both students and students officers.
    """
    grok.context(IStudentPaymentsContainer)
    grok.name('index')
    grok.require('waeup.payStudent')
    form_fields = grok.AutoFields(IStudentPaymentsContainer)
    grok.template('paymentsmanagepage')
    pnav = 4

    def unremovable(self, ticket):
        usertype = getattr(self.request.principal, 'user_type', None)
        if not usertype:
            return False
        return (self.request.principal.user_type == 'student' and ticket.r_code)

    @property
    def label(self):
        return _('${a}: Payments',
            mapping = {'a':self.context.__parent__.display_fullname})

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

    @jsaction(_('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 used payment tickets
            if not self.unremovable(self.context[id]):
                del self.context[id]
                deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a': ', '.join(deleted)}))
            write_log_message(self,'removed: % s' % ', '.join(deleted))
        self.redirect(self.url(self.context))
        return

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

class OnlinePaymentAddFormPage(SIRPAddFormPage):
    """ Page to add an online payment ticket
    """
    grok.context(IStudentPaymentsContainer)
    grok.name('addop')
    grok.require('waeup.payStudent')
    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
        'p_category')
    label = _('Add online payment')
    pnav = 4

    @action(_('Create ticket'), style='primary')
    def createTicket(self, **data):
        p_category = data['p_category']
        student = self.context.__parent__
        if p_category == 'bed_allocation' and student[
            'studycourse'].current_session != grok.getSite()[
            'configuration'].accommodation_session:
                self.flash(
                    _('Your current session does not match ' + \
                    'accommodation session.'))
                self.redirect(self.url(self.context))
                return
        students_utils = getUtility(IStudentsUtils)
        pay_details  = students_utils.getPaymentDetails(
            p_category,student)
        if pay_details['error']:
            self.flash(pay_details['error'])
            self.redirect(self.url(self.context))
            return
        p_item = pay_details['p_item']
        p_session = pay_details['p_session']
        for key in self.context.keys():
            ticket = self.context[key]
            if ticket.p_state == 'paid' and\
               ticket.p_category == p_category and \
               ticket.p_item == p_item and \
               ticket.p_session == p_session:
                  self.flash(
                      _('This type of payment has already been made.'))
                  self.redirect(self.url(self.context))
                  return
        payment = createObject(u'waeup.StudentOnlinePayment')
        self.applyData(payment, **data)
        timestamp = "%d" % int(time()*1000)
        payment.p_id = "p%s" % timestamp
        payment.p_item = p_item
        payment.p_session = p_session
        payment.amount_auth = pay_details['amount']
        payment.surcharge_1 = pay_details['surcharge_1']
        payment.surcharge_2 = pay_details['surcharge_2']
        payment.surcharge_3 = pay_details['surcharge_3']
        self.context[payment.p_id] = payment
        self.flash(_('Payment ticket created.'))
        self.redirect(self.url(self.context))
        return

class OnlinePaymentDisplayFormPage(SIRPDisplayFormPage):
    """ 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 label(self):
        return _('${a}: Online Payment Ticket ${b}', mapping = {
            'a':self.context.getStudent().display_fullname,
            'b':self.context.p_id})

class OnlinePaymentCallbackPage(UtilityView, 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):
        if self.context.p_state == 'paid':
            self.flash(_('This ticket has already been paid.'))
            return
        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. ${a}',
                    mapping = {'a':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. ${a}',
                    mapping = {'a':error}))
                return
            self.context.ac = pin
        elif self.context.p_category == 'bed_allocation':
            # Create HOS access code
            pin, error = create_accesscode('HOS',0,student.student_id)
            if error:
                self.flash(_('Valid callback received. ${a}',
                    mapping = {'a':error}))
                return
            self.context.ac = pin
        self.flash(_('Valid callback received.'))
        return

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

class ExportPDFPaymentSlipPage(UtilityView, grok.View):
    """Deliver a PDF slip of the context.
    """
    grok.context(IStudentOnlinePayment)
    grok.name('payment_receipt.pdf')
    grok.require('waeup.viewStudent')
    form_fields = grok.AutoFields(IStudentOnlinePayment)
    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
    prefix = 'form'
    title = 'Payment Data'

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

    def render(self):
        if self.context.p_state != 'paid':
            self.flash('Ticket not yet paid.')
            self.redirect(self.url(self.context))
            return
        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
            self.request)
        students_utils = getUtility(IStudentsUtils)
        return students_utils.renderPDF(self, 'payment_receipt.pdf',
            self.context.getStudent(), studentview)


class AccommodationManageFormPage(SIRPEditFormPage):
    """ Page to manage bed tickets.

    This manage form page is for both students and students officers.
    """
    grok.context(IStudentAccommodation)
    grok.name('index')
    grok.require('waeup.handleAccommodation')
    form_fields = grok.AutoFields(IStudentAccommodation)
    grok.template('accommodationmanagepage')
    pnav = 4
    officers_only_actions = [_('Remove selected')]

    @property
    def label(self):
        return _('${a}: Accommodation',
            mapping = {'a':self.context.__parent__.display_fullname})

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

    @jsaction(_('Remove selected'))
    def delBedTickets(self, **data):
        if getattr(self.request.principal, 'user_type', None) == 'student':
            self.flash(_('You are not allowed to remove bed tickets.'))
            self.redirect(self.url(self.context))
            return
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash(_('No bed ticket selected.'))
            self.redirect(self.url(self.context))
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            del self.context[id]
            deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a':', '.join(deleted)}))
            write_log_message(self,'removed: % s' % ', '.join(deleted))
        self.redirect(self.url(self.context))
        return

    @property
    def selected_actions(self):
        if getattr(self.request.principal, 'user_type', None) == 'student':
            return [action for action in self.actions
                    if not action.label in self.officers_only_actions]
        return self.actions

class BedTicketAddPage(SIRPPage):
    """ Page to add an online payment ticket
    """
    grok.context(IStudentAccommodation)
    grok.name('add')
    grok.require('waeup.handleAccommodation')
    grok.template('enterpin')
    ac_prefix = 'HOS'
    label = _('Add bed ticket')
    pnav = 4
    buttonname = _('Create bed ticket')
    notice = ''

    def update(self, SUBMIT=None):
        student = self.context.getStudent()
        students_utils = getUtility(IStudentsUtils)
        acc_details  = students_utils.getAccommodationDetails(student)
        if not acc_details:
            self.flash(_("Your data are incomplete."))
            self.redirect(self.url(self.context))
            return
        if not student.state in acc_details['allowed_states']:
            self.flash(_("You are in the wrong registration state."))
            self.redirect(self.url(self.context))
            return
        if student['studycourse'].current_session != acc_details[
            'booking_session']:
            self.flash(
                _('Your current session does not match accommodation session.'))
            self.redirect(self.url(self.context))
            return
        if str(acc_details['booking_session']) in self.context.keys():
            self.flash(
                _('You already booked a bed space in current ' \
                    + 'accommodation session.'))
            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
        # Search and book bed
        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
        entries = cat.searchResults(
            owner=(student.student_id,student.student_id))
        if len(entries):
            # If bed space has bee manually allocated use this bed
            bed = [entry for entry in entries][0]
        else:
            # else search for other available beds
            entries = cat.searchResults(
                bed_type=(acc_details['bt'],acc_details['bt']))
            available_beds = [
                entry for entry in entries if entry.owner == NOT_OCCUPIED]
            if available_beds:
                students_utils = getUtility(IStudentsUtils)
                bed = students_utils.selectBed(available_beds)
                bed.bookBed(student.student_id)
            else:
                self.flash(_('There is no free bed in your category ${a}.',
                    mapping = {'a':acc_details['bt']}))
                return
        # Mark pin as used (this also fires a pin related transition)
        if code.state == USED:
            self.flash(_('Activation code has already been used.'))
            return
        else:
            comment = _(u'invalidated')
            # 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
        # Create bed ticket
        bedticket = createObject(u'waeup.BedTicket')
        bedticket.booking_code = pin
        bedticket.booking_session = acc_details['booking_session']
        bedticket.bed_type = acc_details['bt']
        bedticket.bed = bed
        hall_title = bed.__parent__.hostel_name
        coordinates = bed.getBedCoordinates()[1:]
        block, room_nr, bed_nr = coordinates
        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
            'a':hall_title, 'b':block,
            'c':room_nr, 'd':bed_nr,
            'e':bed.bed_type})
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        bedticket.bed_coordinates = translate(
            bc, 'waeup.sirp',target_language=portal_language)
        key = str(acc_details['booking_session'])
        self.context[key] = bedticket
        self.flash(_('Bed ticket created and bed booked: ${a}',
            mapping = {'a':bedticket.bed_coordinates}))
        self.redirect(self.url(self.context))
        return

class BedTicketDisplayFormPage(SIRPDisplayFormPage):
    """ Page to display bed tickets
    """
    grok.context(IBedTicket)
    grok.name('index')
    grok.require('waeup.handleAccommodation')
    form_fields = grok.AutoFields(IBedTicket)
    form_fields[
        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    pnav = 4

    @property
    def label(self):
        return _('Bed Ticket for Session ${a}',
            mapping = {'a':self.context.getSessionString()})

class ExportPDFBedTicketSlipPage(UtilityView, grok.View):
    """Deliver a PDF slip of the context.
    """
    grok.context(IBedTicket)
    grok.name('bed_allocation.pdf')
    grok.require('waeup.handleAccommodation')
    form_fields = grok.AutoFields(IBedTicket)
    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
    prefix = 'form'

    @property
    def title(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Bed Allocation Data'), 'waeup.sirp',
            target_language=portal_language)

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Bed Allocation: '),
            'waeup.sirp', target_language=portal_language) \
            + ' %s' % self.context.bed_coordinates

    def render(self):
        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
            self.request)
        students_utils = getUtility(IStudentsUtils)
        return students_utils.renderPDF(
            self, 'bed_allocation.pdf',
            self.context.getStudent(), studentview)

class BedTicketRelocationPage(UtilityView, grok.View):
    """ Callback view
    """
    grok.context(IBedTicket)
    grok.name('relocate')
    grok.require('waeup.manageHostels')

    # Relocate student if student parameters have changed or the bed_type
    # of the bed has changed
    def update(self):
        student = self.context.getStudent()
        students_utils = getUtility(IStudentsUtils)
        acc_details  = students_utils.getAccommodationDetails(student)
        if self.context.bed != None and \
              'reserved' in self.context.bed.bed_type:
            self.flash(_("Students in reserved beds can't be relocated."))
            self.redirect(self.url(self.context))
            return
        if acc_details['bt'] == self.context.bed_type and \
                self.context.bed != None and \
                self.context.bed.bed_type == self.context.bed_type:
            self.flash(_("Student can't be relocated."))
            self.redirect(self.url(self.context))
            return
        # Search a bed
        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
        entries = cat.searchResults(
            owner=(student.student_id,student.student_id))
        if len(entries) and self.context.bed == None:
            # If booking has been cancelled but other bed space has been
            # manually allocated after cancellation use this bed
            new_bed = [entry for entry in entries][0]
        else:
            # Search for other available beds
            entries = cat.searchResults(
                bed_type=(acc_details['bt'],acc_details['bt']))
            available_beds = [
                entry for entry in entries if entry.owner == NOT_OCCUPIED]
            if available_beds:
                students_utils = getUtility(IStudentsUtils)
                new_bed = students_utils.selectBed(available_beds)
                new_bed.bookBed(student.student_id)
            else:
                self.flash(_('There is no free bed in your category ${a}.',
                    mapping = {'a':acc_details['bt']}))
                self.redirect(self.url(self.context))
                return
        # Release old bed if exists
        if self.context.bed != None:
            self.context.bed.owner = NOT_OCCUPIED
            notify(grok.ObjectModifiedEvent(self.context.bed))
        # Alocate new bed
        self.context.bed_type = acc_details['bt']
        self.context.bed = new_bed
        hall_title = new_bed.__parent__.hostel_name
        coordinates = new_bed.getBedCoordinates()[1:]
        block, room_nr, bed_nr = coordinates
        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
            'a':hall_title, 'b':block,
            'c':room_nr, 'd':bed_nr,
            'e':new_bed.bed_type})
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        self.context.bed_coordinates = translate(
            bc, 'waeup.sirp',target_language=portal_language)
        self.flash(_('Student relocated: ${a}',
            mapping = {'a':self.context.bed_coordinates}))
        self.redirect(self.url(self.context))
        return

    def render(self):
        return

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

    @property
    def label(self):
        return _('${a}: History', mapping = {'a':self.context.display_fullname})

# Pages for students only

class StudentBaseEditFormPage(SIRPEditFormPage):
    """ View to edit student base data
    """
    grok.context(IStudent)
    grok.name('edit_base')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(IStudentBase).select(
        'email', 'phone')
    label = _('Edit base data')
    pnav = 4

    @action(_('Save'), style='primary')
    def save(self, **data):
        msave(self, **data)
        return

class StudentChangePasswordPage(SIRPEditFormPage):
    """ View to manage student base data
    """
    grok.context(IStudent)
    grok.name('change_password')
    grok.require('waeup.handleStudent')
    grok.template('change_password')
    label = _('Change password')
    pnav = 4

    @action(_('Save'), style='primary')
    def save(self, **data):
        form = self.request.form
        password = form.get('change_password', None)
        password_ctl = form.get('change_password_repeat', None)
        if password:
            validator = getUtility(IPasswordValidator)
            errors = validator.validate_password(password, password_ctl)
            if not errors:
                IUserAccount(self.context).setPassword(password)
                write_log_message(self, 'saved: password')
                self.flash(_('Password changed.'))
            else:
                self.flash( ' '.join(errors))
        return

class StudentFilesUploadPage(SIRPPage):
    """ View to upload files by student
    """
    grok.context(IStudent)
    grok.name('change_portrait')
    grok.require('waeup.uploadStudentFile')
    grok.template('filesuploadpage')
    label = _('Upload portrait')
    pnav = 4

    def update(self):
        if self.context.getStudent().state != ADMITTED:
            emit_lock_message(self)
            return
        super(StudentFilesUploadPage, self).update()
        return

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

    @property
    def all_required_fields_filled(self):
        if self.context.email and self.context.phone:
            return True
        return False

    @property
    def portrait_uploaded(self):
        store = getUtility(IExtFileStore)
        if store.getFileByContext(self.context, attr=u'passport.jpg'):
            return True
        return False

    def update(self, SUBMIT=None):
        if not self.context.state == ADMITTED:
            self.flash(_("Wrong state"))
            self.redirect(self.url(self.context))
            return
        if not self.portrait_uploaded:
            self.flash(_("No portrait uploaded."))
            self.redirect(self.url(self.context, 'change_portrait'))
            return
        if not self.all_required_fields_filled:
            self.flash(_("Not all required fields filled."))
            self.redirect(self.url(self.context, 'edit_base'))
            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"invalidated")
            # 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 StudentClearanceEditFormPage(StudentClearanceManageFormPage):
    """ View to edit student clearance data by student
    """
    grok.context(IStudent)
    grok.name('cedit')
    grok.require('waeup.handleStudent')
    form_fields = grok.AutoFields(
        IStudentClearance).omit('clearance_locked')
    label = _('Edit clearance data')
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')

    def update(self):
        if self.context.clearance_locked:
            emit_lock_message(self)
            return
        return super(StudentClearanceEditFormPage, self).update()

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

    def dataNotComplete(self):
        """To be implemented in the customization package.
        """
        return False

    @action(_('Save and request clearance'), style='primary')
    def requestClearance(self, **data):
        self.applyData(self.context, **data)
        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(SIRPPage):
    grok.context(IStudent)
    grok.name('request_clearance')
    grok.require('waeup.handleStudent')
    grok.template('enterpin')
    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 StartCourseRegistrationPage(SIRPPage):
    grok.context(IStudentStudyCourse)
    grok.name('start_course_registration')
    grok.require('waeup.handleStudent')
    grok.template('enterpin')
    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"invalidated")
            # 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 AddStudyLevelFormPage(SIRPEditFormPage):
    """ 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)
    pnav = 4

    @property
    def label(self):
        studylevelsource = StudyLevelSource().factory
        code = self.context.current_level
        title = studylevelsource.getTitle(self.context, code)
        return _('Add current level ${a}', mapping = {'a':title})

    def update(self):
        if self.context.getStudent().state != PAID:
            emit_lock_message(self)
            return
        super(AddStudyLevelFormPage, self).update()
        return

    @action(_('Create course list now'), style='primary')
    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 StudyLevelEditFormPage(SIRPEditFormPage):
    """ 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):
        if self.context.getStudent().state != PAID:
            emit_lock_message(self)
            return
        super(StudyLevelEditFormPage, self).update()
        datatable.need()
        warning.need()
        return

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        level_title = translate(self.context.level_title, 'waeup.sirp',
            target_language=lang)
        return _('Add and remove course tickets of study level ${a}',
            mapping = {'a':level_title})

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

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

    @jsaction(_('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].mandatory:
                del self.context[id]
                deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a':', '.join(deleted)}))
        self.redirect(self.url(self.context, u'@@edit'))
        return

    @action(_('Register course list'), style='primary')
    def RegisterCourses(self, **data):
        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', 'mandatory', 'automatic', 'carry_over')

    def update(self):
        if self.context.getStudent().state != PAID:
            emit_lock_message(self)
            return
        super(CourseTicketAddFormPage2, self).update()
        return

    @action(_('Add course ticket'))
    def addCourseTicket(self, **data):
        # Safety belt
        if self.context.getStudent().state != PAID:
            return
        ticket = CourseTicket()
        course = data['course']
        for name in ['code', 'title', 'credits', 'passmark', 'semester']:
            setattr(ticket, name, getattr(course, name))
        ticket.automatic = False
        try:
            self.context.addCourseTicket(ticket)
        except KeyError:
            self.flash(_('The ticket exists.'))
            return
        self.flash(_('Successfully added ${a}.',
            mapping = {'a':ticket.code}))
        self.redirect(self.url(self.context, u'@@edit'))
        return

class ChangePasswordRequestPage(SIRPForm):
    """Captcha'd page for students to request a password change.
    """
    grok.context(IUniversity)
    grok.name('changepw')
    grok.require('waeup.Anonymous')
    grok.template('changepw')
    label = _('Change my password')
    form_fields = grok.AutoFields(IStudentChangePassword)

    def update(self):
        # Handle captcha
        self.captcha = getUtility(ICaptchaManager).getCaptcha()
        self.captcha_result = self.captcha.verify(self.request)
        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
        return

    @action(_('Get new login credentials'), style='primary')
    def request(self, **data):
        if not self.captcha_result.is_valid:
            # Captcha will display error messages automatically.
            # No need to flash something.
            return
        # Search student
        cat = queryUtility(ICatalog, name='students_catalog')
        reg_number = data['reg_number']
        email = data['email']
        results = cat.searchResults(
            reg_number=(reg_number, reg_number),
            email=(email,email))
        if len(results) == 0:
            self.flash(_('No student record found.'))
            return
        student = list(results)[0]
        # Change password
        sirp_utils = getUtility(ISIRPUtils)
        pwd = sirp_utils.genPassword()
        IUserAccount(student).setPassword(pwd)
        # Send email with new redentials
        msg = _('You have successfully changed your password for the')
        login_url = self.url(grok.getSite(), 'login')
        success = sirp_utils.sendCredentials(
            IUserAccount(student),pwd,login_url,msg)
        if success:
            self.flash(_('An email with your user name and password ' +
                'has been sent to ${a}.', mapping = {'a':email}))
        else:
            self.flash(_('An smtp server error occurred.'))
        return

class SetPasswordPage(SIRPPage):
    grok.context(ISIRPObject)
    grok.name('setpassword')
    grok.require('waeup.Anonymous')
    grok.template('setpassword')
    label = _('Set password for first-time login')
    ac_prefix = 'PWD'
    pnav = 0
    set_button = _('Set')

    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 ${a}',
                mapping = {'a':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"invalidated")
            # Here we know that the ac is in state initialized so we do not
            # expect an exception
            invalidate_accesscode(pin,comment)
            IUserAccount(student).setPassword(self.ac_number)
            student.adm_code = pin
        self.flash(_('Password has been set. Your Student Id is ${a}',
            mapping = {'a':self.student_id}))
        return