## $Id: browser.py 7741 2012-03-01 09:24:27Z 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 basic applicants and related components.
"""
import os
import sys
import grok
from time import time
from datetime import datetime, date
from zope.component import getUtility, createObject, getAdapter
from zope.formlib.form import setUpEditWidgets
from zope.i18n import translate
from hurry.workflow.interfaces import (
    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
from waeup.sirp.applicants.interfaces import (
    IApplicant, IApplicantEdit, IApplicantsRoot,
    IApplicantsContainer, IApplicantsContainerAdd,
    MAX_UPLOAD_SIZE, IApplicantOnlinePayment, IApplicantsUtils
    )
from waeup.sirp.applicants.workflow import INITIALIZED, STARTED, PAID, SUBMITTED
from waeup.sirp.browser import (
    SIRPPage, SIRPEditFormPage, SIRPAddFormPage, SIRPDisplayFormPage,
    DEFAULT_PASSPORT_IMAGE_PATH)
from waeup.sirp.browser.interfaces import ICaptchaManager
from waeup.sirp.browser.breadcrumbs import Breadcrumb
from waeup.sirp.browser.layout import (
    NullValidator, jsaction, action, UtilityView)
from waeup.sirp.browser.pages import add_local_role, del_local_roles
from waeup.sirp.browser.resources import datepicker, tabs, datatable, warning
from waeup.sirp.interfaces import (
    ISIRPObject, ILocalRolesAssignable, IExtFileStore, IPDF,
    IFileStoreNameChooser, IPasswordValidator, IUserAccount, ISIRPUtils)
from waeup.sirp.interfaces import MessageFactory as _
from waeup.sirp.permissions import get_users_with_local_roles
from waeup.sirp.students.interfaces import IStudentsUtils
from waeup.sirp.utils.helpers import string_from_bytes, file_size
from waeup.sirp.widgets.datewidget import (
    FriendlyDateWidget, FriendlyDateDisplayWidget,
    FriendlyDatetimeDisplayWidget)
from waeup.sirp.widgets.phonewidget import PhoneWidget
from waeup.sirp.widgets.restwidget import ReSTDisplayWidget

grok.context(ISIRPObject) # Make ISIRPObject the default context

class ApplicantsRootPage(SIRPPage):
    grok.context(IApplicantsRoot)
    grok.name('index')
    grok.require('waeup.Public')
    label = _('Application Section')
    pnav = 3

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

class ApplicantsRootManageFormPage(SIRPEditFormPage):
    grok.context(IApplicantsRoot)
    grok.name('manage')
    grok.template('applicantsrootmanagepage')
    label = _('Manage application section')
    pnav = 3
    grok.require('waeup.manageApplication')
    taboneactions = [_('Add applicants container'), _('Remove selected'),
        _('Cancel')]
    tabtwoactions1 = [_('Remove selected local roles')]
    tabtwoactions2 = [_('Add local role')]
    subunits = _('Applicants Containers')

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

    def getLocalRoles(self):
        roles = ILocalRolesAssignable(self.context)
        return roles()

    def getUsers(self):
        """Get a list of all users.
        """
        for key, val in grok.getSite()['users'].items():
            url = self.url(val)
            yield(dict(url=url, name=key, val=val))

    def getUsersWithLocalRoles(self):
        return get_users_with_local_roles(self.context)

    @jsaction(_('Remove selected'))
    def delApplicantsContainers(self, **data):
        form = self.request.form
        child_id = form['val_id']
        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: ${a}',
                mapping = {'a':', '.join(deleted)}))
        self.redirect(self.url(self.context, '@@manage'))
        return

    @action(_('Add applicants container'), validator=NullValidator)
    def addApplicantsContainer(self, **data):
        self.redirect(self.url(self.context, '@@add'))
        return

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

    @action(_('Add local role'), validator=NullValidator)
    def addLocalRole(self, **data):
        return add_local_role(self,3, **data)

    @action(_('Remove selected local roles'))
    def delLocalRoles(self, **data):
        return del_local_roles(self,3,**data)

class ApplicantsContainerAddFormPage(SIRPAddFormPage):
    grok.context(IApplicantsRoot)
    grok.require('waeup.manageApplication')
    grok.name('add')
    grok.template('applicantscontaineraddpage')
    label = _('Add applicants container')
    pnav = 3

    form_fields = grok.AutoFields(
        IApplicantsContainerAdd).omit('code').omit('title', 'description_dict')
    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')

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

    @action(_('Add applicants container'))
    def addApplicantsContainer(self, **data):
        year = data['year']
        code = u'%s%s' % (data['prefix'], year)
        appcats_dict = getUtility(IApplicantsUtils).getApplicationTypeDict()
        title = appcats_dict[data['prefix']][0]
        title = u'%s %s/%s' % (title, year, year + 1)
        if code in self.context.keys():
            self.flash(
                _('An applicants container for the same application type and entrance year exists already in the database.'))
            return
        # Add new applicants container...
        provider = data['provider'][1]
        container = provider.factory()
        self.applyData(container, **data)
        container.code = code
        container.title = title
        self.context[code] = container
        self.flash(_('Added:') + ' "%s".' % code)
        self.redirect(self.url(self.context, u'@@manage'))
        return

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

class ApplicantsRootBreadcrumb(Breadcrumb):
    """A breadcrumb for applicantsroot.
    """
    grok.context(IApplicantsRoot)
    title = _(u'Applicants')

class ApplicantsContainerBreadcrumb(Breadcrumb):
    """A breadcrumb for applicantscontainers.
    """
    grok.context(IApplicantsContainer)

class ApplicantBreadcrumb(Breadcrumb):
    """A breadcrumb for applicants.
    """
    grok.context(IApplicant)

    @property
    def title(self):
        """Get a title for a context.
        """
        return self.context.application_number

class OnlinePaymentBreadcrumb(Breadcrumb):
    """A breadcrumb for payments.
    """
    grok.context(IApplicantOnlinePayment)

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

class ApplicantsContainerPage(SIRPDisplayFormPage):
    """The standard view for regular applicant containers.
    """
    grok.context(IApplicantsContainer)
    grok.name('index')
    grok.require('waeup.Public')
    grok.template('applicantscontainerpage')
    pnav = 3

    form_fields = grok.AutoFields(IApplicantsContainer).omit(
        'title', 'description_dict')
    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['description'].custom_widget = ReSTDisplayWidget

    @property
    def introduction(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        html = self.context.description_dict.get(lang,'')
        if html =='':
            html = self.context.description_dict.get(portal_language,'')
        if html =='':
            return ''
        else:
            return html

    @property
    def label(self):
        return "%s" % self.context.title

class ApplicantsContainerManageFormPage(SIRPEditFormPage):
    grok.context(IApplicantsContainer)
    grok.name('manage')
    grok.template('applicantscontainermanagepage')
    form_fields = grok.AutoFields(IApplicantsContainer).omit(
        'title', 'description_dict')
    taboneactions = [_('Save'),_('Cancel')]
    tabtwoactions = [_('Add applicant'), _('Remove selected'),_('Cancel')]
    tabthreeactions1 = [_('Remove selected local roles')]
    tabthreeactions2 = [_('Add local role')]
    # Use friendlier date widget...
    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
    grok.require('waeup.manageApplication')

    @property
    def label(self):
        return _('Manage applicants container')

    pnav = 3

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        tabs.need()
        self.tab1 = self.tab2 = self.tab3 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()  # Enable jQurey datatables for contents listing
        return super(ApplicantsContainerManageFormPage, self).update()

    def getLocalRoles(self):
        roles = ILocalRolesAssignable(self.context)
        return roles()

    def getUsers(self):
        """Get a list of all users.
        """
        for key, val in grok.getSite()['users'].items():
            url = self.url(val)
            yield(dict(url=url, name=key, val=val))

    def getUsersWithLocalRoles(self):
        return get_users_with_local_roles(self.context)

    def _description(self):
        view = ApplicantsContainerPage(
            self.context,self.request)
        view.setUpWidgets()
        return view.widgets['description']()

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

    @jsaction(_('Remove selected'))
    def delApplicant(self, **data):
        form = self.request.form
        if form.has_key('val_id'):
            child_id = form['val_id']
        else:
            self.flash(_('No applicant 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:
            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: ${a}',
                mapping = {'a':', '.join(deleted)}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
        return

    @action(_('Add applicant'), validator=NullValidator)
    def addApplicant(self, **data):
        self.redirect(self.url(self.context, 'addapplicant'))
        return

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

    @action(_('Add local role'), validator=NullValidator)
    def addLocalRole(self, **data):
        return add_local_role(self,3, **data)

    @action(_('Remove selected local roles'))
    def delLocalRoles(self, **data):
        return del_local_roles(self,3,**data)

class ApplicantAddFormPage(SIRPAddFormPage):
    """Add-form to add an applicant.
    """
    grok.context(IApplicantsContainer)
    grok.require('waeup.manageApplication')
    grok.name('addapplicant')
    #grok.template('applicantaddpage')
    form_fields = grok.AutoFields(IApplicant).select(
        'firstname', 'middlename', 'lastname',
        'email', 'phone')
    label = _('Add applicant')
    pnav = 3

    @action(_('Create application record'))
    def addApplicant(self, **data):
        applicant = createObject(u'waeup.Applicant')
        self.applyData(applicant, **data)
        self.context.addApplicant(applicant)
        self.flash(_('Applicant record created.'))
        self.redirect(
            self.url(self.context[applicant.application_number], 'index'))
        return

class ApplicantDisplayFormPage(SIRPDisplayFormPage):
    grok.context(IApplicant)
    grok.name('index')
    grok.require('waeup.viewApplication')
    grok.template('applicantdisplaypage')
    form_fields = grok.AutoFields(IApplicant).omit(
        'locked', 'course_admitted', 'password')
    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    label = _('Applicant')
    pnav = 3

    def update(self):
        self.passport_url = self.url(self.context, 'passport.jpg')
        # Mark application as started if applicant logs in for the first time
        usertype = getattr(self.request.principal, 'user_type', None)
        if usertype == 'applicant' and \
            IWorkflowState(self.context).getState() == INITIALIZED:
            IWorkflowInfo(self.context).fireTransition('start')
        return

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

    @property
    def label(self):
        container_title = self.context.__parent__.title
        return _('${a} Application Record ${b}', mapping = {
            'a':container_title, 'b':self.context.application_number})

    def getCourseAdmitted(self):
        """Return link, title and code in html format to the certificate
           admitted.
        """
        course_admitted = self.context.course_admitted
        if getattr(course_admitted, '__parent__',None):
            url = self.url(course_admitted)
            title = course_admitted.title
            code = course_admitted.code
            return '<a href="%s">%s - %s</a>' %(url,code,title)
        return ''

class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
    grok.context(IApplicant)
    grok.name('base')
    form_fields = grok.AutoFields(IApplicant).select(
        'applicant_id', 'firstname', 'lastname','email', 'course1')

class CreateStudentPage(UtilityView, grok.View):
    """Create a student object from applicatnt data
    and copy applicant object.
    """
    grok.context(IApplicant)
    grok.name('createstudent')
    grok.require('waeup.manageStudent')

    def update(self):
        msg = self.context.createStudent()[1]
        self.flash(msg)
        self.redirect(self.url(self.context))
        return

    def render(self):
        return

class AcceptanceFeePaymentAddPage(UtilityView, grok.View):
    """ Page to add an online payment ticket
    """
    grok.context(IApplicant)
    grok.name('addafp')
    grok.require('waeup.payApplicant')

    def update(self):
        p_category = 'acceptance'
        session = str(self.context.__parent__.year)
        try:
            academic_session = grok.getSite()['configuration'][session]
        except KeyError:
            self.flash(_('Session configuration object is not available.'))
            return
        timestamp = "%d" % int(time()*1000)
        for key in self.context.keys():
            ticket = self.context[key]
            if ticket.p_state == 'paid':
                  self.flash(
                      _('This type of payment has already been made.'))
                  self.redirect(self.url(self.context))
                  return
        payment = createObject(u'waeup.ApplicantOnlinePayment')
        payment.p_id = "p%s" % timestamp
        payment.p_item = self.context.__parent__.title
        payment.p_year = self.context.__parent__.year
        payment.p_category = p_category
        payment.amount_auth = academic_session.acceptance_fee
        payment.surcharge_1 = academic_session.surcharge_1
        payment.surcharge_2 = academic_session.surcharge_2
        payment.surcharge_3 = academic_session.surcharge_3
        self.context[payment.p_id] = payment
        self.flash(_('Payment ticket created.'))
        return

    def render(self):
        usertype = getattr(self.request.principal, 'user_type', None)
        if usertype == 'applicant':
            self.redirect(self.url(self.context, '@@edit'))
            return
        self.redirect(self.url(self.context, '@@manage'))
        return


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

    @property
    def label(self):
        return _('${a}: Online Payment Ticket ${b}', mapping = {
            'a':self.context.__parent__.display_fullname, 'b':self.context.p_id})

class OnlinePaymentCallbackPage(UtilityView, grok.View):
    """ Callback view
    """
    grok.context(IApplicantOnlinePayment)
    grok.name('callback')
    grok.require('waeup.payApplicant')

    # 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):
        self.wf_info = IWorkflowInfo(self.context.__parent__)
        try:
            self.wf_info.fireTransition('pay')
        except InvalidTransitionError:
            self.flash('Error: %s' % sys.exc_info()[1])
            return
        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()
        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
        self.context.__parent__.loggerInfo(
            ob_class, 'valid callback: %s' % self.context.p_id)
        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(IApplicantOnlinePayment)
    grok.name('payment_receipt.pdf')
    grok.require('waeup.viewApplication')
    form_fields = grok.AutoFields(IApplicantOnlinePayment)
    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
    prefix = 'form'

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

    @property
    def label(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        return translate(_('Online Payment Receipt'),
            'waeup.sirp', target_language=portal_language) \
            + ' %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
        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
            self.request)
        students_utils = getUtility(IStudentsUtils)
        return students_utils.renderPDF(self,'payment_receipt.pdf',
            self.context.__parent__, applicantview)

class ExportPDFPage(UtilityView, grok.View):
    """Deliver a PDF slip of the context.
    """
    grok.context(IApplicant)
    grok.name('application_slip.pdf')
    grok.require('waeup.viewApplication')
    form_fields = grok.AutoFields(IApplicant).omit(
        'locked', 'course_admitted')
    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    prefix = 'form'

    #@property
    #def label(self):
    #    portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
    #    container_title = self.context.__parent__.title
    #    label = translate('Application Record',
    #        'waeup.sirp', target_language=portal_language)
    #    return container_title +  label + self.context.application_number

    #def getCourseAdmitted(self):
    #    """Return title and code in html format to the certificate
    #       admitted.
    #    """
    #    course_admitted = self.context.course_admitted
    #    #if ICertificate.providedBy(course_admitted):
    #    if getattr(course_admitted, '__parent__',None):
    #        title = course_admitted.title
    #        code = course_admitted.code
    #        return '%s - %s' %(code,title)
    #    return ''

    #def setUpWidgets(self, ignore_request=False):
    #    self.adapters = {}
    #    self.widgets = setUpEditWidgets(
    #        self.form_fields, self.prefix, self.context, self.request,
    #        adapters=self.adapters, for_display=True,
    #        ignore_request=ignore_request
    #        )

    def render(self):
        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
            view=self)
        self.response.setHeader(
            'Content-Type', 'application/pdf')
        return pdfstream

