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

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

Add browser tests and fix.

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