## $Id: browser.py 12258 2014-12-18 14:44:30Z henrik $
##
## Copyright (C) 2014 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 customers and related components.
"""

import sys
import grok
import pytz
import os
from urllib import urlencode
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.schema.interfaces import ConstraintNotSatisfied, RequiredMissing
from zope.formlib.textwidgets import BytesDisplayWidget
from zope.security import checkPermission
from hurry.workflow.interfaces import (
    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
from waeup.ikoba.interfaces import MessageFactory as _
from waeup.ikoba.interfaces import (
    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils,
    IPasswordValidator, IUserAccount,
    VERIFIED, REJECTED, EXPIRED, APPROVED)
from waeup.ikoba.browser.layout import (
    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
    IkobaForm, NullValidator, jsaction, action, UtilityView)
from waeup.ikoba.widgets.datewidget import (
    FriendlyDateWidget, FriendlyDateDisplayWidget,
    FriendlyDatetimeDisplayWidget)
from waeup.ikoba.browser.pages import ContactAdminForm
from waeup.ikoba.browser.breadcrumbs import Breadcrumb
from waeup.ikoba.browser.interfaces import ICaptchaManager
from waeup.ikoba.mandates.mandate import PasswordMandate
from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now
from waeup.ikoba.customers.interfaces import (
    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate,
    ICustomerPDFDocument, IContractsContainer, IContract, IContractEdit,
    ISampleContract,
    )
from waeup.ikoba.customers.catalog import search

grok.context(IIkobaObject)

WARNING = _('You can not edit your document after final submission.'
            ' You really want to submit?')


# 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())
    if 'product_object' in changed_fields and data['product_object'] is not None:
        view.context.last_product_id = data['product_object'].product_id
        notify(grok.ObjectModifiedEvent(view.context))
    fields_string = ' + '.join(changed_fields)
    view.flash(_('Form has been saved.'))
    if fields_string:
        view.context.writeLogMessage(
            view, '%s - saved: %s' % (view.context.__name__, fields_string))
    return


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


class CustomersBreadcrumb(Breadcrumb):
    """A breadcrumb for the customers container.
    """
    grok.context(ICustomersContainer)
    title = _('Customers')

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


class CustomerBreadcrumb(Breadcrumb):
    """A breadcrumb for the customer container.
    """
    grok.context(ICustomer)

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


class CustomersContainerPage(IkobaPage):
    """The standard view for customer containers.
    """
    grok.context(ICustomersContainer)
    grok.name('index')
    grok.require('waeup.viewCustomersContainer')
    grok.template('containerpage')
    label = _('Find customers')
    search_button = _('Find customer(s)')
    pnav = 4

    def update(self, *args, **kw):
        form = self.request.form
        self.hitlist = []
        if form.get('searchtype', None) == 'suspended':
            self.searchtype = form['searchtype']
            self.searchterm = None
        elif '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'), type="warning")
            return
        if self.searchtype == 'current_session':
            try:
                self.searchterm = int(self.searchterm)
            except ValueError:
                self.flash(_('Only year dates allowed (e.g. 2011).'),
                           type="danger")
                return
        self.hitlist = search(query=self.searchterm,
            searchtype=self.searchtype, view=self)
        if not self.hitlist:
            self.flash(_('No customer found.'), type="warning")
        return


class CustomersContainerManagePage(IkobaPage):
    """The manage page for customer containers.
    """
    grok.context(ICustomersContainer)
    grok.name('manage')
    grok.require('waeup.manageCustomer')
    grok.template('containermanagepage')
    pnav = 4
    label = _('Manage customer section')
    search_button = _('Find customer(s)')
    remove_button = _('Remove selected')

    def update(self, *args, **kw):
        form = self.request.form
        self.hitlist = []
        if form.get('searchtype', None) == 'suspended':
            self.searchtype = form['searchtype']
            self.searchterm = None
        elif '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'), type="warning")
            return
        if self.searchtype == 'current_session':
            try:
                self.searchterm = int(self.searchterm)
            except ValueError:
                self.flash(_('Only year dates allowed (e.g. 2011).'),
                           type="danger")
                return
        if not 'entries' in form:
            self.hitlist = search(query=self.searchterm,
                searchtype=self.searchtype, view=self)
            if not self.hitlist:
                self.flash(_('No customer found.'), type="warning")
            if 'remove' in form:
                self.flash(_('No item selected.'), type="warning")
            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 CustomerAddFormPage(IkobaAddFormPage):
    """Add-form to add a customer.
    """
    grok.context(ICustomersContainer)
    grok.require('waeup.manageCustomer')
    grok.name('addcustomer')
    form_fields = grok.AutoFields(ICustomer).select(
        'firstname', 'middlename', 'lastname', 'reg_number')
    label = _('Add customer')
    pnav = 4

    @action(_('Create customer record'), style='primary')
    def addCustomer(self, **data):
        customer = createObject(u'waeup.Customer')
        self.applyData(customer, **data)
        self.context.addCustomer(customer)
        self.flash(_('Customer created.'))
        self.redirect(self.url(self.context[customer.customer_id], 'index'))
        return


class LoginAsCustomerStep1(IkobaEditFormPage):
    """ View to temporarily set a customer password.
    """
    grok.context(ICustomer)
    grok.name('loginasstep1')
    grok.require('waeup.loginAsCustomer')
    grok.template('loginasstep1')
    pnav = 4

    def label(self):
        return _(u'Set temporary password for ${a}',
            mapping={'a': self.context.display_fullname})

    @action('Set password now', style='primary')
    def setPassword(self, *args, **data):
        ikoba_utils = getUtility(IIkobaUtils)
        password = ikoba_utils.genPassword()
        self.context.setTempPassword(self.request.principal.id, password)
        self.context.writeLogMessage(
            self, 'temp_password generated: %s' % password)
        args = {'password': password}
        self.redirect(self.url(self.context) +
            '/loginasstep2?%s' % urlencode(args))
        return


class LoginAsCustomerStep2(IkobaPage):
    """ View to temporarily login as customer with a temporary password.
    """
    grok.context(ICustomer)
    grok.name('loginasstep2')
    grok.require('waeup.Public')
    grok.template('loginasstep2')
    login_button = _('Login now')
    pnav = 4

    def label(self):
        return _(u'Login as ${a}',
            mapping={'a': self.context.customer_id})

    def update(self, SUBMIT=None, password=None):
        self.password = password
        if SUBMIT is not None:
            self.flash(_('You successfully logged in as customer.'))
            self.redirect(self.url(self.context))
        return


class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
    """ Page to display customer base data
    """
    grok.context(ICustomer)
    grok.name('index')
    grok.require('waeup.viewCustomer')
    grok.template('basepage')
    form_fields = grok.AutoFields(ICustomer).omit(
        'password', 'suspended', 'suspended_comment')
    pnav = 4

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

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


class ContactCustomerForm(ContactAdminForm):
    grok.context(ICustomer)
    grok.name('contactcustomer')
    grok.require('waeup.viewCustomer')
    pnav = 4
    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')

    def update(self, subject=u'', body=u''):
        super(ContactCustomerForm, self).update()
        self.form_fields.get('subject').field.default = subject
        self.form_fields.get('body').field.default = body
        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()
        ikoba_utils = getUtility(IIkobaUtils)
        success = ikoba_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.'), type="danger")
        return


class CustomerBaseManageFormPage(IkobaEditFormPage):
    """ View to manage customer base data
    """
    grok.context(ICustomer)
    grok.name('manage_base')
    grok.require('waeup.manageCustomer')
    form_fields = grok.AutoFields(ICustomer).omit(
        'customer_id', 'adm_code', 'suspended')
    grok.template('basemanagepage')
    label = _('Manage base data')
    pnav = 4

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

    @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), type="danger")
                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')
        fields_string = ' + '.join(changed_fields)
        self.flash(_('Form has been saved.'))
        if fields_string:
            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
        return


class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
    """ View to trigger customer workflow transitions
    """
    grok.context(ICustomer)
    grok.name('trigtrans')
    grok.require('waeup.triggerTransition')
    grok.template('trigtrans')
    label = _('Trigger registration transition')
    pnav = 4

    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.
        """
        wf_info = IWorkflowInfo(self.context)
        allowed_transitions = [t for t in wf_info.getManualTransitions()]
        return [dict(name='', title=_('No transition'))] +[
            dict(name=x, title=y) for x, y in allowed_transitions]

    @action(_('Apply now'), style='primary')
    def apply(self, **data):
        form = self.request.form
        if 'transition' in form and form['transition']:
            transition_id = form['transition']
            wf_info = IWorkflowInfo(self.context)
            wf_info.fireTransition(transition_id)
            self.flash(_("Transition '%s' executed." % transition_id))
            self.redirect(self.url(self.context))
        return


