source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/browser.py @ 12193

Last change on this file since 12193 was 12184, checked in by Henrik Bettermann, 10 years ago

Add watermark to attached pdf files.

  • Property svn:keywords set to Id
File size: 47.3 KB
Line 
1## $Id: browser.py 12184 2014-12-09 15:58:08Z henrik $
2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for customers and related components.
19"""
20
21import sys
22import grok
23import pytz
24import os
25from urllib import urlencode
26from datetime import datetime
27from zope.event import notify
28from zope.i18n import translate
29from zope.catalog.interfaces import ICatalog
30from zope.component import queryUtility, getUtility, createObject
31from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing
32from zope.formlib.textwidgets import BytesDisplayWidget
33from zope.security import checkPermission
34from hurry.workflow.interfaces import (
35    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
36from waeup.ikoba.interfaces import MessageFactory as _
37from waeup.ikoba.interfaces import (
38    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils,
39    IPasswordValidator, IUserAccount,
40    VERIFIED, REJECTED, EXPIRED, APPROVED)
41from waeup.ikoba.browser.layout import (
42    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
43    IkobaForm, NullValidator, jsaction, action, UtilityView)
44from waeup.ikoba.widgets.datewidget import (
45    FriendlyDateWidget, FriendlyDateDisplayWidget,
46    FriendlyDatetimeDisplayWidget)
47from waeup.ikoba.browser.pages import ContactAdminForm
48from waeup.ikoba.browser.breadcrumbs import Breadcrumb
49from waeup.ikoba.browser.interfaces import ICaptchaManager
50from waeup.ikoba.mandates.mandate import PasswordMandate
51from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
52from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now
53from waeup.ikoba.customers.interfaces import (
54    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
55    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate,
56    ICustomerPDFDocument, IContractsContainer, IContract, IContractEdit,
57    ISampleContract,
58    )
59from waeup.ikoba.customers.catalog import search
60
61grok.context(IIkobaObject)
62
63WARNING = _('You can not edit your document after final submission.'
64            ' You really want to submit?')
65
66
67# Save function used for save methods in pages
68def msave(view, **data):
69    changed_fields = view.applyData(view.context, **data)
70    # Turn list of lists into single list
71    if changed_fields:
72        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
73    if 'product_object' in changed_fields and data['product_object'] is not None:
74        view.context.last_product_id = data['product_object'].product_id
75        notify(grok.ObjectModifiedEvent(view.context))
76    fields_string = ' + '.join(changed_fields)
77    view.flash(_('Form has been saved.'))
78    if fields_string:
79        view.context.writeLogMessage(
80            view, '%s - saved: %s' % (view.context.__name__, fields_string))
81    return
82
83
84def emit_lock_message(view):
85    """Flash a lock message.
86    """
87    view.flash(_('The requested form is locked (read-only).'), type="warning")
88    view.redirect(view.url(view.context))
89    return
90
91
92class CustomersBreadcrumb(Breadcrumb):
93    """A breadcrumb for the customers container.
94    """
95    grok.context(ICustomersContainer)
96    title = _('Customers')
97
98    @property
99    def target(self):
100        user = get_current_principal()
101        if getattr(user, 'user_type', None) == 'customer':
102            return None
103        return self.viewname
104
105
106class CustomerBreadcrumb(Breadcrumb):
107    """A breadcrumb for the customer container.
108    """
109    grok.context(ICustomer)
110
111    def title(self):
112        return self.context.display_fullname
113
114
115class CustomersContainerPage(IkobaPage):
116    """The standard view for customer containers.
117    """
118    grok.context(ICustomersContainer)
119    grok.name('index')
120    grok.require('waeup.viewCustomersContainer')
121    grok.template('containerpage')
122    label = _('Find customers')
123    search_button = _('Find customer(s)')
124    pnav = 4
125
126    def update(self, *args, **kw):
127        form = self.request.form
128        self.hitlist = []
129        if form.get('searchtype', None) == 'suspended':
130            self.searchtype = form['searchtype']
131            self.searchterm = None
132        elif 'searchterm' in form and form['searchterm']:
133            self.searchterm = form['searchterm']
134            self.searchtype = form['searchtype']
135        elif 'old_searchterm' in form:
136            self.searchterm = form['old_searchterm']
137            self.searchtype = form['old_searchtype']
138        else:
139            if 'search' in form:
140                self.flash(_('Empty search string'), type="warning")
141            return
142        if self.searchtype == 'current_session':
143            try:
144                self.searchterm = int(self.searchterm)
145            except ValueError:
146                self.flash(_('Only year dates allowed (e.g. 2011).'),
147                           type="danger")
148                return
149        self.hitlist = search(query=self.searchterm,
150            searchtype=self.searchtype, view=self)
151        if not self.hitlist:
152            self.flash(_('No customer found.'), type="warning")
153        return
154
155
156class CustomersContainerManagePage(IkobaPage):
157    """The manage page for customer containers.
158    """
159    grok.context(ICustomersContainer)
160    grok.name('manage')
161    grok.require('waeup.manageCustomer')
162    grok.template('containermanagepage')
163    pnav = 4
164    label = _('Manage customer section')
165    search_button = _('Find customer(s)')
166    remove_button = _('Remove selected')
167
168    def update(self, *args, **kw):
169        form = self.request.form
170        self.hitlist = []
171        if form.get('searchtype', None) == 'suspended':
172            self.searchtype = form['searchtype']
173            self.searchterm = None
174        elif 'searchterm' in form and form['searchterm']:
175            self.searchterm = form['searchterm']
176            self.searchtype = form['searchtype']
177        elif 'old_searchterm' in form:
178            self.searchterm = form['old_searchterm']
179            self.searchtype = form['old_searchtype']
180        else:
181            if 'search' in form:
182                self.flash(_('Empty search string'), type="warning")
183            return
184        if self.searchtype == 'current_session':
185            try:
186                self.searchterm = int(self.searchterm)
187            except ValueError:
188                self.flash(_('Only year dates allowed (e.g. 2011).'),
189                           type="danger")
190                return
191        if not 'entries' in form:
192            self.hitlist = search(query=self.searchterm,
193                searchtype=self.searchtype, view=self)
194            if not self.hitlist:
195                self.flash(_('No customer found.'), type="warning")
196            if 'remove' in form:
197                self.flash(_('No item selected.'), type="warning")
198            return
199        entries = form['entries']
200        if isinstance(entries, basestring):
201            entries = [entries]
202        deleted = []
203        for entry in entries:
204            if 'remove' in form:
205                del self.context[entry]
206                deleted.append(entry)
207        self.hitlist = search(query=self.searchterm,
208            searchtype=self.searchtype, view=self)
209        if len(deleted):
210            self.flash(_('Successfully removed: ${a}',
211                mapping={'a': ','.join(deleted)}))
212        return
213
214
215class CustomerAddFormPage(IkobaAddFormPage):
216    """Add-form to add a customer.
217    """
218    grok.context(ICustomersContainer)
219    grok.require('waeup.manageCustomer')
220    grok.name('addcustomer')
221    form_fields = grok.AutoFields(ICustomer).select(
222        'firstname', 'middlename', 'lastname', 'reg_number')
223    label = _('Add customer')
224    pnav = 4
225
226    @action(_('Create customer record'), style='primary')
227    def addCustomer(self, **data):
228        customer = createObject(u'waeup.Customer')
229        self.applyData(customer, **data)
230        self.context.addCustomer(customer)
231        self.flash(_('Customer record created.'))
232        self.redirect(self.url(self.context[customer.customer_id], 'index'))
233        return
234
235
236class LoginAsCustomerStep1(IkobaEditFormPage):
237    """ View to temporarily set a customer password.
238    """
239    grok.context(ICustomer)
240    grok.name('loginasstep1')
241    grok.require('waeup.loginAsCustomer')
242    grok.template('loginasstep1')
243    pnav = 4
244
245    def label(self):
246        return _(u'Set temporary password for ${a}',
247            mapping={'a': self.context.display_fullname})
248
249    @action('Set password now', style='primary')
250    def setPassword(self, *args, **data):
251        ikoba_utils = getUtility(IIkobaUtils)
252        password = ikoba_utils.genPassword()
253        self.context.setTempPassword(self.request.principal.id, password)
254        self.context.writeLogMessage(
255            self, 'temp_password generated: %s' % password)
256        args = {'password': password}
257        self.redirect(self.url(self.context) +
258            '/loginasstep2?%s' % urlencode(args))
259        return
260
261
262class LoginAsCustomerStep2(IkobaPage):
263    """ View to temporarily login as customer with a temporary password.
264    """
265    grok.context(ICustomer)
266    grok.name('loginasstep2')
267    grok.require('waeup.Public')
268    grok.template('loginasstep2')
269    login_button = _('Login now')
270    pnav = 4
271
272    def label(self):
273        return _(u'Login as ${a}',
274            mapping={'a': self.context.customer_id})
275
276    def update(self, SUBMIT=None, password=None):
277        self.password = password
278        if SUBMIT is not None:
279            self.flash(_('You successfully logged in as customer.'))
280            self.redirect(self.url(self.context))
281        return
282
283
284class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
285    """ Page to display customer base data
286    """
287    grok.context(ICustomer)
288    grok.name('index')
289    grok.require('waeup.viewCustomer')
290    grok.template('basepage')
291    form_fields = grok.AutoFields(ICustomer).omit(
292        'password', 'suspended', 'suspended_comment')
293    pnav = 4
294
295    @property
296    def label(self):
297        if self.context.suspended:
298            return _('${a}: Base Data (account deactivated)',
299                mapping={'a': self.context.display_fullname})
300        return  _('${a}: Base Data',
301            mapping={'a': self.context.display_fullname})
302
303    @property
304    def hasPassword(self):
305        if self.context.password:
306            return _('set')
307        return _('unset')
308
309
310class ContactCustomerForm(ContactAdminForm):
311    grok.context(ICustomer)
312    grok.name('contactcustomer')
313    grok.require('waeup.viewCustomer')
314    pnav = 4
315    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
316
317    def update(self, subject=u'', body=u''):
318        super(ContactCustomerForm, self).update()
319        self.form_fields.get('subject').field.default = subject
320        self.form_fields.get('body').field.default = body
321        return
322
323    def label(self):
324        return _(u'Send message to ${a}',
325            mapping={'a': self.context.display_fullname})
326
327    @action('Send message now', style='primary')
328    def send(self, *args, **data):
329        try:
330            email = self.request.principal.email
331        except AttributeError:
332            email = self.config.email_admin
333        usertype = getattr(self.request.principal,
334                           'user_type', 'system').title()
335        ikoba_utils = getUtility(IIkobaUtils)
336        success = ikoba_utils.sendContactForm(
337                self.request.principal.title, email,
338                self.context.display_fullname, self.context.email,
339                self.request.principal.id,usertype,
340                self.config.name,
341                data['body'], data['subject'])
342        if success:
343            self.flash(_('Your message has been sent.'))
344        else:
345            self.flash(_('An smtp server error occurred.'), type="danger")
346        return
347
348
349class CustomerBaseManageFormPage(IkobaEditFormPage):
350    """ View to manage customer base data
351    """
352    grok.context(ICustomer)
353    grok.name('manage_base')
354    grok.require('waeup.manageCustomer')
355    form_fields = grok.AutoFields(ICustomer).omit(
356        'customer_id', 'adm_code', 'suspended')
357    grok.template('basemanagepage')
358    label = _('Manage base data')
359    pnav = 4
360
361    def update(self):
362        super(CustomerBaseManageFormPage, self).update()
363        self.wf_info = IWorkflowInfo(self.context)
364        return
365
366    @action(_('Save'), style='primary')
367    def save(self, **data):
368        form = self.request.form
369        password = form.get('password', None)
370        password_ctl = form.get('control_password', None)
371        if password:
372            validator = getUtility(IPasswordValidator)
373            errors = validator.validate_password(password, password_ctl)
374            if errors:
375                self.flash(' '.join(errors), type="danger")
376                return
377        changed_fields = self.applyData(self.context, **data)
378        # Turn list of lists into single list
379        if changed_fields:
380            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
381        else:
382            changed_fields = []
383        if password:
384            # Now we know that the form has no errors and can set password
385            IUserAccount(self.context).setPassword(password)
386            changed_fields.append('password')
387        fields_string = ' + '.join(changed_fields)
388        self.flash(_('Form has been saved.'))
389        if fields_string:
390            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
391        return
392
393
394class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
395    """ View to trigger customer workflow transitions
396    """
397    grok.context(ICustomer)
398    grok.name('trigtrans')
399    grok.require('waeup.triggerTransition')
400    grok.template('trigtrans')
401    label = _('Trigger registration transition')
402    pnav = 4
403
404    def getTransitions(self):
405        """Return a list of dicts of allowed transition ids and titles.
406
407        Each list entry provides keys ``name`` and ``title`` for
408        internal name and (human readable) title of a single
409        transition.
410        """
411        wf_info = IWorkflowInfo(self.context)
412        allowed_transitions = [t for t in wf_info.getManualTransitions()]
413        return [dict(name='', title=_('No transition'))] +[
414            dict(name=x, title=y) for x, y in allowed_transitions]
415
416    @action(_('Save'), style='primary')
417    def save(self, **data):
418        form = self.request.form
419        if 'transition' in form and form['transition']:
420            transition_id = form['transition']
421            wf_info = IWorkflowInfo(self.context)
422            wf_info.fireTransition(transition_id)
423        return
424
425
426class CustomerActivatePage(UtilityView, grok.View):
427    """ Activate customer account
428    """
429    grok.context(ICustomer)
430    grok.name('activate')
431    grok.require('waeup.manageCustomer')
432
433    def update(self):
434        self.context.suspended = False
435        self.context.writeLogMessage(self, 'account activated')
436        history = IObjectHistory(self.context)
437        history.addMessage('Customer account activated')
438        self.flash(_('Customer account has been activated.'))
439        self.redirect(self.url(self.context))
440        return
441
442    def render(self):
443        return
444
445
446class CustomerDeactivatePage(UtilityView, grok.View):
447    """ Deactivate customer account
448    """
449    grok.context(ICustomer)
450    grok.name('deactivate')
451    grok.require('waeup.manageCustomer')
452
453    def update(self):
454        self.context.suspended = True
455        self.context.writeLogMessage(self, 'account deactivated')
456        history = IObjectHistory(self.context)
457        history.addMessage('Customer account deactivated')
458        self.flash(_('Customer account has been deactivated.'))
459        self.redirect(self.url(self.context))
460        return
461
462    def render(self):
463        return
464
465
466class CustomerHistoryPage(IkobaPage):
467    """ Page to display customer history
468    """
469    grok.context(ICustomer)
470    grok.name('history')
471    grok.require('waeup.viewCustomer')
472    grok.template('customerhistory')
473    pnav = 4
474
475    @property
476    def label(self):
477        return _('${a}: History', mapping={'a':self.context.display_fullname})
478
479
480class CustomerRequestPasswordPage(IkobaAddFormPage):
481    """Captcha'd password request page for customers.
482    """
483    grok.name('requestpw')
484    grok.require('waeup.Anonymous')
485    grok.template('requestpw')
486    form_fields = grok.AutoFields(ICustomerRequestPW)
487    label = _('Request password for first-time login')
488
489    def update(self):
490        # Handle captcha
491        self.captcha = getUtility(ICaptchaManager).getCaptcha()
492        self.captcha_result = self.captcha.verify(self.request)
493        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
494        return
495
496    def _redirect(self, email, password, customer_id):
497        # Forward only email address to landing page in base package.
498        self.redirect(self.url(self.context, 'requestpw_complete',
499            data=dict(email=email)))
500        return
501
502    def _pw_used(self):
503        # XXX: False if password has not been used. We need an extra
504        #      attribute which remembers if customer logged in.
505        return True
506
507    @action(_('Send login credentials to email address'), style='primary')
508    def get_credentials(self, **data):
509        if not self.captcha_result.is_valid:
510            # Captcha will display error messages automatically.
511            # No need to flash something.
512            return
513        number = data.get('number','')
514        firstname = data.get('firstname','')
515        cat = getUtility(ICatalog, name='customers_catalog')
516        results = list(
517            cat.searchResults(reg_number=(number, number)))
518        if results:
519            customer = results[0]
520            if getattr(customer,'firstname',None) is None:
521                self.flash(_('An error occurred.'), type="danger")
522                return
523            elif customer.firstname.lower() != firstname.lower():
524                # Don't tell the truth here. Anonymous must not
525                # know that a record was found and only the firstname
526                # verification failed.
527                self.flash(_('No customer record found.'), type="warning")
528                return
529            elif customer.password is not None and self._pw_used:
530                self.flash(_('Your password has already been set and used. '
531                             'Please proceed to the login page.'),
532                           type="warning")
533                return
534            # Store email address but nothing else.
535            customer.email = data['email']
536            notify(grok.ObjectModifiedEvent(customer))
537        else:
538            # No record found, this is the truth.
539            self.flash(_('No customer record found.'), type="warning")
540            return
541
542        ikoba_utils = getUtility(IIkobaUtils)
543        password = ikoba_utils.genPassword()
544        mandate = PasswordMandate()
545        mandate.params['password'] = password
546        mandate.params['user'] = customer
547        site = grok.getSite()
548        site['mandates'].addMandate(mandate)
549        # Send email with credentials
550        args = {'mandate_id':mandate.mandate_id}
551        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
552        url_info = u'Confirmation link: %s' % mandate_url
553        msg = _('You have successfully requested a password for the')
554        if ikoba_utils.sendCredentials(IUserAccount(customer),
555            password, url_info, msg):
556            email_sent = customer.email
557        else:
558            email_sent = None
559        self._redirect(email=email_sent, password=password,
560            customer_id=customer.customer_id)
561        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
562        self.context.logger.info(
563            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
564        return
565
566
567class CustomerCreateAccountPage(IkobaAddFormPage):
568    """Captcha'd account creation page for customers.
569    """
570    grok.name('createaccount')
571    grok.require('waeup.Anonymous')
572    grok.template('createaccount')
573    form_fields = grok.AutoFields(ICustomerCreate)
574    label = _('Create customer account')
575
576    def update(self):
577        # Handle captcha
578        self.captcha = getUtility(ICaptchaManager).getCaptcha()
579        self.captcha_result = self.captcha.verify(self.request)
580        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
581        return
582
583    def _redirect(self, email, password, customer_id):
584        # Forward only email address to landing page in base package.
585        self.redirect(self.url(self.context, 'requestpw_complete',
586            data=dict(email=email)))
587        return
588
589    @action(_('Send login credentials to email address'), style='primary')
590    def create_account(self, **data):
591        if not self.captcha_result.is_valid:
592            # Captcha will display error messages automatically.
593            # No need to flash something.
594            return
595        customer = createObject(u'waeup.Customer')
596        customer.firstname = data.get('firstname','')
597        customer.middlename = data.get('middlename','')
598        customer.lastname = data.get('lastname','')
599        customer.email = data.get('email','')
600        self.context['customers'].addCustomer(customer)
601        ikoba_utils = getUtility(IIkobaUtils)
602        password = ikoba_utils.genPassword()
603        mandate = PasswordMandate()
604        mandate.params['password'] = password
605        mandate.params['user'] = customer
606        site = grok.getSite()
607        site['mandates'].addMandate(mandate)
608        # Send email with credentials
609        args = {'mandate_id':mandate.mandate_id}
610        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
611        url_info = u'Confirmation link: %s' % mandate_url
612        msg = _('You have successfully created a customer account for the')
613        if ikoba_utils.sendCredentials(IUserAccount(customer),
614            password, url_info, msg):
615            email_sent = customer.email
616        else:
617            email_sent = None
618        self._redirect(email=email_sent, password=password,
619            customer_id=customer.customer_id)
620        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
621        self.context.logger.info(
622            '%s - %s - %s' % (ob_class, customer.customer_id, email_sent))
623        return
624
625
626class CustomerRequestPasswordEmailSent(IkobaPage):
627    """Landing page after successful password request.
628
629    """
630    grok.name('requestpw_complete')
631    grok.require('waeup.Public')
632    grok.template('requestpwmailsent')
633    label = _('Your request was successful.')
634
635    def update(self, email=None, customer_id=None, password=None):
636        self.email = email
637        self.password = password
638        self.customer_id = customer_id
639        return
640
641
642class CustomerFilesUploadPage(IkobaPage):
643    """ View to upload files by customer
644    """
645    grok.context(ICustomer)
646    grok.name('change_portrait')
647    grok.require('waeup.uploadCustomerFile')
648    grok.template('filesuploadpage')
649    label = _('Upload files')
650    pnav = 4
651
652    def update(self):
653        CUSTMANAGE_STATES = getUtility(
654            ICustomersUtils).CUSTMANAGE_CUSTOMER_STATES
655        if self.context.customer.state not in CUSTMANAGE_STATES:
656            emit_lock_message(self)
657            return
658        super(CustomerFilesUploadPage, self).update()
659        return
660
661# Pages for customers
662
663
664class CustomerBaseEditFormPage(IkobaEditFormPage):
665    """ View to edit customer base data
666    """
667    grok.context(ICustomer)
668    grok.name('edit_base')
669    grok.require('waeup.handleCustomer')
670    form_fields = grok.AutoFields(ICustomer).select(
671        'email', 'phone')
672    label = _('Edit base data')
673    pnav = 4
674
675    @action(_('Save'), style='primary')
676    def save(self, **data):
677        msave(self, **data)
678        return
679
680
681class CustomerChangePasswordPage(IkobaEditFormPage):
682    """ View to edit customer passords
683    """
684    grok.context(ICustomer)
685    grok.name('changepassword')
686    grok.require('waeup.handleCustomer')
687    grok.template('changepassword')
688    label = _('Change password')
689    pnav = 4
690
691    @action(_('Save'), style='primary')
692    def save(self, **data):
693        form = self.request.form
694        password = form.get('change_password', None)
695        password_ctl = form.get('change_password_repeat', None)
696        if password:
697            validator = getUtility(IPasswordValidator)
698            errors = validator.validate_password(password, password_ctl)
699            if not errors:
700                IUserAccount(self.context).setPassword(password)
701                self.context.writeLogMessage(self, 'saved: password')
702                self.flash(_('Password changed.'))
703            else:
704                self.flash(' '.join(errors), type="warning")
705        return
706
707class CustomerBasePDFFormPage(IkobaDisplayFormPage):
708    """ Page to display customer base data in pdf files.
709    """
710
711    def __init__(self, context, request, omit_fields=()):
712        self.omit_fields = omit_fields
713        super(CustomerBasePDFFormPage, self).__init__(context, request)
714
715    @property
716    def form_fields(self):
717        form_fields = grok.AutoFields(ICustomer)
718        for field in self.omit_fields:
719            form_fields = form_fields.omit(field)
720        return form_fields
721
722# Pages for customer documents
723
724class DocumentsBreadcrumb(Breadcrumb):
725    """A breadcrumb for the documents container.
726    """
727    grok.context(ICustomerDocumentsContainer)
728    title = _('Documents')
729
730
731class DocumentBreadcrumb(Breadcrumb):
732    """A breadcrumb for the customer container.
733    """
734    grok.context(ICustomerDocument)
735
736    @property
737    def title(self):
738        return self.context.document_id
739
740
741class DocumentsManageFormPage(IkobaEditFormPage):
742    """ Page to manage the customer documents
743
744    This manage form page is for both customers and customers officers.
745    """
746    grok.context(ICustomerDocumentsContainer)
747    grok.name('index')
748    grok.require('waeup.viewCustomer')
749    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
750    grok.template('documentsmanagepage')
751    pnav = 4
752
753    @property
754    def manage_documents_allowed(self):
755        return checkPermission('waeup.editCustomerDocuments', self.context)
756
757    def unremovable(self, document):
758        usertype = getattr(self.request.principal, 'user_type', None)
759        if not usertype:
760            return False
761        if not self.manage_documents_allowed:
762            return True
763        return (self.request.principal.user_type == 'customer' and \
764            document.state in (VERIFIED, REJECTED, EXPIRED))
765
766    @property
767    def label(self):
768        return _('${a}: Documents',
769            mapping = {'a':self.context.__parent__.display_fullname})
770
771    @jsaction(_('Remove selected documents'))
772    def delDocument(self, **data):
773        form = self.request.form
774        if 'val_id' in form:
775            child_id = form['val_id']
776        else:
777            self.flash(_('No document selected.'), type="warning")
778            self.redirect(self.url(self.context))
779            return
780        if not isinstance(child_id, list):
781            child_id = [child_id]
782        deleted = []
783        for id in child_id:
784            # Customers are not allowed to remove used documents
785            document = self.context.get(id, None)
786            if document is not None and not self.unremovable(document):
787                del self.context[id]
788                deleted.append(id)
789        if len(deleted):
790            self.flash(_('Successfully removed: ${a}',
791                mapping = {'a': ', '.join(deleted)}))
792            self.context.writeLogMessage(
793                self,'removed: %s' % ', '.join(deleted))
794        self.redirect(self.url(self.context))
795        return
796
797
798class DocumentAddFormPage(IkobaAddFormPage):
799    """ Page to add an document
800    """
801    grok.context(ICustomerDocumentsContainer)
802    grok.name('adddoc')
803    grok.template('documentaddform')
804    grok.require('waeup.editCustomerDocuments')
805    form_fields = grok.AutoFields(ICustomerDocument)
806    label = _('Add document')
807    pnav = 4
808
809    @property
810    def selectable_doctypes(self):
811        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
812        return sorted(doctypes.items())
813
814    @action(_('Create document'), style='primary')
815    def createDocument(self, **data):
816        form = self.request.form
817        customer = self.context.__parent__
818        doctype = form.get('doctype', None)
819        # Here we can create various instances of CustomerDocument derived
820        # classes depending on the doctype parameter given in form.
821        document = createObject('waeup.%s' % doctype)
822        self.context.addDocument(document)
823        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
824        self.flash(_('${a} created.',
825            mapping = {'a': doctype}))
826        self.context.writeLogMessage(
827            self,'added: %s %s' % (doctype, document.document_id))
828        self.redirect(self.url(self.context))
829        return
830
831    @action(_('Cancel'), validator=NullValidator)
832    def cancel(self, **data):
833        self.redirect(self.url(self.context))
834
835
836class DocumentDisplayFormPage(IkobaDisplayFormPage):
837    """ Page to view a document
838    """
839    grok.context(ICustomerDocument)
840    grok.name('index')
841    grok.require('waeup.viewCustomer')
842    grok.template('documentpage')
843    form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date')
844    pnav = 4
845
846    @property
847    def label(self):
848        return _('${a}', mapping = {'a':self.context.title})
849
850
851class DocumentManageFormPage(IkobaEditFormPage):
852    """ Page to edit a document
853    """
854    grok.context(ICustomerDocument)
855    grok.name('manage')
856    grok.require('waeup.manageCustomer')
857    grok.template('documenteditpage')
858    form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date')
859    pnav = 4
860    deletion_warning = _('Are you sure?')
861
862    def update(self):
863        if not self.context.is_editable_by_manager:
864            emit_lock_message(self)
865            return
866        return super(DocumentManageFormPage, self).update()
867
868    @property
869    def label(self):
870        return _('${a}', mapping = {'a':self.context.title})
871
872    @action(_('Save'), style='primary')
873    def save(self, **data):
874        msave(self, **data)
875        return
876
877
878class DocumentEditFormPage(DocumentManageFormPage):
879    """ Page to edit a document
880    """
881    grok.name('edit')
882    grok.require('waeup.handleCustomer')
883
884    def update(self):
885        if not self.context.is_editable_by_customer:
886            emit_lock_message(self)
887            return
888        return super(DocumentEditFormPage, self).update()
889
890    @action(_('Save'), style='primary')
891    def save(self, **data):
892        msave(self, **data)
893        return
894
895    @action(_('Final Submit'), warning=WARNING)
896    def finalsubmit(self, **data):
897        msave(self, **data)
898        IWorkflowInfo(self.context).fireTransition('submit')
899        self.flash(_('Form has been submitted.'))
900        self.redirect(self.url(self.context))
901        return
902
903
904class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
905    """ View to trigger customer document transitions
906    """
907    grok.context(ICustomerDocument)
908    grok.name('trigtrans')
909    grok.require('waeup.triggerTransition')
910    grok.template('trigtrans')
911    label = _('Trigger document transition')
912    pnav = 4
913
914    def update(self):
915        return super(IkobaEditFormPage, self).update()
916
917    def getTransitions(self):
918        """Return a list of dicts of allowed transition ids and titles.
919
920        Each list entry provides keys ``name`` and ``title`` for
921        internal name and (human readable) title of a single
922        transition.
923        """
924        wf_info = IWorkflowInfo(self.context)
925        allowed_transitions = [t for t in wf_info.getManualTransitions()]
926        return [dict(name='', title=_('No transition'))] +[
927            dict(name=x, title=y) for x, y in allowed_transitions]
928
929    @action(_('Save'), style='primary')
930    def save(self, **data):
931        form = self.request.form
932        if 'transition' in form and form['transition']:
933            transition_id = form['transition']
934            wf_info = IWorkflowInfo(self.context)
935            try:
936                wf_info.fireTransition(transition_id)
937            except InvalidTransitionError, error:
938                self.flash(error, type="warning")
939        return
940
941class PDFDocumentsOverviewPage(UtilityView, grok.View):
942    """Deliver an overview slip.
943    """
944    grok.context(ICustomerDocumentsContainer)
945    grok.name('documents_overview_slip.pdf')
946    grok.require('waeup.viewCustomer')
947    prefix = 'form'
948
949    omit_fields = ('suspended', 'sex',
950                   'suspended_comment',)
951
952    form_fields = None
953
954    @property
955    def label(self):
956        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
957        return translate(_('Documents of'),
958            'waeup.ikoba', target_language=portal_language) \
959            + ' %s' % self.context.customer.display_fullname
960
961    @property
962    def tabletitle(self):
963        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
964        tabletitle = []
965        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
966            target_language=portal_language))
967        return tabletitle
968
969    def render(self):
970        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
971        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
972        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
973        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
974        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
975        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
976        tableheader = []
977        tabledata = []
978        contenttitle = []
979        for i in range(1,3):
980            tabledata.append(sorted(
981                [value for value in self.context.values()]))
982            tableheader.append([(Id, 'document_id', 2),
983                             (Title, 'title', 6),
984                             (Type, 'translated_class_name', 6),
985                             (State, 'translated_state', 2),
986                             (LT, 'formatted_transition_date', 3),
987                             ])
988        customerview = CustomerBasePDFFormPage(self.context.customer,
989            self.request, self.omit_fields)
990        customers_utils = getUtility(ICustomersUtils)
991        return customers_utils.renderPDF(
992            self, 'overview_slip.pdf',
993            self.context.customer, customerview,
994            tableheader=tableheader,
995            tabledata=tabledata,
996            omit_fields=self.omit_fields)
997
998
999class PDFDocumentSlipPage(UtilityView, grok.View):
1000    """Deliver pdf file including metadata.
1001    """
1002    grok.context(ICustomerDocument)
1003    grok.name('document_slip.pdf')
1004    grok.require('waeup.viewCustomer')
1005    prefix = 'form'
1006
1007    omit_fields = ('suspended', 'sex',
1008                   'suspended_comment',)
1009
1010    #form_fields = grok.AutoFields(ICustomerPDFDocument).omit(
1011    #    'last_transition_date')
1012    form_fields =()
1013
1014    @property
1015    def label(self):
1016        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1017        return '%s of %s\nTitle: %s' % (
1018            self.context.translated_class_name,
1019            self.context.customer.display_fullname,
1020            self.context.title)
1021
1022    def render(self):
1023        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1024        customerview = CustomerBasePDFFormPage(self.context.customer,
1025            self.request, self.omit_fields)
1026        customers_utils = getUtility(ICustomersUtils)
1027        return customers_utils.renderPDF(
1028            self, 'document_slip.pdf',
1029            self.context.customer, customerview,
1030            omit_fields=self.omit_fields)
1031
1032
1033class PDFMergeDocumentSlipPage(PDFDocumentSlipPage):
1034    """Deliver pdf file including metadata.
1035    """
1036    grok.context(ICustomerPDFDocument)
1037
1038    def render(self):
1039        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1040        customerview = CustomerBasePDFFormPage(self.context.customer,
1041            self.request, self.omit_fields)
1042        customers_utils = getUtility(ICustomersUtils)
1043        if self.context.state == VERIFIED:
1044            watermark_path = os.path.join(
1045                os.path.dirname(__file__), 'static', 'verified.pdf')
1046        else:
1047            watermark_path = os.path.join(
1048                os.path.dirname(__file__), 'static', 'unverified.pdf')
1049        watermark = open(watermark_path, 'rb')
1050        return customers_utils.renderPDF(
1051            self, 'pdfdocument_slip.pdf',
1052            self.context.customer, customerview,
1053            omit_fields=self.omit_fields,
1054            mergefiles=self.context.connected_files,
1055            watermark=watermark)
1056
1057# Pages for customer contracts
1058
1059class ContractsBreadcrumb(Breadcrumb):
1060    """A breadcrumb for the contracts container.
1061    """
1062    grok.context(IContractsContainer)
1063    title = _('Contracts')
1064
1065
1066class ContractBreadcrumb(Breadcrumb):
1067    """A breadcrumb for the customer container.
1068    """
1069    grok.context(IContract)
1070
1071    @property
1072    def title(self):
1073        return self.context.contract_id
1074
1075
1076class ContractsManageFormPage(IkobaEditFormPage):
1077    """ Page to manage the customer contracts
1078
1079    This manage form page is for both customers and officers.
1080    """
1081    grok.context(IContractsContainer)
1082    grok.name('index')
1083    grok.require('waeup.viewCustomer')
1084    form_fields = grok.AutoFields(IContractsContainer)
1085    grok.template('contractsmanagepage')
1086    pnav = 4
1087
1088    @property
1089    def manage_contracts_allowed(self):
1090        return checkPermission('waeup.editContracts', self.context)
1091
1092    def unremovable(self, contract):
1093        usertype = getattr(self.request.principal, 'user_type', None)
1094        if not usertype:
1095            return False
1096        if not self.manage_contracts_allowed:
1097            return True
1098        return (self.request.principal.user_type == 'customer' and \
1099            contract.state in (APPROVED, REJECTED, EXPIRED))
1100
1101    @property
1102    def label(self):
1103        return _('${a}: Contracts',
1104            mapping = {'a':self.context.__parent__.display_fullname})
1105
1106    @jsaction(_('Remove selected contracts'))
1107    def delContract(self, **data):
1108        form = self.request.form
1109        if 'val_id' in form:
1110            child_id = form['val_id']
1111        else:
1112            self.flash(_('No contract selected.'), type="warning")
1113            self.redirect(self.url(self.context))
1114            return
1115        if not isinstance(child_id, list):
1116            child_id = [child_id]
1117        deleted = []
1118        for id in child_id:
1119            # Customers are not allowed to remove used contracts
1120            contract = self.context.get(id, None)
1121            if contract is not None and not self.unremovable(contract):
1122                del self.context[id]
1123                deleted.append(id)
1124        if len(deleted):
1125            self.flash(_('Successfully removed: ${a}',
1126                mapping = {'a': ', '.join(deleted)}))
1127            self.context.writeLogMessage(
1128                self,'removed: %s' % ', '.join(deleted))
1129        self.redirect(self.url(self.context))
1130        return
1131
1132
1133class ContractAddFormPage(IkobaAddFormPage):
1134    """ Page to add an contract
1135    """
1136    grok.context(IContractsContainer)
1137    grok.name('addapp')
1138    grok.template('contractaddform')
1139    grok.require('waeup.editContracts')
1140    form_fields = grok.AutoFields(IContract)
1141    label = _('Add contract')
1142    pnav = 4
1143
1144    @property
1145    def selectable_contypes(self):
1146        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
1147        return sorted(contypes.items())
1148
1149    @action(_('Create contract'), style='primary')
1150    def createContract(self, **data):
1151        form = self.request.form
1152        customer = self.context.__parent__
1153        contype = form.get('contype', None)
1154        # Here we can create various instances of Contract derived
1155        # classes depending on the contype parameter given in form.
1156        contract = createObject('waeup.%s' % contype)
1157        self.context.addContract(contract)
1158        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
1159        self.flash(_('${a} created.',
1160            mapping = {'a': contype}))
1161        self.context.writeLogMessage(
1162            self,'added: %s %s' % (contype, contract.contract_id))
1163        self.redirect(self.url(self.context))
1164        return
1165
1166    @action(_('Cancel'), validator=NullValidator)
1167    def cancel(self, **data):
1168        self.redirect(self.url(self.context))
1169
1170
1171class ContractDisplayFormPage(IkobaDisplayFormPage):
1172    """ Page to view a contract
1173    """
1174    grok.context(IContract)
1175    grok.name('index')
1176    grok.require('waeup.viewCustomer')
1177    grok.template('contractpage')
1178    pnav = 4
1179
1180    @property
1181    def form_fields(self):
1182        form_fields = grok.AutoFields(self.context.form_fields_interface).omit(
1183            'last_transition_date')
1184        for field in form_fields:
1185            if field.__name__.endswith('_object'):
1186                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1187        return form_fields
1188
1189    @property
1190    def label(self):
1191        return _('${a}', mapping = {'a':self.context.title})
1192
1193
1194class ContractManageFormPage(IkobaEditFormPage):
1195    """ Page to edit a contract
1196    """
1197    grok.context(IContract)
1198    grok.name('manage')
1199    grok.require('waeup.manageCustomer')
1200    grok.template('contracteditpage')
1201    pnav = 4
1202    deletion_warning = _('Are you sure?')
1203
1204    @property
1205    def form_fields(self):
1206        return grok.AutoFields(self.context.form_fields_interface).omit(
1207            'last_transition_date')
1208
1209    @property
1210    def label(self):
1211        return _('${a}', mapping = {'a':self.context.title})
1212
1213    @action(_('Save'), style='primary')
1214    def save(self, **data):
1215        msave(self, **data)
1216        return
1217
1218
1219class ContractEditFormPage(ContractManageFormPage):
1220    """ Page to edit a contract
1221    """
1222    grok.name('edit')
1223    grok.require('waeup.handleCustomer')
1224
1225    @property
1226    def form_fields(self):
1227        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1228            'last_transition_date')
1229
1230    def update(self):
1231        if not self.context.is_editable_by_customer:
1232            emit_lock_message(self)
1233            return
1234        return super(ContractEditFormPage, self).update()
1235
1236    @action(_('Save'), style='primary')
1237    def save(self, **data):
1238        msave(self, **data)
1239        return
1240
1241    @action(_('Apply now (final submit)'), warning=WARNING)
1242    def finalsubmit(self, **data):
1243        msave(self, **data)
1244        IWorkflowInfo(self.context).fireTransition('submit')
1245        self.flash(_('Form has been submitted.'))
1246        self.redirect(self.url(self.context))
1247        return
1248
1249
1250class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1251    """ View to trigger customer contract transitions
1252    """
1253    grok.context(IContract)
1254    grok.name('trigtrans')
1255    grok.require('waeup.triggerTransition')
1256    grok.template('trigtrans')
1257    label = _('Trigger contract transition')
1258    pnav = 4
1259
1260    def update(self):
1261        return super(IkobaEditFormPage, self).update()
1262
1263    def getTransitions(self):
1264        """Return a list of dicts of allowed transition ids and titles.
1265
1266        Each list entry provides keys ``name`` and ``title`` for
1267        internal name and (human readable) title of a single
1268        transition.
1269        """
1270        wf_info = IWorkflowInfo(self.context)
1271        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1272        return [dict(name='', title=_('No transition'))] +[
1273            dict(name=x, title=y) for x, y in allowed_transitions]
1274
1275    @action(_('Save'), style='primary')
1276    def save(self, **data):
1277        form = self.request.form
1278        if 'transition' in form and form['transition']:
1279            transition_id = form['transition']
1280            wf_info = IWorkflowInfo(self.context)
1281            try:
1282                wf_info.fireTransition(transition_id)
1283            except InvalidTransitionError, error:
1284                self.flash(error, type="warning")
1285        return
1286
1287class PDFContractsOverviewPage(UtilityView, grok.View):
1288    """Deliver an overview slip.
1289    """
1290    grok.context(IContractsContainer)
1291    grok.name('contracts_overview_slip.pdf')
1292    grok.require('waeup.viewCustomer')
1293    prefix = 'form'
1294
1295    omit_fields = ('suspended', 'sex',
1296                   'suspended_comment',)
1297
1298    form_fields = None
1299
1300    @property
1301    def label(self):
1302        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1303        return translate(_('Contracts of'),
1304            'waeup.ikoba', target_language=portal_language) \
1305            + ' %s' % self.context.customer.display_fullname
1306
1307    @property
1308    def tabletitle(self):
1309        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1310        tabletitle = []
1311        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1312            target_language=portal_language))
1313        return tabletitle
1314
1315    def render(self):
1316        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1317        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1318        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1319        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1320        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1321        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1322        tableheader = []
1323        tabledata = []
1324        contenttitle = []
1325        for i in range(1,3):
1326            tabledata.append(sorted(
1327                [value for value in self.context.values()]))
1328            tableheader.append([(Id, 'contract_id', 2),
1329                             (Title, 'title', 6),
1330                             (Type, 'translated_class_name', 6),
1331                             (State, 'translated_state', 2),
1332                             (LT, 'formatted_transition_date', 3),
1333                             ])
1334        customerview = CustomerBasePDFFormPage(self.context.customer,
1335            self.request, self.omit_fields)
1336        customers_utils = getUtility(ICustomersUtils)
1337        return customers_utils.renderPDF(
1338            self, 'overview_slip.pdf',
1339            self.context.customer, customerview,
1340            tableheader=tableheader,
1341            tabledata=tabledata,
1342            omit_fields=self.omit_fields)
1343
1344
1345class PDFContractSlipPage(UtilityView, grok.View):
1346    """Deliver pdf file including metadata.
1347    """
1348    grok.context(IContract)
1349    grok.name('contract_slip.pdf')
1350    grok.require('waeup.viewCustomer')
1351    prefix = 'form'
1352
1353    omit_fields = ('suspended', 'sex',
1354                   'suspended_comment',)
1355
1356    #form_fields = grok.AutoFields(ICustomerPDFContract).omit(
1357    #    'last_transition_date')
1358    form_fields =()
1359
1360    @property
1361    def label(self):
1362        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1363        return '%s of %s\nTitle: %s' % (
1364            self.context.translated_class_name,
1365            self.context.customer.display_fullname,
1366            self.context.title)
1367
1368    def render(self):
1369        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1370        customerview = CustomerBasePDFFormPage(self.context.customer,
1371            self.request, self.omit_fields)
1372        customers_utils = getUtility(ICustomersUtils)
1373        return customers_utils.renderPDF(
1374            self, 'contract_slip.pdf',
1375            self.context.customer, customerview,
1376            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.