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

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

Add second customer document class.
Select document factory when adding documents.
Add last_transition_date attribute and further property attributes to documents.

  • Property svn:keywords set to Id
File size: 34.3 KB
Line 
1## $Id: browser.py 12053 2014-11-25 08:19:54Z 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)
38from waeup.ikoba.browser.layout import (
39    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
40    IkobaForm, NullValidator, jsaction, action, UtilityView)
41from waeup.ikoba.widgets.datewidget import (
42    FriendlyDateWidget, FriendlyDateDisplayWidget,
43    FriendlyDatetimeDisplayWidget)
44from waeup.ikoba.browser.pages import ContactAdminForm
45from waeup.ikoba.browser.breadcrumbs import Breadcrumb
46from waeup.ikoba.browser.interfaces import ICaptchaManager
47from waeup.ikoba.mandates.mandate import PasswordMandate
48from waeup.ikoba.documents.workflow import VERIFIED, REJECTED, OUTDATED
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    )
54from waeup.ikoba.customers.catalog import search
55
56grok.context(IIkobaObject)
57
58WARNING = _('You can not edit your document after final submission.'
59            ' You really want to submit?')
60
61
62# Save function used for save methods in pages
63def msave(view, **data):
64    changed_fields = view.applyData(view.context, **data)
65    # Turn list of lists into single list
66    if changed_fields:
67        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
68    fields_string = ' + '.join(changed_fields)
69    view.flash(_('Form has been saved.'))
70    if fields_string:
71        view.context.writeLogMessage(view, 'saved: %s' % fields_string)
72    return
73
74
75def emit_lock_message(view):
76    """Flash a lock message.
77    """
78    view.flash(_('The requested form is locked (read-only).'), type="warning")
79    view.redirect(view.url(view.context))
80    return
81
82
83class CustomersBreadcrumb(Breadcrumb):
84    """A breadcrumb for the customers container.
85    """
86    grok.context(ICustomersContainer)
87    title = _('Customers')
88
89    @property
90    def target(self):
91        user = get_current_principal()
92        if getattr(user, 'user_type', None) == 'customer':
93            return None
94        return self.viewname
95
96
97class CustomerBreadcrumb(Breadcrumb):
98    """A breadcrumb for the customer container.
99    """
100    grok.context(ICustomer)
101
102    def title(self):
103        return self.context.display_fullname
104
105
106class CustomersContainerPage(IkobaPage):
107    """The standard view for customer containers.
108    """
109    grok.context(ICustomersContainer)
110    grok.name('index')
111    grok.require('waeup.viewCustomersContainer')
112    grok.template('containerpage')
113    label = _('Find customers')
114    search_button = _('Find customer(s)')
115    pnav = 4
116
117    def update(self, *args, **kw):
118        form = self.request.form
119        self.hitlist = []
120        if form.get('searchtype', None) == 'suspended':
121            self.searchtype = form['searchtype']
122            self.searchterm = None
123        elif 'searchterm' in form and form['searchterm']:
124            self.searchterm = form['searchterm']
125            self.searchtype = form['searchtype']
126        elif 'old_searchterm' in form:
127            self.searchterm = form['old_searchterm']
128            self.searchtype = form['old_searchtype']
129        else:
130            if 'search' in form:
131                self.flash(_('Empty search string'), type="warning")
132            return
133        if self.searchtype == 'current_session':
134            try:
135                self.searchterm = int(self.searchterm)
136            except ValueError:
137                self.flash(_('Only year dates allowed (e.g. 2011).'),
138                           type="danger")
139                return
140        self.hitlist = search(query=self.searchterm,
141            searchtype=self.searchtype, view=self)
142        if not self.hitlist:
143            self.flash(_('No customer found.'), type="warning")
144        return
145
146
147class CustomersContainerManagePage(IkobaPage):
148    """The manage page for customer containers.
149    """
150    grok.context(ICustomersContainer)
151    grok.name('manage')
152    grok.require('waeup.manageCustomer')
153    grok.template('containermanagepage')
154    pnav = 4
155    label = _('Manage customer section')
156    search_button = _('Find customer(s)')
157    remove_button = _('Remove selected')
158
159    def update(self, *args, **kw):
160        form = self.request.form
161        self.hitlist = []
162        if form.get('searchtype', None) == 'suspended':
163            self.searchtype = form['searchtype']
164            self.searchterm = None
165        elif 'searchterm' in form and form['searchterm']:
166            self.searchterm = form['searchterm']
167            self.searchtype = form['searchtype']
168        elif 'old_searchterm' in form:
169            self.searchterm = form['old_searchterm']
170            self.searchtype = form['old_searchtype']
171        else:
172            if 'search' in form:
173                self.flash(_('Empty search string'), type="warning")
174            return
175        if self.searchtype == 'current_session':
176            try:
177                self.searchterm = int(self.searchterm)
178            except ValueError:
179                self.flash(_('Only year dates allowed (e.g. 2011).'),
180                           type="danger")
181                return
182        if not 'entries' in form:
183            self.hitlist = search(query=self.searchterm,
184                searchtype=self.searchtype, view=self)
185            if not self.hitlist:
186                self.flash(_('No customer found.'), type="warning")
187            if 'remove' in form:
188                self.flash(_('No item selected.'), type="warning")
189            return
190        entries = form['entries']
191        if isinstance(entries, basestring):
192            entries = [entries]
193        deleted = []
194        for entry in entries:
195            if 'remove' in form:
196                del self.context[entry]
197                deleted.append(entry)
198        self.hitlist = search(query=self.searchterm,
199            searchtype=self.searchtype, view=self)
200        if len(deleted):
201            self.flash(_('Successfully removed: ${a}',
202                mapping={'a': ','.join(deleted)}))
203        return
204
205
206class CustomerAddFormPage(IkobaAddFormPage):
207    """Add-form to add a customer.
208    """
209    grok.context(ICustomersContainer)
210    grok.require('waeup.manageCustomer')
211    grok.name('addcustomer')
212    form_fields = grok.AutoFields(ICustomer).select(
213        'firstname', 'middlename', 'lastname', 'reg_number')
214    label = _('Add customer')
215    pnav = 4
216
217    @action(_('Create customer record'), style='primary')
218    def addCustomer(self, **data):
219        customer = createObject(u'waeup.Customer')
220        self.applyData(customer, **data)
221        self.context.addCustomer(customer)
222        self.flash(_('Customer record created.'))
223        self.redirect(self.url(self.context[customer.customer_id], 'index'))
224        return
225
226
227class LoginAsCustomerStep1(IkobaEditFormPage):
228    """ View to temporarily set a customer password.
229    """
230    grok.context(ICustomer)
231    grok.name('loginasstep1')
232    grok.require('waeup.loginAsCustomer')
233    grok.template('loginasstep1')
234    pnav = 4
235
236    def label(self):
237        return _(u'Set temporary password for ${a}',
238            mapping={'a': self.context.display_fullname})
239
240    @action('Set password now', style='primary')
241    def setPassword(self, *args, **data):
242        ikoba_utils = getUtility(IIkobaUtils)
243        password = ikoba_utils.genPassword()
244        self.context.setTempPassword(self.request.principal.id, password)
245        self.context.writeLogMessage(
246            self, 'temp_password generated: %s' % password)
247        args = {'password': password}
248        self.redirect(self.url(self.context) +
249            '/loginasstep2?%s' % urlencode(args))
250        return
251
252
253class LoginAsCustomerStep2(IkobaPage):
254    """ View to temporarily login as customer with a temporary password.
255    """
256    grok.context(ICustomer)
257    grok.name('loginasstep2')
258    grok.require('waeup.Public')
259    grok.template('loginasstep2')
260    login_button = _('Login now')
261    pnav = 4
262
263    def label(self):
264        return _(u'Login as ${a}',
265            mapping={'a': self.context.customer_id})
266
267    def update(self, SUBMIT=None, password=None):
268        self.password = password
269        if SUBMIT is not None:
270            self.flash(_('You successfully logged in as customer.'))
271            self.redirect(self.url(self.context))
272        return
273
274
275class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
276    """ Page to display customer base data
277    """
278    grok.context(ICustomer)
279    grok.name('index')
280    grok.require('waeup.viewCustomer')
281    grok.template('basepage')
282    form_fields = grok.AutoFields(ICustomer).omit(
283        'password', 'suspended', 'suspended_comment')
284    pnav = 4
285
286    @property
287    def label(self):
288        if self.context.suspended:
289            return _('${a}: Base Data (account deactivated)',
290                mapping={'a': self.context.display_fullname})
291        return  _('${a}: Base Data',
292            mapping={'a': self.context.display_fullname})
293
294    @property
295    def hasPassword(self):
296        if self.context.password:
297            return _('set')
298        return _('unset')
299
300
301class ContactCustomerForm(ContactAdminForm):
302    grok.context(ICustomer)
303    grok.name('contactcustomer')
304    grok.require('waeup.viewCustomer')
305    pnav = 4
306    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
307
308    def update(self, subject=u'', body=u''):
309        super(ContactCustomerForm, self).update()
310        self.form_fields.get('subject').field.default = subject
311        self.form_fields.get('body').field.default = body
312        return
313
314    def label(self):
315        return _(u'Send message to ${a}',
316            mapping={'a': self.context.display_fullname})
317
318    @action('Send message now', style='primary')
319    def send(self, *args, **data):
320        try:
321            email = self.request.principal.email
322        except AttributeError:
323            email = self.config.email_admin
324        usertype = getattr(self.request.principal,
325                           'user_type', 'system').title()
326        ikoba_utils = getUtility(IIkobaUtils)
327        success = ikoba_utils.sendContactForm(
328                self.request.principal.title, email,
329                self.context.display_fullname, self.context.email,
330                self.request.principal.id,usertype,
331                self.config.name,
332                data['body'], data['subject'])
333        if success:
334            self.flash(_('Your message has been sent.'))
335        else:
336            self.flash(_('An smtp server error occurred.'), type="danger")
337        return
338
339
340class CustomerBaseManageFormPage(IkobaEditFormPage):
341    """ View to manage customer base data
342    """
343    grok.context(ICustomer)
344    grok.name('manage_base')
345    grok.require('waeup.manageCustomer')
346    form_fields = grok.AutoFields(ICustomer).omit(
347        'customer_id', 'adm_code', 'suspended')
348    grok.template('basemanagepage')
349    label = _('Manage base data')
350    pnav = 4
351
352    def update(self):
353        super(CustomerBaseManageFormPage, self).update()
354        self.wf_info = IWorkflowInfo(self.context)
355        return
356
357    @action(_('Save'), style='primary')
358    def save(self, **data):
359        form = self.request.form
360        password = form.get('password', None)
361        password_ctl = form.get('control_password', None)
362        if password:
363            validator = getUtility(IPasswordValidator)
364            errors = validator.validate_password(password, password_ctl)
365            if errors:
366                self.flash(' '.join(errors), type="danger")
367                return
368        changed_fields = self.applyData(self.context, **data)
369        # Turn list of lists into single list
370        if changed_fields:
371            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
372        else:
373            changed_fields = []
374        if password:
375            # Now we know that the form has no errors and can set password
376            IUserAccount(self.context).setPassword(password)
377            changed_fields.append('password')
378        fields_string = ' + '.join(changed_fields)
379        self.flash(_('Form has been saved.'))
380        if fields_string:
381            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
382        return
383
384
385class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
386    """ View to trigger customer workflow transitions
387    """
388    grok.context(ICustomer)
389    grok.name('trigtrans')
390    grok.require('waeup.triggerTransition')
391    grok.template('trigtrans')
392    label = _('Trigger registration transition')
393    pnav = 4
394
395    def getTransitions(self):
396        """Return a list of dicts of allowed transition ids and titles.
397
398        Each list entry provides keys ``name`` and ``title`` for
399        internal name and (human readable) title of a single
400        transition.
401        """
402        wf_info = IWorkflowInfo(self.context)
403        allowed_transitions = [t for t in wf_info.getManualTransitions()]
404        return [dict(name='', title=_('No transition'))] +[
405            dict(name=x, title=y) for x, y in allowed_transitions]
406
407    @action(_('Save'), style='primary')
408    def save(self, **data):
409        form = self.request.form
410        if 'transition' in form and form['transition']:
411            transition_id = form['transition']
412            wf_info = IWorkflowInfo(self.context)
413            wf_info.fireTransition(transition_id)
414        return
415
416
417class CustomerActivatePage(UtilityView, grok.View):
418    """ Activate customer account
419    """
420    grok.context(ICustomer)
421    grok.name('activate')
422    grok.require('waeup.manageCustomer')
423
424    def update(self):
425        self.context.suspended = False
426        self.context.writeLogMessage(self, 'account activated')
427        history = IObjectHistory(self.context)
428        history.addMessage('Customer account activated')
429        self.flash(_('Customer account has been activated.'))
430        self.redirect(self.url(self.context))
431        return
432
433    def render(self):
434        return
435
436
437class CustomerDeactivatePage(UtilityView, grok.View):
438    """ Deactivate customer account
439    """
440    grok.context(ICustomer)
441    grok.name('deactivate')
442    grok.require('waeup.manageCustomer')
443
444    def update(self):
445        self.context.suspended = True
446        self.context.writeLogMessage(self, 'account deactivated')
447        history = IObjectHistory(self.context)
448        history.addMessage('Customer account deactivated')
449        self.flash(_('Customer account has been deactivated.'))
450        self.redirect(self.url(self.context))
451        return
452
453    def render(self):
454        return
455
456
457class CustomerHistoryPage(IkobaPage):
458    """ Page to display customer history
459    """
460    grok.context(ICustomer)
461    grok.name('history')
462    grok.require('waeup.viewCustomer')
463    grok.template('customerhistory')
464    pnav = 4
465
466    @property
467    def label(self):
468        return _('${a}: History', mapping={'a':self.context.display_fullname})
469
470
471class CustomerRequestPasswordPage(IkobaAddFormPage):
472    """Captcha'd password request page for customers.
473    """
474    grok.name('requestpw')
475    grok.require('waeup.Anonymous')
476    grok.template('requestpw')
477    form_fields = grok.AutoFields(ICustomerRequestPW)
478    label = _('Request password for first-time login')
479
480    def update(self):
481        # Handle captcha
482        self.captcha = getUtility(ICaptchaManager).getCaptcha()
483        self.captcha_result = self.captcha.verify(self.request)
484        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
485        return
486
487    def _redirect(self, email, password, customer_id):
488        # Forward only email address to landing page in base package.
489        self.redirect(self.url(self.context, 'requestpw_complete',
490            data=dict(email=email)))
491        return
492
493    def _pw_used(self):
494        # XXX: False if password has not been used. We need an extra
495        #      attribute which remembers if customer logged in.
496        return True
497
498    @action(_('Send login credentials to email address'), style='primary')
499    def get_credentials(self, **data):
500        if not self.captcha_result.is_valid:
501            # Captcha will display error messages automatically.
502            # No need to flash something.
503            return
504        number = data.get('number','')
505        firstname = data.get('firstname','')
506        cat = getUtility(ICatalog, name='customers_catalog')
507        results = list(
508            cat.searchResults(reg_number=(number, number)))
509        if results:
510            customer = results[0]
511            if getattr(customer,'firstname',None) is None:
512                self.flash(_('An error occurred.'), type="danger")
513                return
514            elif customer.firstname.lower() != firstname.lower():
515                # Don't tell the truth here. Anonymous must not
516                # know that a record was found and only the firstname
517                # verification failed.
518                self.flash(_('No customer record found.'), type="warning")
519                return
520            elif customer.password is not None and self._pw_used:
521                self.flash(_('Your password has already been set and used. '
522                             'Please proceed to the login page.'),
523                           type="warning")
524                return
525            # Store email address but nothing else.
526            customer.email = data['email']
527            notify(grok.ObjectModifiedEvent(customer))
528        else:
529            # No record found, this is the truth.
530            self.flash(_('No customer record found.'), type="warning")
531            return
532
533        ikoba_utils = getUtility(IIkobaUtils)
534        password = ikoba_utils.genPassword()
535        mandate = PasswordMandate()
536        mandate.params['password'] = password
537        mandate.params['user'] = customer
538        site = grok.getSite()
539        site['mandates'].addMandate(mandate)
540        # Send email with credentials
541        args = {'mandate_id':mandate.mandate_id}
542        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
543        url_info = u'Confirmation link: %s' % mandate_url
544        msg = _('You have successfully requested a password for the')
545        if ikoba_utils.sendCredentials(IUserAccount(customer),
546            password, url_info, msg):
547            email_sent = customer.email
548        else:
549            email_sent = None
550        self._redirect(email=email_sent, password=password,
551            customer_id=customer.customer_id)
552        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
553        self.context.logger.info(
554            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
555        return
556
557
558class CustomerCreateAccountPage(IkobaAddFormPage):
559    """Captcha'd account creation page for customers.
560    """
561    grok.name('createaccount')
562    grok.require('waeup.Anonymous')
563    grok.template('createaccount')
564    form_fields = grok.AutoFields(ICustomerCreate)
565    label = _('Create customer account')
566
567    def update(self):
568        # Handle captcha
569        self.captcha = getUtility(ICaptchaManager).getCaptcha()
570        self.captcha_result = self.captcha.verify(self.request)
571        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
572        return
573
574    def _redirect(self, email, password, customer_id):
575        # Forward only email address to landing page in base package.
576        self.redirect(self.url(self.context, 'requestpw_complete',
577            data=dict(email=email)))
578        return
579
580    @action(_('Send login credentials to email address'), style='primary')
581    def create_account(self, **data):
582        if not self.captcha_result.is_valid:
583            # Captcha will display error messages automatically.
584            # No need to flash something.
585            return
586        customer = createObject(u'waeup.Customer')
587        customer.firstname = data.get('firstname','')
588        customer.middlename = data.get('middlename','')
589        customer.lastname = data.get('lastname','')
590        customer.email = data.get('email','')
591        self.context['customers'].addCustomer(customer)
592        ikoba_utils = getUtility(IIkobaUtils)
593        password = ikoba_utils.genPassword()
594        mandate = PasswordMandate()
595        mandate.params['password'] = password
596        mandate.params['user'] = customer
597        site = grok.getSite()
598        site['mandates'].addMandate(mandate)
599        # Send email with credentials
600        args = {'mandate_id':mandate.mandate_id}
601        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
602        url_info = u'Confirmation link: %s' % mandate_url
603        msg = _('You have successfully created a customer account for the')
604        if ikoba_utils.sendCredentials(IUserAccount(customer),
605            password, url_info, msg):
606            email_sent = customer.email
607        else:
608            email_sent = None
609        self._redirect(email=email_sent, password=password,
610            customer_id=customer.customer_id)
611        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
612        self.context.logger.info(
613            '%s - %s - %s' % (ob_class, customer.customer_id, email_sent))
614        return
615
616
617class CustomerRequestPasswordEmailSent(IkobaPage):
618    """Landing page after successful password request.
619
620    """
621    grok.name('requestpw_complete')
622    grok.require('waeup.Public')
623    grok.template('requestpwmailsent')
624    label = _('Your request was successful.')
625
626    def update(self, email=None, customer_id=None, password=None):
627        self.email = email
628        self.password = password
629        self.customer_id = customer_id
630        return
631
632
633class CustomerFilesUploadPage(IkobaPage):
634    """ View to upload files by customer
635    """
636    grok.context(ICustomer)
637    grok.name('change_portrait')
638    grok.require('waeup.uploadCustomerFile')
639    grok.template('filesuploadpage')
640    label = _('Upload files')
641    pnav = 4
642
643    def update(self):
644        CUSTMANAGE_STATES = getUtility(ICustomersUtils).CUSTMANAGE_STATES
645        if self.context.customer.state not in CUSTMANAGE_STATES:
646            emit_lock_message(self)
647            return
648        super(CustomerFilesUploadPage, self).update()
649        return
650
651# Pages for customers
652
653
654class CustomerBaseEditFormPage(IkobaEditFormPage):
655    """ View to edit customer base data
656    """
657    grok.context(ICustomer)
658    grok.name('edit_base')
659    grok.require('waeup.handleCustomer')
660    form_fields = grok.AutoFields(ICustomer).select(
661        'email', 'phone')
662    label = _('Edit base data')
663    pnav = 4
664
665    @action(_('Save'), style='primary')
666    def save(self, **data):
667        msave(self, **data)
668        return
669
670
671class CustomerChangePasswordPage(IkobaEditFormPage):
672    """ View to edit customer passords
673    """
674    grok.context(ICustomer)
675    grok.name('changepassword')
676    grok.require('waeup.handleCustomer')
677    grok.template('changepassword')
678    label = _('Change password')
679    pnav = 4
680
681    @action(_('Save'), style='primary')
682    def save(self, **data):
683        form = self.request.form
684        password = form.get('change_password', None)
685        password_ctl = form.get('change_password_repeat', None)
686        if password:
687            validator = getUtility(IPasswordValidator)
688            errors = validator.validate_password(password, password_ctl)
689            if not errors:
690                IUserAccount(self.context).setPassword(password)
691                self.context.writeLogMessage(self, 'saved: password')
692                self.flash(_('Password changed.'))
693            else:
694                self.flash(' '.join(errors), type="warning")
695        return
696
697class CustomerBasePDFFormPage(IkobaDisplayFormPage):
698    """ Page to display customer base data in pdf files.
699    """
700
701    def __init__(self, context, request, omit_fields=()):
702        self.omit_fields = omit_fields
703        super(CustomerBasePDFFormPage, self).__init__(context, request)
704
705    @property
706    def form_fields(self):
707        form_fields = grok.AutoFields(ICustomer)
708        for field in self.omit_fields:
709            form_fields = form_fields.omit(field)
710        return form_fields
711
712# Pages for customer documents
713
714class DocumentsBreadcrumb(Breadcrumb):
715    """A breadcrumb for the documents container.
716    """
717    grok.context(ICustomerDocumentsContainer)
718    title = _('Documents')
719
720
721class DocumentBreadcrumb(Breadcrumb):
722    """A breadcrumb for the customer container.
723    """
724    grok.context(ICustomerDocument)
725
726    @property
727    def title(self):
728        return self.context.document_id
729
730
731class DocumentsManageFormPage(IkobaEditFormPage):
732    """ Page to manage the customer documents
733
734    This manage form page is for both customers and customers officers.
735    """
736    grok.context(ICustomerDocumentsContainer)
737    grok.name('index')
738    grok.require('waeup.viewCustomer')
739    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
740    grok.template('documentsmanagepage')
741    pnav = 4
742
743    @property
744    def manage_documents_allowed(self):
745        return checkPermission('waeup.editCustomerDocuments', self.context)
746
747    def unremovable(self, document):
748        usertype = getattr(self.request.principal, 'user_type', None)
749        if not usertype:
750            return False
751        if not self.manage_documents_allowed:
752            return True
753        return (self.request.principal.user_type == 'customer' and \
754            document.state in (VERIFIED, REJECTED, OUTDATED))
755
756    @property
757    def label(self):
758        return _('${a}: Documents',
759            mapping = {'a':self.context.__parent__.display_fullname})
760
761    @jsaction(_('Remove selected documents'))
762    def delDocument(self, **data):
763        form = self.request.form
764        if 'val_id' in form:
765            child_id = form['val_id']
766        else:
767            self.flash(_('No document selected.'), type="warning")
768            self.redirect(self.url(self.context))
769            return
770        if not isinstance(child_id, list):
771            child_id = [child_id]
772        deleted = []
773        for id in child_id:
774            # Customers are not allowed to remove used documents
775            document = self.context.get(id, None)
776            if document is not None and not self.unremovable(document):
777                del self.context[id]
778                deleted.append(id)
779        if len(deleted):
780            self.flash(_('Successfully removed: ${a}',
781                mapping = {'a': ', '.join(deleted)}))
782            self.context.writeLogMessage(
783                self,'removed: %s' % ', '.join(deleted))
784        self.redirect(self.url(self.context))
785        return
786
787
788class DocumentAddFormPage(IkobaAddFormPage):
789    """ Page to add an document
790    """
791    grok.context(ICustomerDocumentsContainer)
792    grok.name('adddoc')
793    grok.template('documentaddform')
794    grok.require('waeup.editCustomerDocuments')
795    form_fields = grok.AutoFields(ICustomerDocument)
796    label = _('Add document')
797    pnav = 4
798
799    @property
800    def selectable_doctypes(self):
801        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
802        return sorted(doctypes.items())
803
804    @action(_('Create document'), style='primary')
805    def createDocument(self, **data):
806        form = self.request.form
807        customer = self.context.__parent__
808        doctype = form.get('doctype', None)
809        # Here we can create various instances of CustomerDocument derived
810        # classes depending on the doctype parameter given in form.
811        document = createObject('waeup.%s' % doctype)
812        self.context.addDocument(document)
813        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
814        self.flash(_('${a} created.',
815            mapping = {'a': doctype}))
816        self.context.writeLogMessage(
817            self,'added: %s %s' % (doctype, document.document_id))
818        self.redirect(self.url(self.context))
819        return
820
821    @action(_('Cancel'), validator=NullValidator)
822    def cancel(self, **data):
823        self.redirect(self.url(self.context))
824
825
826class DocumentDisplayFormPage(IkobaDisplayFormPage):
827    """ Page to view a document
828    """
829    grok.context(ICustomerDocument)
830    grok.name('index')
831    grok.require('waeup.viewCustomer')
832    grok.template('documentpage')
833    form_fields = grok.AutoFields(ICustomerDocument)
834    form_fields[
835        'last_transition_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
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 ExportPDFDocumentsOverviewPage(UtilityView, grok.View):
937    """Deliver an overview slip.
938    """
939    grok.context(ICustomer)
940    grok.name('overview_slip.pdf')
941    grok.require('waeup.viewCustomer')
942    prefix = 'form'
943
944    omit_fields = ('suspended', 'sex', 'suspended_comment')
945
946    form_fields = None
947
948    @property
949    def label(self):
950        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
951        return translate(_('Documents of'),
952            'waeup.ikoba', target_language=portal_language) \
953            + ' %s' % self.context.display_fullname
954
955    @property
956    def tabletitle(self):
957        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
958        tabletitle = []
959        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
960            target_language=portal_language))
961        return tabletitle
962
963    def render(self):
964        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
965        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
966        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
967        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
968        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
969        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
970        tableheader = []
971        tabledata = []
972        contenttitle = []
973        for i in range(1,3):
974            tabledata.append(sorted(
975                [value for value in self.context['documents'].values()]))
976            tableheader.append([(Id, 'document_id', 2),
977                             (Title, 'title', 6),
978                             (Type, 'translated_classname', 6),
979                             (State, 'translated_state', 2),
980                             (LT, 'formatted_transition_date', 3),
981                             ])
982        customerview = CustomerBasePDFFormPage(self.context,
983            self.request, self.omit_fields)
984        customers_utils = getUtility(ICustomersUtils)
985        return customers_utils.renderPDF(
986            self, 'overview_slip.pdf',
987            self.context, customerview,
988            tableheader=tableheader,
989            tabledata=tabledata,
990            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.