## $Id: browser.py 12196 2014-12-11 16:34:01Z 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 record 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(_('Save'), style='primary') def save(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) 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 record 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 record 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 """ grok.context(ICustomer) grok.name('change_portrait') grok.require('waeup.uploadCustomerFile') grok.template('filesuploadpage') label = _('Upload files') 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}) @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 an document """ grok.context(ICustomerDocumentsContainer) grok.name('adddoc') grok.template('documentaddform') grok.require('waeup.editCustomerDocuments') form_fields = grok.AutoFields(ICustomerDocument) label = _('Add document') pnav = 4 @property def selectable_doctypes(self): doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT return sorted(doctypes.items()) @action(_('Create 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.context.addDocument(document) doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype] self.flash(_('${a} created.', 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') form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date') pnav = 4 @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') form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date') pnav = 4 deletion_warning = _('Are you sure?') 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(_('Save'), style='primary') def save(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) except InvalidTransitionError, error: self.flash(error, type="warning") 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 = grok.AutoFields(ICustomerPDFDocument).omit( # 'last_transition_date') 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}) @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('addapp') grok.template('contractaddform') grok.require('waeup.editContracts') form_fields = grok.AutoFields(IContract) label = _('Add contract') pnav = 4 @property def selectable_contypes(self): contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT return sorted(contypes.items()) @action(_('Create 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.context.addContract(contract) contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype] self.flash(_('${a} created.', 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).omit( 'last_transition_date') 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( 'last_transition_date') @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( 'last_transition_date') 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(_('Save'), style='primary') def save(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) except InvalidTransitionError, error: self.flash(error, type="warning") 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).omit( 'last_transition_date') @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)