def handle_img_upload(upload, context, view):
    """Handle upload of applicant image.

    Returns `True` in case of success or `False`.

    Please note that file pointer passed in (`upload`) most probably
    points to end of file when leaving this function.
    """
    size = file_size(upload)
    if size > MAX_UPLOAD_SIZE:
        view.flash(_('Uploaded image is too big!'))
        return False
    dummy, ext = os.path.splitext(upload.filename)
    ext.lower()
    if ext != '.jpg':
        view.flash(_('jpg file extension expected.'))
        return False
    upload.seek(0) # file pointer moved when determining size
    store = getUtility(IExtFileStore)
    file_id = IFileStoreNameChooser(context).chooseName()
    store.createFile(file_id, upload)
    return True

class ApplicantManageFormPage(SIRPEditFormPage):
    """A full edit view for applicant data.
    """
    grok.context(IApplicant)
    grok.name('manage')
    grok.require('waeup.manageApplication')
    form_fields = grok.AutoFields(IApplicant)
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    form_fields['student_id'].for_display = True
    form_fields['applicant_id'].for_display = True
    form_fields['phone'].custom_widget = PhoneWidget
    grok.template('applicanteditpage')
    manage_applications = True
    pnav = 3
    display_actions = [[_('Save'), _('Final Submit')],
        [_('Add online payment ticket'),_('Remove selected tickets')]]

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        warning.need()
        super(ApplicantManageFormPage, self).update()
        self.wf_info = IWorkflowInfo(self.context)
        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
        self.passport_changed = None
        upload = self.request.form.get('form.passport', None)
        if upload:
            # We got a fresh upload
            self.passport_changed = handle_img_upload(
                upload, self.context, self)
        return

    @property
    def label(self):
        container_title = self.context.__parent__.title
        return _('${a} Application Form ${b}', mapping = {
            'a':container_title, 'b':self.context.application_number})

    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
        if self.passport_changed is False:  # False is not None!
            return # error during image upload. Ignore other values
        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 self.passport_changed:
            changed_fields.append('passport')
        if password:
            # Now we know that the form has no errors and can set password ...
            IUserAccount(self.context).setPassword(password)
            changed_fields.append('password')
        fields_string = ' + '.join(changed_fields)
        trans_id = form.get('transition', None)
        if trans_id:
            self.wf_info.fireTransition(trans_id)
        self.flash(_('Form has been saved.'))
        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
        if fields_string:
            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
        return

    def unremovable(self, ticket):
        return False

    # This method is also used by the ApplicantEditFormPage
    def delPaymentTickets(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:
            # Applicants are not allowed to remove used payment 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: ${a}',
                mapping = {'a':', '.join(deleted)}))
            ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
            self.context.loggerInfo(
                ob_class, 'removed: % s' % ', '.join(deleted))
        return

    # We explicitely want the forms to be validated before payment tickets
    # can be created. If no validation is requested, use
    # 'validator=NullValidator' in the action directive
    @action(_('Add online payment ticket'))
    def addPaymentTicket(self, **data):
        self.redirect(self.url(self.context, '@@addafp'))
        return

    @jsaction(_('Remove selected tickets'))
    def removePaymentTickets(self, **data):
        self.delPaymentTickets(**data)
        self.redirect(self.url(self.context) + '/@@manage')
        return