class CustomerActivatePage(UtilityView, grok.View):
    """ Activate customer account
    """
    grok.context(ICustomer)
    grok.name('activate')
    grok.require('waeup.manageCustomer')

    def update(self):
        self.context.suspended = False
        self.context.writeLogMessage(self, 'account activated')
        history = IObjectHistory(self.context)
        history.addMessage('Customer account activated')
        self.flash(_('Customer account has been activated.'))
        self.redirect(self.url(self.context))
        return

    def render(self):
        return


class CustomerDeactivatePage(UtilityView, grok.View):
    """ Deactivate customer account
    """
    grok.context(ICustomer)
    grok.name('deactivate')
    grok.require('waeup.manageCustomer')

    def update(self):
        self.context.suspended = True
        self.context.writeLogMessage(self, 'account deactivated')
        history = IObjectHistory(self.context)
        history.addMessage('Customer account deactivated')
        self.flash(_('Customer account has been deactivated.'))
        self.redirect(self.url(self.context))
        return

    def render(self):
        return


class CustomerHistoryPage(IkobaPage):
    """ Page to display customer history
    """
    grok.context(ICustomer)
    grok.name('history')
    grok.require('waeup.viewCustomer')
    grok.template('customerhistory')
    pnav = 4

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


class CustomerRequestPasswordPage(IkobaAddFormPage):
    """Captcha'd password request page for customers.
    """
    grok.name('requestpw')
    grok.require('waeup.Anonymous')
    grok.template('requestpw')
    form_fields = grok.AutoFields(ICustomerRequestPW)
    label = _('Request password for first-time login')

    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

    def _redirect(self, email, password, customer_id):
        # Forward only email address to landing page in base package.
        self.redirect(self.url(self.context, 'requestpw_complete',
            data=dict(email=email)))
        return

    def _pw_used(self):
        # XXX: False if password has not been used. We need an extra
        #      attribute which remembers if customer logged in.
        return True

    @action(_('Send login credentials to email address'), style='primary')
    def get_credentials(self, **data):
        if not self.captcha_result.is_valid:
            # Captcha will display error messages automatically.
            # No need to flash something.
            return
        number = data.get('number','')
        firstname = data.get('firstname','')
        cat = getUtility(ICatalog, name='customers_catalog')
        results = list(
            cat.searchResults(reg_number=(number, number)))
        if results:
            customer = results[0]
            if getattr(customer,'firstname',None) is None:
                self.flash(_('An error occurred.'), type="danger")
                return
            elif customer.firstname.lower() != firstname.lower():
                # Don't tell the truth here. Anonymous must not
                # know that a record was found and only the firstname
                # verification failed.
                self.flash(_('No customer found.'), type="warning")
                return
            elif customer.password is not None and self._pw_used:
                self.flash(_('Your password has already been set and used. '
                             'Please proceed to the login page.'),
                           type="warning")
                return
            # Store email address but nothing else.
            customer.email = data['email']
            notify(grok.ObjectModifiedEvent(customer))
        else:
            # No record found, this is the truth.
            self.flash(_('No customer found.'), type="warning")
            return

        ikoba_utils = getUtility(IIkobaUtils)
        password = ikoba_utils.genPassword()
        mandate = PasswordMandate()
        mandate.params['password'] = password
        mandate.params['user'] = customer
        site = grok.getSite()
        site['mandates'].addMandate(mandate)
        # Send email with credentials
        args = {'mandate_id':mandate.mandate_id}
        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
        url_info = u'Confirmation link: %s' % mandate_url
        msg = _('You have successfully requested a password for the')
        if ikoba_utils.sendCredentials(IUserAccount(customer),
            password, url_info, msg):
            email_sent = customer.email
        else:
            email_sent = None
        self._redirect(email=email_sent, password=password,
            customer_id=customer.customer_id)
        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
        self.context.logger.info(
            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
        return


class CustomerCreateAccountPage(IkobaAddFormPage):
    """Captcha'd account creation page for customers.
    """
    grok.name('createaccount')
    grok.require('waeup.Anonymous')
    grok.template('createaccount')
    form_fields = grok.AutoFields(ICustomerCreate)
    label = _('Create customer account')

    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

    def _redirect(self, email, password, customer_id):
        # Forward only email address to landing page in base package.
        self.redirect(self.url(self.context, 'requestpw_complete',
            data=dict(email=email)))
        return

    @action(_('Send login credentials to email address'), style='primary')
    def create_account(self, **data):
        if not self.captcha_result.is_valid:
            # Captcha will display error messages automatically.
            # No need to flash something.
            return
        customer = createObject(u'waeup.Customer')
        customer.firstname = data.get('firstname','')
        customer.middlename = data.get('middlename','')
        customer.lastname = data.get('lastname','')
        customer.email = data.get('email','')
        self.context['customers'].addCustomer(customer)
        ikoba_utils = getUtility(IIkobaUtils)
        password = ikoba_utils.genPassword()
        mandate = PasswordMandate()
        mandate.params['password'] = password
        mandate.params['user'] = customer
        site = grok.getSite()
        site['mandates'].addMandate(mandate)
        # Send email with credentials
        args = {'mandate_id':mandate.mandate_id}
        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
        url_info = u'Confirmation link: %s' % mandate_url
        msg = _('You have successfully created a customer account for the')
        if ikoba_utils.sendCredentials(IUserAccount(customer),
            password, url_info, msg):
            email_sent = customer.email
        else:
            email_sent = None
        self._redirect(email=email_sent, password=password,
            customer_id=customer.customer_id)
        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
        self.context.logger.info(
            '%s - %s - %s' % (ob_class, customer.customer_id, email_sent))
        return


class CustomerRequestPasswordEmailSent(IkobaPage):
    """Landing page after successful password request.

    """
    grok.name('requestpw_complete')
    grok.require('waeup.Public')
    grok.template('requestpwmailsent')
    label = _('Your request was successful.')

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


class CustomerFilesUploadPage(IkobaPage):
    """ View to upload files by customer

    We use this page only to upload a passport picture (portrait).
    """
    grok.context(ICustomer)
    grok.name('change_portrait')
    grok.require('waeup.uploadCustomerFile')
    grok.template('filesuploadpage')
    label = _('Change portrait')
    pnav = 4

    def update(self):
        CUSTMANAGE_STATES = getUtility(
            ICustomersUtils).CUSTMANAGE_CUSTOMER_STATES
        if self.context.customer.state not in CUSTMANAGE_STATES:
            emit_lock_message(self)
            return
        super(CustomerFilesUploadPage, self).update()
        return

# Pages for customers


class CustomerBaseEditFormPage(IkobaEditFormPage):
    """ View to edit customer base data
    """
    grok.context(ICustomer)
    grok.name('edit_base')
    grok.require('waeup.handleCustomer')
    form_fields = grok.AutoFields(ICustomer).select(
        'email', 'phone')
    label = _('Edit base data')
    pnav = 4

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


class CustomerChangePasswordPage(IkobaEditFormPage):
    """ View to edit customer passords
    """
    grok.context(ICustomer)
    grok.name('changepassword')
    grok.require('waeup.handleCustomer')
    grok.template('changepassword')
    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)
                self.context.writeLogMessage(self, 'saved: password')
                self.flash(_('Password changed.'))
            else:
                self.flash(' '.join(errors), type="warning")
        return

