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

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

Add event subscriber handle_product_removed which ensures that also referrers to customer application objects are removed when a product is deleted.

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