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

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

Add pdf utility methods first pdf slip components.

  • Property svn:keywords set to Id
File size: 32.7 KB
Line 
1## $Id: browser.py 12051 2014-11-24 11:27:53Z henrik $
2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for customers and related components.
19"""
20
21import sys
22import grok
23import pytz
24from urllib import urlencode
25from datetime import datetime
26from zope.event import notify
27from zope.i18n import translate
28from zope.catalog.interfaces import ICatalog
29from zope.component import queryUtility, getUtility, createObject
30from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing
31from zope.formlib.textwidgets import BytesDisplayWidget
32from zope.security import checkPermission
33from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
34from waeup.ikoba.interfaces import MessageFactory as _
35from waeup.ikoba.interfaces import (
36    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils,
37    IPasswordValidator, IUserAccount)
38from waeup.ikoba.browser.layout import (
39    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
40    IkobaForm, NullValidator, jsaction, action, UtilityView)
41from waeup.ikoba.browser.pages import ContactAdminForm
42from waeup.ikoba.browser.breadcrumbs import Breadcrumb
43from waeup.ikoba.browser.interfaces import ICaptchaManager
44from waeup.ikoba.mandates.mandate import PasswordMandate
45from waeup.ikoba.documents.workflow import VERIFIED, REJECTED, OUTDATED
46from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now
47from waeup.ikoba.customers.interfaces import (
48    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
49    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate
50    )
51from waeup.ikoba.customers.catalog import search
52
53grok.context(IIkobaObject)
54
55WARNING = _('You can not edit your document after final submission.'
56            ' You really want to submit?')
57
58
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
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
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
102
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
143
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}',
199                mapping={'a': ','.join(deleted)}))
200        return
201
202
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
223
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}',
235            mapping={'a': self.context.display_fullname})
236
237    @action('Set password now', style='primary')
238    def setPassword(self, *args, **data):
239        ikoba_utils = getUtility(IIkobaUtils)
240        password = ikoba_utils.genPassword()
241        self.context.setTempPassword(self.request.principal.id, password)
242        self.context.writeLogMessage(
243            self, 'temp_password generated: %s' % password)
244        args = {'password': password}
245        self.redirect(self.url(self.context) +
246            '/loginasstep2?%s' % urlencode(args))
247        return
248
249
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}',
262            mapping={'a': self.context.customer_id})
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
271
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)',
287                mapping={'a': self.context.display_fullname})
288        return  _('${a}: Base Data',
289            mapping={'a': self.context.display_fullname})
290
291    @property
292    def hasPassword(self):
293        if self.context.password:
294            return _('set')
295        return _('unset')
296
297
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}',
313            mapping={'a': self.context.display_fullname})
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()
323        ikoba_utils = getUtility(IIkobaUtils)
324        success = ikoba_utils.sendContactForm(
325                self.request.principal.title, email,
326                self.context.display_fullname, self.context.email,
327                self.request.principal.id,usertype,
328                self.config.name,
329                data['body'], data['subject'])
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
336
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:
363                self.flash(' '.join(errors), type="danger")
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
381
382class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
383    """ View to trigger customer workflow transitions
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
413
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
433
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
453
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):
465        return _('${a}: History', mapping={'a':self.context.display_fullname})
466
467
468class CustomerRequestPasswordPage(IkobaAddFormPage):
469    """Captcha'd password request page for customers.
470    """
471    grok.name('requestpw')
472    grok.require('waeup.Anonymous')
473    grok.template('requestpw')
474    form_fields = grok.AutoFields(ICustomerRequestPW)
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):
485        # Forward only email address to landing page in base package.
486        self.redirect(self.url(self.context, 'requestpw_complete',
487            data=dict(email=email)))
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
530        ikoba_utils = getUtility(IIkobaUtils)
531        password = ikoba_utils.genPassword()
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')
542        if ikoba_utils.sendCredentials(IUserAccount(customer),
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)
549        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
550        self.context.logger.info(
551            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
552        return
553
554
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
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')
621    label = _('Your request was successful.')
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
627        return
628
629
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):
641        CUSTMANAGE_STATES = getUtility(ICustomersUtils).CUSTMANAGE_STATES
642        if self.context.customer.state not in CUSTMANAGE_STATES:
643            emit_lock_message(self)
644            return
645        super(CustomerFilesUploadPage, self).update()
646        return
647
648# Pages for customers
649
650
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
667
668class CustomerChangePasswordPage(IkobaEditFormPage):
669    """ View to edit customer passords
670    """
671    grok.context(ICustomer)
672    grok.name('changepassword')
673    grok.require('waeup.handleCustomer')
674    grok.template('changepassword')
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:
691                self.flash(' '.join(errors), type="warning")
692        return
693
694class CustomerBasePDFFormPage(IkobaDisplayFormPage):
695    """ Page to display customer base data in pdf files.
696    """
697
698    def __init__(self, context, request, omit_fields=()):
699        self.omit_fields = omit_fields
700        super(CustomerBasePDFFormPage, self).__init__(context, request)
701
702    @property
703    def form_fields(self):
704        form_fields = grok.AutoFields(ICustomer)
705        for field in self.omit_fields:
706            form_fields = form_fields.omit(field)
707        return form_fields
708
709# Pages for customer documents
710
711class DocumentsBreadcrumb(Breadcrumb):
712    """A breadcrumb for the documents container.
713    """
714    grok.context(ICustomerDocumentsContainer)
715    title = _('Documents')
716
717
718class DocumentBreadcrumb(Breadcrumb):
719    """A breadcrumb for the customer container.
720    """
721    grok.context(ICustomerDocument)
722
723    @property
724    def title(self):
725        return self.context.document_id
726
727
728class DocumentsManageFormPage(IkobaEditFormPage):
729    """ Page to manage the customer documents
730
731    This manage form page is for both customers and customers officers.
732    """
733    grok.context(ICustomerDocumentsContainer)
734    grok.name('index')
735    grok.require('waeup.viewCustomer')
736    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
737    grok.template('documentsmanagepage')
738    pnav = 4
739
740    @property
741    def manage_documents_allowed(self):
742        return checkPermission('waeup.editCustomerDocuments', self.context)
743
744    def unremovable(self, document):
745        usertype = getattr(self.request.principal, 'user_type', None)
746        if not usertype:
747            return False
748        if not self.manage_documents_allowed:
749            return True
750        return (self.request.principal.user_type == 'customer' and \
751            document.state in (VERIFIED, REJECTED, OUTDATED))
752
753    @property
754    def label(self):
755        return _('${a}: Documents',
756            mapping = {'a':self.context.__parent__.display_fullname})
757
758    @jsaction(_('Remove selected documents'))
759    def delDocument(self, **data):
760        form = self.request.form
761        if 'val_id' in form:
762            child_id = form['val_id']
763        else:
764            self.flash(_('No document selected.'), type="warning")
765            self.redirect(self.url(self.context))
766            return
767        if not isinstance(child_id, list):
768            child_id = [child_id]
769        deleted = []
770        for id in child_id:
771            # Customers are not allowed to remove used documents
772            document = self.context.get(id, None)
773            if document is not None and not self.unremovable(document):
774                del self.context[id]
775                deleted.append(id)
776        if len(deleted):
777            self.flash(_('Successfully removed: ${a}',
778                mapping = {'a': ', '.join(deleted)}))
779            self.context.writeLogMessage(
780                self,'removed: %s' % ', '.join(deleted))
781        self.redirect(self.url(self.context))
782        return
783
784
785class DocumentAddFormPage(IkobaAddFormPage):
786    """ Page to add an document
787    """
788    grok.context(ICustomerDocumentsContainer)
789    grok.name('adddoc')
790    grok.template('documentaddform')
791    grok.require('waeup.editCustomerDocuments')
792    form_fields = grok.AutoFields(ICustomerDocument)
793    label = _('Add document')
794    pnav = 4
795
796    @property
797    def selectable_doctypes(self):
798        doctypes = getUtility(IIkobaUtils).DOCUMENT_TYPES
799        return sorted(doctypes.items())
800
801    @action(_('Create document'), style='primary')
802    def createDocument(self, **data):
803        form = self.request.form
804        customer = self.context.__parent__
805        doctype = form.get('doctype', None)
806        # Here we can create various instances of CustomerDocument derived
807        # classes depending on the doctype parameter given in form.
808        # So far we only have one CustomerDocument class and no derived
809        # classes.
810        document = createObject(u'waeup.CustomerDocument')
811        self.context.addDocument(document)
812        doctype = getUtility(IIkobaUtils).DOCUMENT_TYPES[doctype]
813        self.flash(_('${a} created.',
814            mapping = {'a': doctype}))
815        self.context.writeLogMessage(
816            self,'added: %s %s' % (doctype, document.document_id))
817        self.redirect(self.url(self.context))
818        return
819
820    @action(_('Cancel'), validator=NullValidator)
821    def cancel(self, **data):
822        self.redirect(self.url(self.context))
823
824
825class DocumentDisplayFormPage(IkobaDisplayFormPage):
826    """ Page to view a document
827    """
828    grok.context(ICustomerDocument)
829    grok.name('index')
830    grok.require('waeup.viewCustomer')
831    grok.template('documentpage')
832    form_fields = grok.AutoFields(ICustomerDocument)
833    pnav = 4
834
835    #@property
836    #def label(self):
837    #    return _('${a}: Document ${b}', mapping = {
838    #        'a':self.context.customer.display_fullname,
839    #        'b':self.context.document_id})
840
841    @property
842    def label(self):
843        return _('${a}', mapping = {'a':self.context.title})
844
845
846class DocumentManageFormPage(IkobaEditFormPage):
847    """ Page to edit a document
848    """
849    grok.context(ICustomerDocument)
850    grok.name('manage')
851    grok.require('waeup.manageCustomer')
852    grok.template('documenteditpage')
853    form_fields = grok.AutoFields(ICustomerDocument)
854    pnav = 4
855    deletion_warning = _('Are you sure?')
856
857    #@property
858    #def label(self):
859    #    return _('${a}: Document ${b}', mapping = {
860    #        'a':self.context.customer.display_fullname,
861    #        'b':self.context.document_id})
862
863    @property
864    def label(self):
865        return _('${a}', mapping = {'a':self.context.title})
866
867    @action(_('Save'), style='primary')
868    def save(self, **data):
869        msave(self, **data)
870        return
871
872
873class DocumentEditFormPage(DocumentManageFormPage):
874    """ Page to edit a document
875    """
876    grok.name('edit')
877    grok.require('waeup.handleCustomer')
878
879    def update(self):
880        if not self.context.is_editable:
881            emit_lock_message(self)
882            return
883        return super(DocumentEditFormPage, self).update()
884
885    @action(_('Save'), style='primary')
886    def save(self, **data):
887        msave(self, **data)
888        return
889
890    @action(_('Final Submit'), warning=WARNING)
891    def finalsubmit(self, **data):
892        msave(self, **data)
893        IWorkflowInfo(self.context).fireTransition('submit')
894        self.flash(_('Form has been submitted.'))
895        self.redirect(self.url(self.context))
896        return
897
898
899class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
900    """ View to trigger customer document transitions
901    """
902    grok.context(ICustomerDocument)
903    grok.name('trigtrans')
904    grok.require('waeup.triggerTransition')
905    grok.template('trigtrans')
906    label = _('Trigger document transition')
907    pnav = 4
908
909    def update(self):
910        return super(IkobaEditFormPage, self).update()
911
912    def getTransitions(self):
913        """Return a list of dicts of allowed transition ids and titles.
914
915        Each list entry provides keys ``name`` and ``title`` for
916        internal name and (human readable) title of a single
917        transition.
918        """
919        wf_info = IWorkflowInfo(self.context)
920        allowed_transitions = [t for t in wf_info.getManualTransitions()]
921        return [dict(name='', title=_('No transition'))] +[
922            dict(name=x, title=y) for x, y in allowed_transitions]
923
924    @action(_('Save'), style='primary')
925    def save(self, **data):
926        form = self.request.form
927        if 'transition' in form and form['transition']:
928            transition_id = form['transition']
929            wf_info = IWorkflowInfo(self.context)
930            wf_info.fireTransition(transition_id)
931        return
932
933class ExportPDFDocumentsOverviewPage(UtilityView, grok.View):
934    """Deliver an overview slip.
935    """
936    grok.context(ICustomer)
937    grok.name('overview_slip.pdf')
938    grok.require('waeup.viewCustomer')
939    prefix = 'form'
940
941    omit_fields = ('suspended', 'sex', 'suspended_comment')
942
943    form_fields = None
944
945    @property
946    def label(self):
947        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
948        return translate(_('Documents of'),
949            'waeup.ikoba', target_language=portal_language) \
950            + ' %s' % self.context.display_fullname
951
952    def render(self):
953        customerview = CustomerBasePDFFormPage(self.context,
954            self.request, self.omit_fields)
955        customers_utils = getUtility(ICustomersUtils)
956        return customers_utils.renderPDF(
957            self, 'overview_slip.pdf',
958            self.context, customerview,
959            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.