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

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

Implement customer self-registration.

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