class ApplicantEditFormPage(ApplicantManageFormPage):
    """An applicant-centered edit view for applicant data.
    """
    grok.context(IApplicantEdit)
    grok.name('edit')
    grok.require('waeup.handleApplication')
    form_fields = grok.AutoFields(IApplicantEdit).omit(
        'locked', 'course_admitted', 'student_id',
        'screening_score', 'reg_number'
        )
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    form_fields['phone'].custom_widget = PhoneWidget
    form_fields['applicant_id'].for_display = True
    grok.template('applicanteditpage')
    manage_applications = False

    @property
    def display_actions(self):
        state = IWorkflowState(self.context).getState()
        if state == INITIALIZED:
            actions = [[],[]]
        elif state == STARTED:
            actions = [[_('Save')],
                [_('Add online payment ticket'),_('Remove selected tickets')]]
        elif state == PAID:
            actions = [[_('Save'), _('Final Submit')],
                [_('Remove selected tickets')]]
        else:
            actions = [[],[]]
        return actions

    def unremovable(self, ticket):
        state = IWorkflowState(self.context).getState()
        return ticket.r_code or state in (INITIALIZED, SUBMITTED)

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

    def update(self):
        if self.context.locked:
            self.emit_lock_message()
            return
        super(ApplicantEditFormPage, self).update()
        return

    def dataNotComplete(self):
        store = getUtility(IExtFileStore)
        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
            return _('No passport picture uploaded.')
        if not self.request.form.get('confirm_passport', False):
            return _('Passport picture confirmation box not ticked.')
        return False

    # We explicitely want the forms to be validated before payment tickets
    # can be created. If no validation is requested, use
    # 'validator=NullValidator' in the action directive
    @action(_('Add online payment ticket'))
    def addPaymentTicket(self, **data):
        self.redirect(self.url(self.context, '@@addafp'))
        return

    @jsaction(_('Remove selected tickets'))
    def removePaymentTickets(self, **data):
        self.delPaymentTickets(**data)
        self.redirect(self.url(self.context) + '/@@edit')
        return

    @action(_('Save'))
    def save(self, **data):
        if self.passport_changed is False:  # False is not None!
            return # error during image upload. Ignore other values
        self.applyData(self.context, **data)
        self.flash('Form has been saved.')
        return

    @action(_('Final Submit'))
    def finalsubmit(self, **data):
        if self.passport_changed is False:  # False is not None!
            return # error during image upload. Ignore other values
        if self.dataNotComplete():
            self.flash(self.dataNotComplete())
            return
        self.applyData(self.context, **data)
        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 != PAID:
            self.flash(_('This form cannot be submitted. Wrong state!'))
            return
        IWorkflowInfo(self.context).fireTransition('submit')
        self.context.application_date = datetime.now()
        self.context.locked = True
        self.flash(_('Form has been submitted.'))
        self.redirect(self.url(self.context))
        return