class CustomerBasePDFFormPage(IkobaDisplayFormPage):
    """ Page to display customer base data in pdf files.
    """

    def __init__(self, context, request, omit_fields=()):
        self.omit_fields = omit_fields
        super(CustomerBasePDFFormPage, self).__init__(context, request)

    @property
    def form_fields(self):
        form_fields = grok.AutoFields(ICustomer)
        for field in self.omit_fields:
            form_fields = form_fields.omit(field)
        return form_fields

# Pages for customer documents

class DocumentsBreadcrumb(Breadcrumb):
    """A breadcrumb for the documents container.
    """
    grok.context(ICustomerDocumentsContainer)
    title = _('Documents')


class DocumentBreadcrumb(Breadcrumb):
    """A breadcrumb for the customer container.
    """
    grok.context(ICustomerDocument)

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


class DocumentsManageFormPage(IkobaEditFormPage):
    """ Page to manage the customer documents

    This manage form page is for both customers and customers officers.
    """
    grok.context(ICustomerDocumentsContainer)
    grok.name('index')
    grok.require('waeup.viewCustomer')
    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
    grok.template('documentsmanagepage')
    pnav = 4

    @property
    def manage_documents_allowed(self):
        return checkPermission('waeup.editCustomerDocuments', self.context)

    def unremovable(self, document):
        usertype = getattr(self.request.principal, 'user_type', None)
        if not usertype:
            return False
        if not self.manage_documents_allowed:
            return True
        return (self.request.principal.user_type == 'customer' and \
            document.state in (VERIFIED, REJECTED, EXPIRED))

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

    @action(_('Add document'), validator=NullValidator, style='primary')
    def addDocument(self, **data):
        self.redirect(self.url(self.context, 'adddoc'))
        return

    @jsaction(_('Remove selected documents'))
    def delDocument(self, **data):
        form = self.request.form
        if 'val_id' in form:
            child_id = form['val_id']
        else:
            self.flash(_('No document selected.'), type="warning")
            self.redirect(self.url(self.context))
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            # Customers are not allowed to remove used documents
            document = self.context.get(id, None)
            if document is not None and not self.unremovable(document):
                del self.context[id]
                deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a': ', '.join(deleted)}))
            self.context.writeLogMessage(
                self,'removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context))
        return


class DocumentAddFormPage(IkobaAddFormPage):
    """ Page to add a document

    This add form page is for both customers and customers officers.
    """
    grok.context(ICustomerDocumentsContainer)
    grok.name('adddoc')
    grok.template('documentaddform')
    grok.require('waeup.editCustomerDocuments')
    label = _('Add document')
    pnav = 4

    form_fields = grok.AutoFields(ICustomerDocument).omit('document_id')

    @property
    def selectable_doctypes(self):
        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
        return sorted(doctypes.items())

    @action(_('Add document'), style='primary')
    def createDocument(self, **data):
        form = self.request.form
        customer = self.context.__parent__
        doctype = form.get('doctype', None)
        # Here we can create various instances of CustomerDocument derived 
        # classes depending on the doctype parameter given in form.
        document = createObject('waeup.%s' % doctype)
        self.applyData(document, **data)
        self.context.addDocument(document)
        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
        self.flash(_('${a} added.', mapping = {'a': doctype}))
        self.context.writeLogMessage(
            self,'added: %s %s' % (doctype, document.document_id))
        self.redirect(self.url(self.context))
        return

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


class DocumentDisplayFormPage(IkobaDisplayFormPage):
    """ Page to view a document
    """
    grok.context(ICustomerDocument)
    grok.name('index')
    grok.require('waeup.viewCustomer')
    grok.template('documentpage')
    pnav = 4

    @property
    def form_fields(self):
        return grok.AutoFields(self.context.form_fields_interface)

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


class DocumentManageFormPage(IkobaEditFormPage):
    """ Page to edit a document
    """
    grok.context(ICustomerDocument)
    grok.name('manage')
    grok.require('waeup.manageCustomer')
    grok.template('documenteditpage')
    pnav = 4
    deletion_warning = _('Are you sure?')

    @property
    def form_fields(self):
        return grok.AutoFields(
            self.context.form_fields_interface).omit('document_id')

    def update(self):
        if not self.context.is_editable_by_manager:
            emit_lock_message(self)
            return
        return super(DocumentManageFormPage, self).update()

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

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


class DocumentEditFormPage(DocumentManageFormPage):
    """ Page to edit a document
    """
    grok.name('edit')
    grok.require('waeup.handleCustomer')

    def update(self):
        if not self.context.is_editable_by_customer:
            emit_lock_message(self)
            return
        return super(DocumentEditFormPage, self).update()

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

    @action(_('Final Submit'), warning=WARNING)
    def finalsubmit(self, **data):
        msave(self, **data)
        IWorkflowInfo(self.context).fireTransition('submit')
        self.flash(_('Form has been submitted.'))
        self.redirect(self.url(self.context))
        return


class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
    """ View to trigger customer document transitions
    """
    grok.context(ICustomerDocument)
    grok.name('trigtrans')
    grok.require('waeup.triggerTransition')
    grok.template('trigtrans')
    label = _('Trigger document transition')
    pnav = 4

    def update(self):
        return super(IkobaEditFormPage, self).update()

    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.
        """
        wf_info = IWorkflowInfo(self.context)
        allowed_transitions = [t for t in wf_info.getManualTransitions()]
        return [dict(name='', title=_('No transition'))] +[
            dict(name=x, title=y) for x, y in allowed_transitions]

    @action(_('Apply now'), style='primary')
    def apply(self, **data):
        form = self.request.form
        if 'transition' in form and form['transition']:
            transition_id = form['transition']
            wf_info = IWorkflowInfo(self.context)
            try:
                wf_info.fireTransition(transition_id)
                self.flash(_("Transition '%s' executed." % transition_id))
            except InvalidTransitionError, error:
                self.flash(error, type="warning")
            self.redirect(self.url(self.context))
        return

class PDFDocumentsOverviewPage(UtilityView, grok.View):
    """Deliver an overview slip.
    """
    grok.context(ICustomerDocumentsContainer)
    grok.name('documents_overview_slip.pdf')
    grok.require('waeup.viewCustomer')
    prefix = 'form'

    omit_fields = ('suspended', 'sex',
                   'suspended_comment',)

    form_fields = None

    @property
    def label(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        return translate(_('Documents of'),
            'waeup.ikoba', target_language=portal_language) \
            + ' %s' % self.context.customer.display_fullname

    @property
    def tabletitle(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        tabletitle = []
        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
            target_language=portal_language))
        return tabletitle

    def render(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
        tableheader = []
        tabledata = []
        contenttitle = []
        for i in range(1,3):
            tabledata.append(sorted(
                [value for value in self.context.values()]))
            tableheader.append([(Id, 'document_id', 2),
                             (Title, 'title', 6),
                             (Type, 'translated_class_name', 6),
                             (State, 'translated_state', 2),
                             (LT, 'formatted_transition_date', 3),
                             ])
        customerview = CustomerBasePDFFormPage(self.context.customer,
            self.request, self.omit_fields)
        customers_utils = getUtility(ICustomersUtils)
        return customers_utils.renderPDF(
            self, 'overview_slip.pdf',
            self.context.customer, customerview,
            tableheader=tableheader,
            tabledata=tabledata,
            omit_fields=self.omit_fields)


class PDFDocumentSlipPage(UtilityView, grok.View):
    """Deliver pdf file including metadata.
    """
    grok.context(ICustomerDocument)
    grok.name('document_slip.pdf')
    grok.require('waeup.viewCustomer')
    prefix = 'form'

    omit_fields = ('suspended', 'sex',
                   'suspended_comment',)

    form_fields =()

    @property
    def label(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        return '%s of %s\nTitle: %s' % (
            self.context.translated_class_name,
            self.context.customer.display_fullname,
            self.context.title)

    def render(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        customerview = CustomerBasePDFFormPage(self.context.customer,
            self.request, self.omit_fields)
        customers_utils = getUtility(ICustomersUtils)
        return customers_utils.renderPDF(
            self, 'document_slip.pdf',
            self.context.customer, customerview,
            omit_fields=self.omit_fields)


class PDFMergeDocumentSlipPage(PDFDocumentSlipPage):
    """Deliver pdf file including metadata.
    """
    grok.context(ICustomerPDFDocument)

    def render(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        customerview = CustomerBasePDFFormPage(self.context.customer,
            self.request, self.omit_fields)
        customers_utils = getUtility(ICustomersUtils)
        if self.context.state == VERIFIED:
            watermark_path = os.path.join(
                os.path.dirname(__file__), 'static', 'verified.pdf')
        else:
            watermark_path = os.path.join(
                os.path.dirname(__file__), 'static', 'unverified.pdf')
        watermark = open(watermark_path, 'rb')
        return customers_utils.renderPDF(
            self, 'pdfdocument_slip.pdf',
            self.context.customer, customerview,
            omit_fields=self.omit_fields,
            mergefiles=self.context.connected_files,
            watermark=watermark)

# Pages for customer contracts

class ContractsBreadcrumb(Breadcrumb):
    """A breadcrumb for the contracts container.
    """
    grok.context(IContractsContainer)
    title = _('Contracts')


class ContractBreadcrumb(Breadcrumb):
    """A breadcrumb for the customer container.
    """
    grok.context(IContract)

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


class ContractsManageFormPage(IkobaEditFormPage):
    """ Page to manage the customer contracts

    This manage form page is for both customers and officers.
    """
    grok.context(IContractsContainer)
    grok.name('index')
    grok.require('waeup.viewCustomer')
    form_fields = grok.AutoFields(IContractsContainer)
    grok.template('contractsmanagepage')
    pnav = 4

    @property
    def manage_contracts_allowed(self):
        return checkPermission('waeup.editContracts', self.context)

    def unremovable(self, contract):
        usertype = getattr(self.request.principal, 'user_type', None)
        if not usertype:
            return False
        if not self.manage_contracts_allowed:
            return True
        return (self.request.principal.user_type == 'customer' and \
            contract.state in (APPROVED, REJECTED, EXPIRED))

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

    @action(_('Add contract'), validator=NullValidator, style='primary')
    def addContract(self, **data):
        self.redirect(self.url(self.context, 'addcontract'))
        return

    @jsaction(_('Remove selected contracts'))
    def delContract(self, **data):
        form = self.request.form
        if 'val_id' in form:
            child_id = form['val_id']
        else:
            self.flash(_('No contract selected.'), type="warning")
            self.redirect(self.url(self.context))
            return
        if not isinstance(child_id, list):
            child_id = [child_id]
        deleted = []
        for id in child_id:
            # Customers are not allowed to remove used contracts
            contract = self.context.get(id, None)
            if contract is not None and not self.unremovable(contract):
                del self.context[id]
                deleted.append(id)
        if len(deleted):
            self.flash(_('Successfully removed: ${a}',
                mapping = {'a': ', '.join(deleted)}))
            self.context.writeLogMessage(
                self,'removed: %s' % ', '.join(deleted))
        self.redirect(self.url(self.context))
        return


class ContractAddFormPage(IkobaAddFormPage):
    """ Page to add an contract
    """
    grok.context(IContractsContainer)
    grok.name('addcontract')
    grok.template('contractaddform')
    grok.require('waeup.editContracts')
    label = _('Add contract')
    pnav = 4

    form_fields = grok.AutoFields(IContract).omit(
        'product_object', 'contract_id')

    @property
    def selectable_contypes(self):
        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
        return sorted(contypes.items())

    @action(_('Add contract'), style='primary')
    def createContract(self, **data):
        form = self.request.form
        customer = self.context.__parent__
        contype = form.get('contype', None)
        # Here we can create various instances of Contract derived
        # classes depending on the contype parameter given in form.
        contract = createObject('waeup.%s' % contype)
        self.applyData(contract, **data)
        self.context.addContract(contract)
        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
        self.flash(_('${a} added.', mapping = {'a': contype}))
        self.context.writeLogMessage(
            self,'added: %s %s' % (contype, contract.contract_id))
        self.redirect(self.url(self.context))
        return

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


class ContractDisplayFormPage(IkobaDisplayFormPage):
    """ Page to view a contract
    """
    grok.context(IContract)
    grok.name('index')
    grok.require('waeup.viewCustomer')
    grok.template('contractpage')
    pnav = 4

    @property
    def form_fields(self):
        form_fields = grok.AutoFields(self.context.form_fields_interface)
        for field in form_fields:
            if field.__name__.endswith('_object'):
                form_fields[field.__name__].custom_widget = HREFDisplayWidget
        return form_fields

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


class ContractManageFormPage(IkobaEditFormPage):
    """ Page to edit a contract
    """
    grok.context(IContract)
    grok.name('manage')
    grok.require('waeup.manageCustomer')
    grok.template('contracteditpage')
    pnav = 4
    deletion_warning = _('Are you sure?')

    @property
    def form_fields(self):
        return grok.AutoFields(self.context.form_fields_interface).omit(
            'contract_id')

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

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


class ContractEditFormPage(ContractManageFormPage):
    """ Page to edit a contract
    """
    grok.name('edit')
    grok.require('waeup.handleCustomer')

    @property
    def form_fields(self):
        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
            'contract_id')

    def update(self):
        if not self.context.is_editable_by_customer:
            emit_lock_message(self)
            return
        return super(ContractEditFormPage, self).update()

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

    @action(_('Apply now (final submit)'), warning=WARNING)
    def finalsubmit(self, **data):
        msave(self, **data)
        IWorkflowInfo(self.context).fireTransition('submit')
        self.flash(_('Form has been submitted.'))
        self.redirect(self.url(self.context))
        return


class ContractTriggerTransitionFormPage(IkobaEditFormPage):
    """ View to trigger customer contract transitions
    """
    grok.context(IContract)
    grok.name('trigtrans')
    grok.require('waeup.triggerTransition')
    grok.template('trigtrans')
    label = _('Trigger contract transition')
    pnav = 4

    def update(self):
        return super(IkobaEditFormPage, self).update()

    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.
        """
        wf_info = IWorkflowInfo(self.context)
        allowed_transitions = [t for t in wf_info.getManualTransitions()]
        return [dict(name='', title=_('No transition'))] +[
            dict(name=x, title=y) for x, y in allowed_transitions]

    @action(_('Apply now'), style='primary')
    def apply(self, **data):
        form = self.request.form
        if 'transition' in form and form['transition']:
            transition_id = form['transition']
            wf_info = IWorkflowInfo(self.context)
            try:
                wf_info.fireTransition(transition_id)
                self.flash(_("Transition '%s' executed." % transition_id))
            except InvalidTransitionError, error:
                self.flash(error, type="warning")
            self.redirect(self.url(self.context))
        return

class PDFContractsOverviewPage(UtilityView, grok.View):
    """Deliver an overview slip.
    """
    grok.context(IContractsContainer)
    grok.name('contracts_overview_slip.pdf')
    grok.require('waeup.viewCustomer')
    prefix = 'form'

    omit_fields = ('suspended', 'sex',
                   'suspended_comment',)

    form_fields = None

    @property
    def label(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        return translate(_('Contracts of'),
            'waeup.ikoba', target_language=portal_language) \
            + ' %s' % self.context.customer.display_fullname

    @property
    def tabletitle(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        tabletitle = []
        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
            target_language=portal_language))
        return tabletitle

    def render(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
        tableheader = []
        tabledata = []
        contenttitle = []
        for i in range(1,3):
            tabledata.append(sorted(
                [value for value in self.context.values()]))
            tableheader.append([(Id, 'contract_id', 2),
                             (Title, 'title', 6),
                             (Type, 'translated_class_name', 6),
                             (State, 'translated_state', 2),
                             (LT, 'formatted_transition_date', 3),
                             ])
        customerview = CustomerBasePDFFormPage(self.context.customer,
            self.request, self.omit_fields)
        customers_utils = getUtility(ICustomersUtils)
        return customers_utils.renderPDF(
            self, 'overview_slip.pdf',
            self.context.customer, customerview,
            tableheader=tableheader,
            tabledata=tabledata,
            omit_fields=self.omit_fields)


class PDFContractSlipPage(UtilityView, grok.View):
    """Deliver pdf file including metadata.
    """
    grok.context(ISampleContract)
    grok.name('contract_slip.pdf')
    grok.require('waeup.viewCustomer')
    prefix = 'form'

    omit_fields = ('suspended', 'sex',
                   'suspended_comment',)

    form_fields = grok.AutoFields(ISampleContract)

    @property
    def label(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        return '%s of %s\nTitle: %s' % (
            self.context.translated_class_name,
            self.context.customer.display_fullname,
            self.context.title)

    def render(self):
        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
        customerview = CustomerBasePDFFormPage(self.context.customer,
            self.request, self.omit_fields)
        customers_utils = getUtility(ICustomersUtils)
        return customers_utils.renderPDF(
            self, 'contract_slip.pdf',
            self.context.customer, customerview,
            omit_fields=self.omit_fields)
