Ignore:
Timestamp:
16 Nov 2014, 11:53:02 (10 years ago)
Author:
Henrik Bettermann
Message:

Add browser components for customers. Tests will follow.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.ikoba/trunk/src/waeup/ikoba/customers/browser.py

    r11958 r11967  
    1919"""
    2020
     21import sys
    2122import 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
    2234from waeup.ikoba.interfaces import MessageFactory as _
     35from waeup.ikoba.interfaces import IContactForm, IObjectHistory, IIkobaObject
    2336from waeup.ikoba.browser.layout import (
    2437    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
    25     IkobaForm, NullValidator)
     38    IkobaForm, NullValidator, jsaction, action, UtilityView)
     39from waeup.ikoba.browser.pages import ContactAdminForm
    2640from waeup.ikoba.browser.breadcrumbs import Breadcrumb
    2741from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now
    2842from waeup.ikoba.customers.interfaces import (
    29     ICustomersContainer
     43    ICustomer, ICustomersContainer, ICustomerRequestPW
    3044    )
    3145from waeup.ikoba.customers.catalog import search
     46
     47grok.context(IIkobaObject)
    3248
    3349class CustomersBreadcrumb(Breadcrumb):
     
    4359            return None
    4460        return self.viewname
     61
     62
     63class CustomerBreadcrumb(Breadcrumb):
     64    """A breadcrumb for the customer container.
     65    """
     66    grok.context(ICustomer)
     67
     68    def title(self):
     69        return self.context.display_fullname
    4570
    4671class CustomersContainerPage(IkobaPage):
     
    141166                mapping = {'a':', '.join(deleted)}))
    142167        return
     168
     169class CustomerAddFormPage(IkobaAddFormPage):
     170    """Add-form to add a customer.
     171    """
     172    grok.context(ICustomersContainer)
     173    grok.require('waeup.manageCustomer')
     174    grok.name('addcustomer')
     175    form_fields = grok.AutoFields(ICustomer).select(
     176        'firstname', 'middlename', 'lastname', 'reg_number')
     177    label = _('Add customer')
     178    pnav = 4
     179
     180    @action(_('Create customer record'), style='primary')
     181    def addCustomer(self, **data):
     182        customer = createObject(u'waeup.Customer')
     183        self.applyData(customer, **data)
     184        self.context.addCustomer(customer)
     185        self.flash(_('Customer record created.'))
     186        self.redirect(self.url(self.context[customer.customer_id], 'index'))
     187        return
     188
     189class LoginAsCustomerStep1(IkobaEditFormPage):
     190    """ View to temporarily set a customer password.
     191    """
     192    grok.context(ICustomer)
     193    grok.name('loginasstep1')
     194    grok.require('waeup.loginAsCustomer')
     195    grok.template('loginasstep1')
     196    pnav = 4
     197
     198    def label(self):
     199        return _(u'Set temporary password for ${a}',
     200            mapping = {'a':self.context.display_fullname})
     201
     202    @action('Set password now', style='primary')
     203    def setPassword(self, *args, **data):
     204        kofa_utils = getUtility(IIkobaUtils)
     205        password = kofa_utils.genPassword()
     206        self.context.setTempPassword(self.request.principal.id, password)
     207        self.context.writeLogMessage(
     208            self, 'temp_password generated: %s' % password)
     209        args = {'password':password}
     210        self.redirect(self.url(self.context) +
     211            '/loginasstep2?%s' % urlencode(args))
     212        return
     213
     214class LoginAsCustomerStep2(IkobaPage):
     215    """ View to temporarily login as customer with a temporary password.
     216    """
     217    grok.context(ICustomer)
     218    grok.name('loginasstep2')
     219    grok.require('waeup.Public')
     220    grok.template('loginasstep2')
     221    login_button = _('Login now')
     222    pnav = 4
     223
     224    def label(self):
     225        return _(u'Login as ${a}',
     226            mapping = {'a':self.context.customer_id})
     227
     228    def update(self, SUBMIT=None, password=None):
     229        self.password = password
     230        if SUBMIT is not None:
     231            self.flash(_('You successfully logged in as customer.'))
     232            self.redirect(self.url(self.context))
     233        return
     234
     235class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
     236    """ Page to display customer base data
     237    """
     238    grok.context(ICustomer)
     239    grok.name('index')
     240    grok.require('waeup.viewCustomer')
     241    grok.template('basepage')
     242    form_fields = grok.AutoFields(ICustomer).omit(
     243        'password', 'suspended', 'suspended_comment')
     244    pnav = 4
     245
     246    @property
     247    def label(self):
     248        if self.context.suspended:
     249            return _('${a}: Base Data (account deactivated)',
     250                mapping = {'a':self.context.display_fullname})
     251        return  _('${a}: Base Data',
     252            mapping = {'a':self.context.display_fullname})
     253
     254    @property
     255    def hasPassword(self):
     256        if self.context.password:
     257            return _('set')
     258        return _('unset')
     259
     260class ContactCustomerForm(ContactAdminForm):
     261    grok.context(ICustomer)
     262    grok.name('contactcustomer')
     263    grok.require('waeup.viewCustomer')
     264    pnav = 4
     265    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
     266
     267    def update(self, subject=u'', body=u''):
     268        super(ContactCustomerForm, self).update()
     269        self.form_fields.get('subject').field.default = subject
     270        self.form_fields.get('body').field.default = body
     271        return
     272
     273    def label(self):
     274        return _(u'Send message to ${a}',
     275            mapping = {'a':self.context.display_fullname})
     276
     277    @action('Send message now', style='primary')
     278    def send(self, *args, **data):
     279        try:
     280            email = self.request.principal.email
     281        except AttributeError:
     282            email = self.config.email_admin
     283        usertype = getattr(self.request.principal,
     284                           'user_type', 'system').title()
     285        kofa_utils = getUtility(IIkobaUtils)
     286        success = kofa_utils.sendContactForm(
     287                self.request.principal.title,email,
     288                self.context.display_fullname,self.context.email,
     289                self.request.principal.id,usertype,
     290                self.config.name,
     291                data['body'],data['subject'])
     292        if success:
     293            self.flash(_('Your message has been sent.'))
     294        else:
     295            self.flash(_('An smtp server error occurred.'), type="danger")
     296        return
     297
     298class CustomerBaseManageFormPage(IkobaEditFormPage):
     299    """ View to manage customer base data
     300    """
     301    grok.context(ICustomer)
     302    grok.name('manage_base')
     303    grok.require('waeup.manageCustomer')
     304    form_fields = grok.AutoFields(ICustomer).omit(
     305        'customer_id', 'adm_code', 'suspended')
     306    grok.template('basemanagepage')
     307    label = _('Manage base data')
     308    pnav = 4
     309
     310    def update(self):
     311        super(CustomerBaseManageFormPage, self).update()
     312        self.wf_info = IWorkflowInfo(self.context)
     313        return
     314
     315    @action(_('Save'), style='primary')
     316    def save(self, **data):
     317        form = self.request.form
     318        password = form.get('password', None)
     319        password_ctl = form.get('control_password', None)
     320        if password:
     321            validator = getUtility(IPasswordValidator)
     322            errors = validator.validate_password(password, password_ctl)
     323            if errors:
     324                self.flash( ' '.join(errors), type="danger")
     325                return
     326        changed_fields = self.applyData(self.context, **data)
     327        # Turn list of lists into single list
     328        if changed_fields:
     329            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
     330        else:
     331            changed_fields = []
     332        if password:
     333            # Now we know that the form has no errors and can set password
     334            IUserAccount(self.context).setPassword(password)
     335            changed_fields.append('password')
     336        fields_string = ' + '.join(changed_fields)
     337        self.flash(_('Form has been saved.'))
     338        if fields_string:
     339            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
     340        return
     341
     342class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
     343    """ View to manage customer base data
     344    """
     345    grok.context(ICustomer)
     346    grok.name('trigtrans')
     347    grok.require('waeup.triggerTransition')
     348    grok.template('trigtrans')
     349    label = _('Trigger registration transition')
     350    pnav = 4
     351
     352    def getTransitions(self):
     353        """Return a list of dicts of allowed transition ids and titles.
     354
     355        Each list entry provides keys ``name`` and ``title`` for
     356        internal name and (human readable) title of a single
     357        transition.
     358        """
     359        wf_info = IWorkflowInfo(self.context)
     360        allowed_transitions = [t for t in wf_info.getManualTransitions()]
     361        return [dict(name='', title=_('No transition'))] +[
     362            dict(name=x, title=y) for x, y in allowed_transitions]
     363
     364    @action(_('Save'), style='primary')
     365    def save(self, **data):
     366        form = self.request.form
     367        if 'transition' in form and form['transition']:
     368            transition_id = form['transition']
     369            wf_info = IWorkflowInfo(self.context)
     370            wf_info.fireTransition(transition_id)
     371        return
     372
     373class CustomerActivatePage(UtilityView, grok.View):
     374    """ Activate customer account
     375    """
     376    grok.context(ICustomer)
     377    grok.name('activate')
     378    grok.require('waeup.manageCustomer')
     379
     380    def update(self):
     381        self.context.suspended = False
     382        self.context.writeLogMessage(self, 'account activated')
     383        history = IObjectHistory(self.context)
     384        history.addMessage('Customer account activated')
     385        self.flash(_('Customer account has been activated.'))
     386        self.redirect(self.url(self.context))
     387        return
     388
     389    def render(self):
     390        return
     391
     392class CustomerDeactivatePage(UtilityView, grok.View):
     393    """ Deactivate customer account
     394    """
     395    grok.context(ICustomer)
     396    grok.name('deactivate')
     397    grok.require('waeup.manageCustomer')
     398
     399    def update(self):
     400        self.context.suspended = True
     401        self.context.writeLogMessage(self, 'account deactivated')
     402        history = IObjectHistory(self.context)
     403        history.addMessage('Customer account deactivated')
     404        self.flash(_('Customer account has been deactivated.'))
     405        self.redirect(self.url(self.context))
     406        return
     407
     408    def render(self):
     409        return
     410
     411class CustomerHistoryPage(IkobaPage):
     412    """ Page to display customer history
     413    """
     414    grok.context(ICustomer)
     415    grok.name('history')
     416    grok.require('waeup.viewCustomer')
     417    grok.template('customerhistory')
     418    pnav = 4
     419
     420    @property
     421    def label(self):
     422        return _('${a}: History', mapping = {'a':self.context.display_fullname})
     423
     424class CustomerRequestPasswordPage(IkobaAddFormPage):
     425    """Captcha'd registration page for applicants.
     426    """
     427    grok.name('requestpw')
     428    grok.require('waeup.Anonymous')
     429    grok.template('requestpw')
     430    form_fields = grok.AutoFields(ICustomerRequestPW).select(
     431        'firstname','number','email')
     432    label = _('Request password for first-time login')
     433
     434    def update(self):
     435        # Handle captcha
     436        self.captcha = getUtility(ICaptchaManager).getCaptcha()
     437        self.captcha_result = self.captcha.verify(self.request)
     438        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
     439        return
     440
     441    def _redirect(self, email, password, customer_id):
     442        # Forward only email to landing page in base package.
     443        self.redirect(self.url(self.context, 'requestpw_complete',
     444            data = dict(email=email)))
     445        return
     446
     447    def _pw_used(self):
     448        # XXX: False if password has not been used. We need an extra
     449        #      attribute which remembers if customer logged in.
     450        return True
     451
     452    @action(_('Send login credentials to email address'), style='primary')
     453    def get_credentials(self, **data):
     454        if not self.captcha_result.is_valid:
     455            # Captcha will display error messages automatically.
     456            # No need to flash something.
     457            return
     458        number = data.get('number','')
     459        firstname = data.get('firstname','')
     460        cat = getUtility(ICatalog, name='customers_catalog')
     461        results = list(
     462            cat.searchResults(reg_number=(number, number)))
     463        if results:
     464            customer = results[0]
     465            if getattr(customer,'firstname',None) is None:
     466                self.flash(_('An error occurred.'), type="danger")
     467                return
     468            elif customer.firstname.lower() != firstname.lower():
     469                # Don't tell the truth here. Anonymous must not
     470                # know that a record was found and only the firstname
     471                # verification failed.
     472                self.flash(_('No customer record found.'), type="warning")
     473                return
     474            elif customer.password is not None and self._pw_used:
     475                self.flash(_('Your password has already been set and used. '
     476                             'Please proceed to the login page.'),
     477                           type="warning")
     478                return
     479            # Store email address but nothing else.
     480            customer.email = data['email']
     481            notify(grok.ObjectModifiedEvent(customer))
     482        else:
     483            # No record found, this is the truth.
     484            self.flash(_('No customer record found.'), type="warning")
     485            return
     486
     487        kofa_utils = getUtility(IKofaUtils)
     488        password = kofa_utils.genPassword()
     489        mandate = PasswordMandate()
     490        mandate.params['password'] = password
     491        mandate.params['user'] = customer
     492        site = grok.getSite()
     493        site['mandates'].addMandate(mandate)
     494        # Send email with credentials
     495        args = {'mandate_id':mandate.mandate_id}
     496        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
     497        url_info = u'Confirmation link: %s' % mandate_url
     498        msg = _('You have successfully requested a password for the')
     499        if kofa_utils.sendCredentials(IUserAccount(customer),
     500            password, url_info, msg):
     501            email_sent = customer.email
     502        else:
     503            email_sent = None
     504        self._redirect(email=email_sent, password=password,
     505            customer_id=customer.customer_id)
     506        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     507        self.context.logger.info(
     508            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
     509        return
     510
     511class CustomerRequestPasswordEmailSent(IkobaPage):
     512    """Landing page after successful password request.
     513
     514    """
     515    grok.name('requestpw_complete')
     516    grok.require('waeup.Public')
     517    grok.template('requestpwmailsent')
     518    label = _('Your password request was successful.')
     519
     520    def update(self, email=None, customer_id=None, password=None):
     521        self.email = email
     522        self.password = password
     523        self.customer_id = customer_id
     524        return
Note: See TracChangeset for help on using the changeset viewer.