class PassportImage(grok.View):
    """Renders the passport image for applicants.
    """
    grok.name('passport.jpg')
    grok.context(IApplicant)
    grok.require('waeup.viewApplication')

    def render(self):
        # A filename chooser turns a context into a filename suitable
        # for file storage.
        image = getUtility(IExtFileStore).getFileByContext(self.context)
        self.response.setHeader(
            'Content-Type', 'image/jpeg')
        if image is None:
            # show placeholder image
            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
        return image

class ApplicantRegistrationPage(SIRPAddFormPage):
    """Captcha'd registration page for applicants.
    """
    grok.context(IApplicantsContainer)
    grok.name('register')
    grok.require('waeup.Anonymous')
    grok.template('applicantregister')
    form_fields = grok.AutoFields(IApplicantEdit).select(
        'firstname', 'middlename', 'lastname', 'email', 'phone')
    form_fields['phone'].custom_widget = PhoneWidget

    @property
    def label(self):
        return _('Register for ${a} Application',
            mapping = {'a':self.context.title})

    def update(self):
        # Check if application has started ...
        if not self.context.startdate or self.context.startdate > date.today():
            self.flash(_('Application has not yet started.'))
            self.redirect(self.url(self.context))
            return
        # ... or ended
        if not self.context.enddate or self.context.enddate < date.today():
            self.flash(_('Application has ended.'))
            self.redirect(self.url(self.context))
            return
        # 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 login credentials'), style='primary')
    def register(self, **data):
        if not self.captcha_result.is_valid:
            # captcha will display error messages automatically.
            # No need to flash something.
            return
        # Add applicant and create password
        applicant = createObject('waeup.Applicant')
        self.applyData(applicant, **data)
        self.context.addApplicant(applicant)
        sirp_utils = getUtility(ISIRPUtils)
        password = sirp_utils.genPassword()
        IUserAccount(applicant).setPassword(password)
        # Send email with credentials
        login_url = self.url(grok.getSite(), 'login')
        msg = _('You have successfully been registered for the')
        if sirp_utils.sendCredentials(IUserAccount(applicant),
            password, login_url, msg):
            self.redirect(self.url(self.context, 'registration_complete',
                                   data = dict(email=applicant.email)))
            return
        else:
            self.flash(_('Email could not been sent. Please retry later.'))
        return

class ApplicantRegistrationEmailSent(SIRPPage):
    """Landing page after successful registration.
    """
    grok.name('registration_complete')
    grok.require('waeup.Public')
    grok.template('applicantregemailsent')
    label = _('Your registration was successful.')

    def update(self, email=None):
        self.email = email
        return
