##
## browser.py
## Login : <uli@pu.smp.net>
## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
## $Id$
##
## Copyright (C) 2010 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 sys
import grok

from datetime import datetime, date
from zope.authentication.interfaces import ILogout, IAuthentication
from zope.component import getUtility
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.form import setUpEditWidgets
from zope.securitypolicy.interfaces import IPrincipalRoleManager
from zope.traversing.browser import absoluteURL

from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import (Frame, Paragraph, Image,
    Table, Spacer)
from reportlab.platypus.tables import TableStyle

from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
from waeup.sirp.accesscodes.workflow import USED
from waeup.sirp.browser import (
    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
from waeup.sirp.browser.breadcrumbs import Breadcrumb
from waeup.sirp.browser.layout import NullValidator
from waeup.sirp.browser.pages import add_local_role, del_local_roles
from waeup.sirp.browser.resources import datepicker, tabs, datatable
from waeup.sirp.browser.viewlets import (
    ManageActionButton, PrimaryNavTab, LeftSidebarLink
    )
from waeup.sirp.image.browser.widget import (
    ThumbnailWidget, EncodingImageFileWidget,
    )
from waeup.sirp.image.image import createWAeUPImageFile
from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
from waeup.sirp.permissions import get_users_with_local_roles
from waeup.sirp.university.interfaces import ICertificate
from waeup.sirp.widgets.datewidget import (
    FriendlyDateWidget, FriendlyDateDisplayWidget)
from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
from waeup.sirp.widgets.objectwidget import (
    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
from waeup.sirp.applicants.interfaces import (
    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
    )
from waeup.sirp.applicants.workflow import INITIALIZED, STARTED

results_widget = CustomWidgetFactory(
    WAeUPObjectWidget, ResultEntry)

results_display_widget = CustomWidgetFactory(
    WAeUPObjectDisplayWidget, ResultEntry)

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

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

class ManageApplicantsRootActionButton(ManageActionButton):
    grok.context(IApplicantsRoot)
    grok.view(ApplicantsRootPage)
    grok.require('waeup.manageApplications')
    text = 'Manage application section'

class ApplicantsRootManageFormPage(WAeUPEditFormPage):
    grok.context(IApplicantsRoot)
    grok.name('manage')
    grok.template('applicantsrootmanagepage')
    title = 'Applicants'
    label = 'Manage application section'
    pnav = 3
    grok.require('waeup.manageApplications')
    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()
        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)

    # ToDo: Show warning message before deletion
    @grok.action('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: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
        return

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

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

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

    @grok.action('Remove selected local roles')
    def delLocalRoles(self, **data):
        return del_local_roles(self,2,**data)

class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
    grok.context(IApplicantsRoot)
    grok.require('waeup.manageApplications')
    grok.name('add')
    grok.template('applicantscontaineraddpage')
    title = 'Applicants'
    label = 'Add applicants container'
    pnav = 3

    form_fields = grok.AutoFields(
        IApplicantsContainerAdd).omit('code').omit('title')
    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()

    @grok.action('Add applicants container')
    def addApplicantsContainer(self, **data):
        year = data['year']
        code = u'%s%s' % (data['prefix'], year)
        prefix = application_types_vocab.getTerm(data['prefix'])
        title = u'%s %s/%s' % (prefix.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')+'#tab-1')
        return

    @grok.action('Cancel', validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context, '@@manage') + '#tab-1')

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.access_code

class ApplicantsTab(PrimaryNavTab):
    """Applicants tab in primary navigation.
    """

    grok.context(IWAeUPObject)
    grok.order(3)
    grok.require('waeup.Public')
    grok.template('primarynavtab')

    pnav = 3
    tab_title = u'Applicants'

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

class ApplicantsContainerPage(WAeUPDisplayFormPage):
    """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')
    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
    form_fields['description'].custom_widget = ReSTDisplayWidget

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

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

class ApplicantsContainerManageActionButton(ManageActionButton):
    grok.order(1)
    grok.context(IApplicantsContainer)
    grok.view(ApplicantsContainerPage)
    grok.require('waeup.manageApplications')
    text = 'Manage applicants container'

class LoginApplicantActionButton(ManageActionButton):
    grok.order(2)
    grok.context(IApplicantsContainer)
    grok.view(ApplicantsContainerPage)
    grok.require('waeup.Anonymous')
    icon = 'login.png'
    text = 'Login for applicants'
    target = 'login'

class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
    grok.context(IApplicantsContainer)
    grok.name('manage')
    grok.template('applicantscontainermanagepage')
    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
    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.manageApplications')

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

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

    pnav = 3

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        tabs.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)

    @grok.action('Save')
    def apply(self, **data):
        self.applyData(self.context, **data)
        self.flash('Data saved.')
        return

    # ToDo: Show warning message before deletion
    @grok.action('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')+'#tab-2')
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            try:
                del self.context[id]
                deleted.append(id)
            except:
                self.flash('Could not delete %s: %s: %s' % (
                        id, sys.exc_info()[0], sys.exc_info()[1]))
        if len(deleted):
            self.flash('Successfully removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
        return

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

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

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

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

class LoginApplicant(WAeUPPage):
    grok.context(IApplicantsContainer)
    grok.name('login')
    grok.require('waeup.Public')

    @property
    def title(self):
        return u"Applicant Login: %s" % self.context.title

    @property
    def label(self):
        return u"Login for '%s' applicants only" % self.context.title

    pnav = 3

    @property
    def ac_prefix(self):
        return self.context.ac_prefix

    def update(self, SUBMIT=None):
        self.ac_series = self.request.form.get('form.ac_series', None)
        self.ac_number = self.request.form.get('form.ac_number', None)
        if SUBMIT is None:
            return
        if self.request.principal.id == 'zope.anybody':
            self.flash('Entered credentials are invalid.')
            return
        if not IApplicantPrincipal.providedBy(self.request.principal):
            # Don't care if user is already authenticated as non-applicant
            return

        # From here we handle an applicant (not an officer browsing)
        pin = self.request.principal.access_code

        # If application has not yet started,
        # logout without marking AC as used
        if self.context.startdate > date.today():
            self.flash('Application has not yet started.')
            auth = getUtility(IAuthentication)
            ILogout(auth).logout(self.request)
            self.redirect(self.url(self.context, 'login'))
            return

        # If application has ended and applicant record doesn't exist,
        # logout without marking AC as used
        if not pin in self.context.keys() and self.context.enddate < date.today():
            self.flash('Application has ended.')
            auth = getUtility(IAuthentication)
            ILogout(auth).logout(self.request)
            self.redirect(self.url(self.context, 'login'))
            return

        # Mark AC as used (this also fires a pin related transition)
        if get_access_code(pin).state == USED:
            pass
        else:
            comment = u"AC invalidated"
            # Here we know that the ac is in state initialized so we do not
            # expect an exception
            invalidate_accesscode(pin,comment)

        if not pin in self.context.keys():
            # Create applicant record
            applicant = Applicant()
            applicant.access_code = pin
            self.context[pin] = applicant

        role_manager = IPrincipalRoleManager(self.context[pin])
        role_manager.assignRoleToPrincipal(
            'waeup.local.ApplicationOwner', self.request.principal.id)

        # Assign current principal the PortalUser role
        role_manager = IPrincipalRoleManager(grok.getSite())
        role_manager.assignRoleToPrincipal(
            'waeup.PortalUser', self.request.principal.id)

        # Mark application as started
        if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
            IWorkflowInfo(self.context[pin]).fireTransition('start')

        self.redirect(self.url(self.context[pin], 'edit'))
        return

class ApplicantAddFormPage(WAeUPAddFormPage):
    """Add-form to add an applicant.
    """
    grok.context(IApplicantsContainer)
    grok.require('waeup.manageApplications')
    grok.name('addapplicant')
    grok.template('applicantaddpage')
    title = 'Applicants'
    label = 'Add applicant'
    pnav = 3

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

    @property
    def ac_prefix(self):
        return self.context.ac_prefix

    @grok.action('Create application record')
    def addApplicant(self, **data):
        ac_series = self.request.form.get('form.ac_series', None)
        ac_number = self.request.form.get('form.ac_number', None)
        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
        if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
            self.flash('%s is not a valid access code.' % pin)
            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
            return
        else:
            # Create applicant record
            applicant = Applicant()
            applicant.access_code = pin
            self.context[pin] = applicant
        self.redirect(self.url(self.context[pin], 'edit'))
        return

class AccessCodeViewLink(LeftSidebarLink):
    grok.order(1)
    grok.require('waeup.Public')
    icon = 'actionicon_view.png'
    title = 'View Record'
    target = '/@@index'

    @property
    def url(self):
        if not IApplicantPrincipal.providedBy(self.request.principal):
            return ''
        access_code = getattr(self.request.principal,'access_code',None)
        if access_code:
            applicant_object = get_applicant_data(access_code)
            return absoluteURL(applicant_object, self.request) + self.target
        #else:
        #    return ''

class AccessCodeEditLink(AccessCodeViewLink):
    grok.order(2)
    grok.require('waeup.Public')
    icon = 'actionicon_modify.png'
    title = 'Edit Record'
    target = '/@@edit'

    @property
    def url(self):
        if not IApplicantPrincipal.providedBy(self.request.principal):
            return ''
        access_code = getattr(self.request.principal,'access_code',None)
        if access_code:
            applicant_object = get_applicant_data(access_code)
            if applicant_object.locked:
                return ''
            return absoluteURL(applicant_object, self.request) + self.target
        #else:
        #    return ''

class AccessCodeSlipLink(AccessCodeViewLink):
    grok.order(3)
    grok.require('waeup.Public')
    icon = 'actionicon_pdf.png'
    title = 'Download Slip'
    target = '/application_slip.pdf'

class DisplayApplicant(WAeUPDisplayFormPage):
    grok.context(IApplicant)
    grok.name('index')
    grok.require('waeup.handleApplication')
    form_fields = grok.AutoFields(IApplicant).omit(
        'locked').omit('course_admitted')
    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
    form_fields['passport'].custom_widget = ThumbnailWidget
    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    label = 'Applicant'
    grok.template('form_display')
    pnav = 3

    @property
    def title(self):
        if self.request.principal.title == 'Applicant':
            return u'Your Application Record'
        return '%s' % self.context.access_code

    @property
    def label(self):
        container_title = self.context.__parent__.title
        return '%s Application Record' % container_title

    def getCourseAdmitted(self):
        """Return link, title and code in html format to the certificate
           admitted.
        """
        course_admitted = self.context.course_admitted
        if ICertificate.providedBy(course_admitted):
            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 PDFActionButton(ManageActionButton):
    grok.context(IApplicant)
    #grok.view(DisplayApplicant)
    grok.require('waeup.manageApplications')
    icon = 'actionicon_pdf.png'
    text = 'Download application slip'
    target = 'application_slip.pdf'

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

    @property
    def label(self):
        container_title = self.context.__parent__.title
        return '%s Application Record' % container_title

    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):
            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):
        # (0,0),(-1,-1) = whole table
        # (0,0),(0,-1) = first column
        # (-1,0),(-1,-1) = last column
        # (0,0),(-1,0) = first row
        # (0,-1),(-1,-1) = last row
        SLIP_STYLE = TableStyle(
            [('VALIGN',(0,0),(-1,-1),'TOP')]
            )

        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
        pdf.setTitle(self.label)
        pdf.setSubject('Application')
        pdf.setAuthor('%s (%s)' % (self.request.principal.title,
            self.request.principal.id))
        pdf.setCreator('WAeUP SIRP')
        width, height = A4
        style = getSampleStyleSheet()
        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))

        story = []
        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
        header_title = getattr(grok.getSite(), 'name', u'Sample University')
        story.append(Paragraph(header_title, style["Heading1"]))
        frame_header.addFromList(story,pdf)

        story = []
        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
        story.append(Paragraph(self.label, style["Heading2"]))
        #story.append(HRFlowable())
        story.append(Spacer(1, 18))
        for msg in self.context.history.messages:
            f_msg = '<font face="Courier" size=10>%s</font>' % msg
            story.append(Paragraph(f_msg, style["Normal"]))
        story.append(Spacer(1, 24))
        self.setUpWidgets()
        data = []
        for widget in self.widgets:
            f_label = '<font size=12>%s</font>:' % widget.label.strip()
            f_label = Paragraph(f_label, style["Normal"])
            if widget.name != 'form.passport':
                f_text = '<font size=12>%s</font>' % widget()
                f_text = Paragraph(f_text, style["Normal"])
                data.append([f_label,f_text])
            else:
                filename = widget._data.file.name
                im = Image(filename,width=4*cm, height=3*cm,kind='bound')
                data.append([f_label,im])
        f_label = '<font size=12>Admitted Course of Study:</font>'
        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
        f_label = Paragraph(f_label, style["Normal"])
        f_text = Paragraph(f_text, style["Normal"])
        data.append([f_label,f_text])
        table = Table(data,style=SLIP_STYLE)
        story.append(table)
        frame_body.addFromList(story,pdf)

        story = []
        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
        timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
        f_text = '<font size=10>%s</font>' % timestamp
        story.append(Paragraph(f_text, style["Normal"]))
        frame_footer.addFromList(story,pdf)

        self.response.setHeader(
            'Content-Type', 'application/pdf')
        return pdf.getpdfdata()

class ApplicantManageActionButton(ManageActionButton):
    grok.context(IApplicant)
    grok.view(DisplayApplicant)
    grok.require('waeup.manageApplications')
    text = 'Manage application record'
    target = 'edit_full'

class EditApplicantFull(WAeUPEditFormPage):
    """A full edit view for applicant data.
    """
    grok.context(IApplicant)
    grok.name('edit_full')
    grok.require('waeup.manageApplications')
    form_fields = grok.AutoFields(IApplicant)
    form_fields['passport'].custom_widget = EncodingImageFileWidget
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    grok.template('form_edit')
    manage_applications = True
    pnav = 3

    def update(self):
        datepicker.need() # Enable jQuery datepicker in date fields.
        super(EditApplicantFull, self).update()
        self.wf_info = IWorkflowInfo(self.context)
        upload = self.request.form.get('form.passport', None)
        if upload:
            # We got a fresh upload
            image = createWAeUPImageFile(upload.filename, upload)
            # This would normally be totally illegal. We set context
            # data without the complete form data being
            # verified. Normally data is set in methods like `save`
            # only, which is only called when all form data is correct
            # (and if not, the context is not touched). With images
            # and their uploads the problem then is, that the uploaded
            # image won't be shown in a partially erranous form
            # because the new uploaded image is still not part of the
            # context obeject and the image widget cannot work around
            # that (it has no access to the new data). For this reason
            # we set the image here and not in 'save()' or other
            # methods.
            self.context.passport = image
            upload.seek(0)
            self.passport_changed = True
        return

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

    @property
    def label(self):
        container_title = self.context.__parent__.title
        return '%s Application Form' % container_title

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

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

    @grok.action('Save')
    def save(self, **data):
        changed_fields = self.applyData(self.context, **data)
        changed_fields = changed_fields.values()
        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
        self.context._p_changed = True
        form = self.request.form
        if form.has_key('transition') and form['transition']:
            transition_id = form['transition']
            self.wf_info.fireTransition(transition_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

class EditApplicantStudent(EditApplicantFull):
    """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',
        )
    form_fields['passport'].custom_widget = EncodingImageFileWidget
    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    grok.template('form_edit')
    manage_applications = False
    title = u'Your Application Form'


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

    def update(self):
        if self.context.locked:
            self.emitLockMessage()
            return
        datepicker.need() # Enable jQuery datepicker in date fields.
        upload = self.request.form.get('form.passport', None)
        if upload:
            # We got a fresh upload
            image = createWAeUPImageFile(upload.filename, upload)
            # This would normally be totally illegal. We set context
            # data without the complete form data being
            # verified. Normally data is set in methods like `save`
            # only, which is only called when all form data is correct
            # (and if not, the context is not touched). With images
            # and their uploads the problem then is, that the uploaded
            # image won't be shown in a partially erranous form
            # because the new uploaded image is still not part of the
            # context obeject and the image widget cannot work around
            # that (it has no access to the new data). For this reason
            # we set the image here and not in 'save()' or other
            # methods.
            self.context.passport = image
            upload.seek(0)
            self.passport_changed = True
        super(EditApplicantStudent, self).update()
        return

    def dataNotComplete(self):
        if not self.request.form.get('confirm_passport', False):
            return 'Passport confirmation box not ticked.'
        #if len(self.errors) > 0:
        #    return 'Form has errors.'
        return False

    @grok.action('Save')
    def save(self, **data):
        #if self.context.locked:
        #    self.emitLockMessage()
        #    return
        self.applyData(self.context, **data)
        self.context._p_changed = True
        self.flash('Form has been saved.')
        return

    @grok.action('Final Submit')
    def finalsubmit(self, **data):
        #if self.context.locked:
        #    self.emitLockMessage()
        #    return
        self.applyData(self.context, **data)
        self.context._p_changed = True
        if self.dataNotComplete():
            self.flash(self.dataNotComplete())
            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 != STARTED:
            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 ApplicantViewActionButton(ManageActionButton):
    grok.context(IApplicant)
    grok.view(EditApplicantFull)
    grok.require('waeup.manageApplications')
    icon = 'actionicon_view.png'
    text = 'View application record'
    target = 'index'
