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

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

Add PDFDocumentSlipPage and related components.

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