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

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

Add application browser components.

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