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

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

Add pdf download button to downloads page.

  • Property svn:keywords set to Id
File size: 34.3 KB
RevLine 
[12015]1## $Id: browser.py 12059 2014-11-25 17:39:53Z henrik $
[11958]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
[11967]21import sys
[11958]22import grok
[11967]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
[11958]34from waeup.ikoba.interfaces import MessageFactory as _
[11971]35from waeup.ikoba.interfaces import (
36    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils,
37    IPasswordValidator, IUserAccount)
[11958]38from waeup.ikoba.browser.layout import (
39    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
[11967]40    IkobaForm, NullValidator, jsaction, action, UtilityView)
[12053]41from waeup.ikoba.widgets.datewidget import (
42    FriendlyDateWidget, FriendlyDateDisplayWidget,
43    FriendlyDatetimeDisplayWidget)
[11967]44from waeup.ikoba.browser.pages import ContactAdminForm
[11958]45from waeup.ikoba.browser.breadcrumbs import Breadcrumb
[11971]46from waeup.ikoba.browser.interfaces import ICaptchaManager
[11977]47from waeup.ikoba.mandates.mandate import PasswordMandate
[12018]48from waeup.ikoba.documents.workflow import VERIFIED, REJECTED, OUTDATED
[11958]49from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now
50from waeup.ikoba.customers.interfaces import (
[12015]51    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
[12039]52    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate
[11958]53    )
54from waeup.ikoba.customers.catalog import search
55
[11967]56grok.context(IIkobaObject)
57
[12034]58WARNING = _('You can not edit your document after final submission.'
59            ' You really want to submit?')
[11985]60
[12034]61
[11986]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
[11958]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
[11967]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
[11985]105
[11958]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
[11985]146
[11958]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}',
[11985]202                mapping={'a': ','.join(deleted)}))
[11967]203        return
204
[11985]205
[11967]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
[11985]226
[11967]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}',
[11985]238            mapping={'a': self.context.display_fullname})
[11967]239
240    @action('Set password now', style='primary')
241    def setPassword(self, *args, **data):
[11979]242        ikoba_utils = getUtility(IIkobaUtils)
243        password = ikoba_utils.genPassword()
[11967]244        self.context.setTempPassword(self.request.principal.id, password)
245        self.context.writeLogMessage(
246            self, 'temp_password generated: %s' % password)
[11985]247        args = {'password': password}
[11967]248        self.redirect(self.url(self.context) +
249            '/loginasstep2?%s' % urlencode(args))
250        return
251
[11985]252
[11967]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}',
[11985]265            mapping={'a': self.context.customer_id})
[11967]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
[11985]274
[11967]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)',
[11985]290                mapping={'a': self.context.display_fullname})
[11967]291        return  _('${a}: Base Data',
[11985]292            mapping={'a': self.context.display_fullname})
[11967]293
294    @property
295    def hasPassword(self):
296        if self.context.password:
297            return _('set')
298        return _('unset')
299
[11985]300
[11967]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}',
[11985]316            mapping={'a': self.context.display_fullname})
[11967]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()
[11979]326        ikoba_utils = getUtility(IIkobaUtils)
327        success = ikoba_utils.sendContactForm(
[11985]328                self.request.principal.title, email,
329                self.context.display_fullname, self.context.email,
[11967]330                self.request.principal.id,usertype,
331                self.config.name,
[11985]332                data['body'], data['subject'])
[11967]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
[11985]339
[11967]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:
[11985]366                self.flash(' '.join(errors), type="danger")
[11967]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
[11985]384
[11967]385class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
[12028]386    """ View to trigger customer workflow transitions
[11967]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
[11985]416
[11967]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
[11985]436
[11967]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
[11985]456
[11967]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):
[11985]468        return _('${a}: History', mapping={'a':self.context.display_fullname})
[11967]469
[11985]470
[11967]471class CustomerRequestPasswordPage(IkobaAddFormPage):
[12039]472    """Captcha'd password request page for customers.
[11967]473    """
474    grok.name('requestpw')
475    grok.require('waeup.Anonymous')
476    grok.template('requestpw')
[12039]477    form_fields = grok.AutoFields(ICustomerRequestPW)
[11967]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):
[12039]488        # Forward only email address to landing page in base package.
[11967]489        self.redirect(self.url(self.context, 'requestpw_complete',
[11985]490            data=dict(email=email)))
[11967]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
[11979]533        ikoba_utils = getUtility(IIkobaUtils)
534        password = ikoba_utils.genPassword()
[11967]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')
[11979]545        if ikoba_utils.sendCredentials(IUserAccount(customer),
[11967]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)
[11977]552        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
[11967]553        self.context.logger.info(
554            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
555        return
556
[11985]557
[12039]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
[11967]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')
[12039]624    label = _('Your request was successful.')
[11967]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
[11971]630        return
631
[11985]632
[11971]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):
[12018]644        CUSTMANAGE_STATES = getUtility(ICustomersUtils).CUSTMANAGE_STATES
645        if self.context.customer.state not in CUSTMANAGE_STATES:
[11971]646            emit_lock_message(self)
647            return
648        super(CustomerFilesUploadPage, self).update()
649        return
650
[12015]651# Pages for customers
[11971]652
[11985]653
[11971]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
[11985]670
[11971]671class CustomerChangePasswordPage(IkobaEditFormPage):
672    """ View to edit customer passords
673    """
674    grok.context(ICustomer)
[11977]675    grok.name('changepassword')
[11971]676    grok.require('waeup.handleCustomer')
[11977]677    grok.template('changepassword')
[11971]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:
[11985]694                self.flash(' '.join(errors), type="warning")
[11971]695        return
[12015]696
[12051]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
[12015]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):
[12053]801        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
[12015]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.
[12053]811        document = createObject('waeup.%s' % doctype)
[12015]812        self.context.addDocument(document)
[12053]813        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
[12015]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')
[12016]832    grok.template('documentpage')
[12055]833    form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date')
[12015]834    pnav = 4
835
[12018]836    #@property
837    #def label(self):
838    #    return _('${a}: Document ${b}', mapping = {
839    #        'a':self.context.customer.display_fullname,
840    #        'b':self.context.document_id})
841
[12015]842    @property
843    def label(self):
[12018]844        return _('${a}', mapping = {'a':self.context.title})
[12015]845
846
847class DocumentManageFormPage(IkobaEditFormPage):
848    """ Page to edit a document
849    """
850    grok.context(ICustomerDocument)
851    grok.name('manage')
[12016]852    grok.require('waeup.manageCustomer')
[12018]853    grok.template('documenteditpage')
[12053]854    form_fields = grok.AutoFields(ICustomerDocument).omit('last_transition_date')
[12015]855    pnav = 4
[12035]856    deletion_warning = _('Are you sure?')
[12015]857
[12018]858    #@property
859    #def label(self):
860    #    return _('${a}: Document ${b}', mapping = {
861    #        'a':self.context.customer.display_fullname,
862    #        'b':self.context.document_id})
863
[12015]864    @property
865    def label(self):
[12018]866        return _('${a}', mapping = {'a':self.context.title})
[12016]867
868    @action(_('Save'), style='primary')
869    def save(self, **data):
870        msave(self, **data)
[12018]871        return
872
[12028]873
[12018]874class DocumentEditFormPage(DocumentManageFormPage):
875    """ Page to edit a document
876    """
877    grok.name('edit')
878    grok.require('waeup.handleCustomer')
879
880    def update(self):
881        if not self.context.is_editable:
882            emit_lock_message(self)
883            return
[12028]884        return super(DocumentEditFormPage, self).update()
885
[12034]886    @action(_('Save'), style='primary')
887    def save(self, **data):
888        msave(self, **data)
889        return
[12028]890
[12034]891    @action(_('Final Submit'), warning=WARNING)
892    def finalsubmit(self, **data):
893        msave(self, **data)
894        IWorkflowInfo(self.context).fireTransition('submit')
895        self.flash(_('Form has been submitted.'))
896        self.redirect(self.url(self.context))
897        return
898
899
[12028]900class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
901    """ View to trigger customer document transitions
902    """
903    grok.context(ICustomerDocument)
904    grok.name('trigtrans')
905    grok.require('waeup.triggerTransition')
906    grok.template('trigtrans')
907    label = _('Trigger document transition')
908    pnav = 4
909
910    def update(self):
911        return super(IkobaEditFormPage, self).update()
912
913    def getTransitions(self):
914        """Return a list of dicts of allowed transition ids and titles.
915
916        Each list entry provides keys ``name`` and ``title`` for
917        internal name and (human readable) title of a single
918        transition.
919        """
920        wf_info = IWorkflowInfo(self.context)
921        allowed_transitions = [t for t in wf_info.getManualTransitions()]
922        return [dict(name='', title=_('No transition'))] +[
923            dict(name=x, title=y) for x, y in allowed_transitions]
924
925    @action(_('Save'), style='primary')
926    def save(self, **data):
927        form = self.request.form
928        if 'transition' in form and form['transition']:
929            transition_id = form['transition']
930            wf_info = IWorkflowInfo(self.context)
931            wf_info.fireTransition(transition_id)
932        return
[12051]933
934class ExportPDFDocumentsOverviewPage(UtilityView, grok.View):
935    """Deliver an overview slip.
936    """
[12059]937    grok.context(ICustomerDocumentsContainer)
[12051]938    grok.name('overview_slip.pdf')
939    grok.require('waeup.viewCustomer')
940    prefix = 'form'
941
[12055]942    omit_fields = ('suspended', 'sex',
943                   'suspended_comment',)
[12051]944
945    form_fields = None
946
947    @property
948    def label(self):
949        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
950        return translate(_('Documents of'),
951            'waeup.ikoba', target_language=portal_language) \
[12059]952            + ' %s' % self.context.customer.display_fullname
[12051]953
[12052]954    @property
955    def tabletitle(self):
956        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
957        tabletitle = []
958        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
959            target_language=portal_language))
960        return tabletitle
961
[12051]962    def render(self):
[12052]963        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
[12053]964        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
[12052]965        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
[12053]966        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
[12052]967        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
[12053]968        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
969        tableheader = []
[12052]970        tabledata = []
971        contenttitle = []
972        for i in range(1,3):
973            tabledata.append(sorted(
[12059]974                [value for value in self.context.values()]))
[12053]975            tableheader.append([(Id, 'document_id', 2),
976                             (Title, 'title', 6),
[12056]977                             (Type, 'translated_class_name', 6),
[12053]978                             (State, 'translated_state', 2),
979                             (LT, 'formatted_transition_date', 3),
[12052]980                             ])
[12059]981        customerview = CustomerBasePDFFormPage(self.context.customer,
[12051]982            self.request, self.omit_fields)
983        customers_utils = getUtility(ICustomersUtils)
984        return customers_utils.renderPDF(
985            self, 'overview_slip.pdf',
[12059]986            self.context.customer, customerview,
[12052]987            tableheader=tableheader,
988            tabledata=tabledata,
[12051]989            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.