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

Add browser components for customers. Tests will follow.

Location:
main/waeup.ikoba/trunk/src/waeup/ikoba
Files:
7 added
7 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
  • main/waeup.ikoba/trunk/src/waeup/ikoba/customers/interfaces.py

    r11958 r11967  
    190190        """
    191191
    192     def export_student(student, filepath=None):
     192    def export_customer(customer, filepath=None):
    193193        """Export data for a given customer.
    194194        """
     
    198198        """
    199199
     200class ICustomerRequestPW(ICustomer):
     201    """Representation of an customer for first-time password request.
     202
     203    This interface is used when customers use the requestpw page to
     204    login for the the first time.
     205    """
     206    number = schema.TextLine(
     207        title = _(u'Registration Number'),
     208        required = True,
     209        )
     210
     211    firstname = schema.TextLine(
     212        title = _(u'First Name'),
     213        required = True,
     214        )
     215
     216    email = schema.ASCIILine(
     217        title = _(u'Email Address'),
     218        required = True,
     219        constraint=validate_email,
     220        )
     221
  • main/waeup.ikoba/trunk/src/waeup/ikoba/customers/permissions.py

    r11958 r11967  
    5050    grok.name('waeup.loginAsCustomer')
    5151
     52class TriggerTransition(grok.Permission):
     53    grok.name('waeup.triggerTransition')
     54
    5255# Local role
    5356class CustomerRecordOwner(grok.Role):
     
    7679                     'waeup.manageCustomer', 'waeup.viewCustomersContainer',
    7780                     'waeup.payCustomer', 'waeup.uploadCustomerFile',
    78                      'waeup.viewCustomersTab')
     81                     'waeup.viewCustomersTab', 'waeup.triggerTransition')
    7982
    8083class CustomerImpersonator(grok.Role):
  • main/waeup.ikoba/trunk/src/waeup/ikoba/customers/viewlets.py

    r11964 r11967  
    1818
    1919import grok
     20from zope.i18n import translate
     21from zope.interface import Interface
    2022from waeup.ikoba.interfaces import IIkobaObject
    2123from waeup.ikoba.interfaces import MessageFactory as _
     
    2527    default_primary_nav_template, default_filedisplay_template,
    2628    default_fileupload_template)
    27 
    28 grok.context(IIkobaObject) # Make IKofaObject the default context
     29from waeup.ikoba.customers.interfaces import (
     30    ICustomer, ICustomersContainer)
     31from waeup.ikoba.customers.browser import (
     32    CustomersContainerPage, CustomersContainerManagePage,
     33    CustomerBaseDisplayFormPage)
     34
     35grok.context(IIkobaObject) # Make IIkobaObject the default context
    2936grok.templatedir('browser_templates')
    3037
     
    99106            ]
    100107        return targets
     108
     109class CustomerManageSidebar(grok.ViewletManager):
     110    grok.name('left_customermanage')
     111
     112class CustomerManageLink(grok.Viewlet):
     113    """A link displayed in the customer box which shows up for CustomerNavigation
     114    objects.
     115
     116    """
     117    grok.baseclass()
     118    grok.viewletmanager(CustomerManageSidebar)
     119    grok.view(Interface)
     120    grok.order(5)
     121    grok.require('waeup.viewCustomer')
     122
     123    link = 'index'
     124    text = _(u'Base Data')
     125
     126    def render(self):
     127        url = self.view.url(self.context.customer, self.link)
     128        # Here we know that the cookie has been set
     129        lang = self.request.cookies.get('kofa.language')
     130        text = translate(self.text, 'waeup.kofa',
     131            target_language=lang)
     132        if not self.link:
     133            return ''
     134        return u'<li><a href="%s">%s</a></li>' % (
     135                url, text)
     136
     137class CustomerManageBaseLink(CustomerManageLink):
     138    grok.order(2)
     139    link = 'index'
     140    text = _(u'Base Data')
     141
     142class CustomerManageHistoryLink(CustomerManageLink):
     143    grok.order(8)
     144    link = 'history'
     145    text = _(u'History')
     146
     147class CustomersContainerManageActionButton(ManageActionButton):
     148    grok.order(1)
     149    grok.context(ICustomersContainer)
     150    grok.view(CustomersContainerPage)
     151    grok.require('waeup.manageCustomer')
     152    text = _('Manage customer section')
     153
     154class CustomersContainerAddActionButton(AddActionButton):
     155    grok.order(1)
     156    grok.context(ICustomersContainer)
     157    grok.view(CustomersContainerManagePage)
     158    grok.require('waeup.manageCustomer')
     159    text = _('Add customer')
     160    target = 'addcustomer'
     161
     162class ContactActionButton(ManageActionButton):
     163    grok.order(5)
     164    grok.context(ICustomer)
     165    grok.view(CustomerBaseDisplayFormPage)
     166    grok.require('waeup.manageCustomer')
     167    icon = 'actionicon_mail.png'
     168    text = _('Send email')
     169    target = 'contactcustomer'
     170
     171class CustomerBaseManageActionButton(ManageActionButton):
     172    grok.order(1)
     173    grok.context(ICustomer)
     174    grok.view(CustomerBaseDisplayFormPage)
     175    grok.require('waeup.manageCustomer')
     176    text = _('Manage')
     177    target = 'manage_base'
     178
     179class CustomerTrigTransActionButton(ManageActionButton):
     180    grok.order(2)
     181    grok.context(ICustomer)
     182    grok.view(CustomerBaseDisplayFormPage)
     183    grok.require('waeup.triggerTransition')
     184    icon = 'actionicon_trigtrans.png'
     185    text = _(u'Trigger transition')
     186    target = 'trigtrans'
     187
     188class CustomerLoginAsActionButton(ManageActionButton):
     189    grok.order(3)
     190    grok.context(ICustomer)
     191    grok.view(CustomerBaseDisplayFormPage)
     192    grok.require('waeup.loginAsCustomer')
     193    icon = 'actionicon_mask.png'
     194    text = _(u'Login as customer')
     195    target = 'loginasstep1'
     196
     197class CustomerDeactivateActionButton(ManageActionButton):
     198    grok.order(7)
     199    grok.context(ICustomer)
     200    grok.view(CustomerBaseDisplayFormPage)
     201    grok.require('waeup.manageCustomer')
     202    text = _('Deactivate account')
     203    target = 'deactivate'
     204    icon = 'actionicon_traffic_lights_red.png'
     205
     206    @property
     207    def target_url(self):
     208        if self.context.suspended:
     209            return ''
     210        return self.view.url(self.view.context, self.target)
     211
     212    @property
     213    def onclick(self):
     214        return "return window.confirm(%s);" % _(
     215            "'A history message will be added. Are you sure?'")
     216
     217class CustomerActivateActionButton(ManageActionButton):
     218    grok.order(7)
     219    grok.context(ICustomer)
     220    grok.view(CustomerBaseDisplayFormPage)
     221    grok.require('waeup.manageCustomer')
     222    text = _('Activate account')
     223    target = 'activate'
     224    icon = 'actionicon_traffic_lights_green.png'
     225
     226    @property
     227    def target_url(self):
     228        if not self.context.suspended:
     229            return ''
     230        return self.view.url(self.view.context, self.target)
     231
     232    @property
     233    def onclick(self):
     234        return "return window.confirm(%s);" % _(
     235            "'A history message will be added. Are you sure?'")
  • main/waeup.ikoba/trunk/src/waeup/ikoba/customers/workflow.py

    r11964 r11967  
    2525from waeup.ikoba.interfaces import (
    2626    IObjectHistory, IIkobaWorkflowInfo, IIkobaUtils,
    27     CREATED, REQUESTED, APPROVED)
     27    STARTED, CREATED, REQUESTED, APPROVED)
    2828from waeup.ikoba.interfaces import MessageFactory as _
    2929from waeup.ikoba.workflow import IkobaWorkflow, IkobaWorkflowInfo
     
    3232
    3333
    34 IMPORTABLE_STATES = (REQUESTED, APPROVED)
     34IMPORTABLE_STATES = (STARTED, REQUESTED, APPROVED)
    3535
    3636REGISTRATION_TRANSITIONS = (
     
    4444
    4545    Transition(
     46        transition_id = 'start',
     47        title = _('Start registration'),
     48        source = CREATED,
     49        condition = NullCondition,
     50        msg = _('Customer registration started'),
     51        destination = STARTED),
     52
     53    Transition(
    4654        transition_id = 'request',
    4755        title = _('Request registration'),
    4856        msg = _('Customer registration requested'),
    49         source = REQUESTED,
    50         destination = APPROVED),
     57        source = STARTED,
     58        destination = REQUESTED),
    5159
    5260    Transition(
     
    6270        msg = _('Reset to initial customer state'),
    6371        source = APPROVED,
    64         destination = CREATED),
     72        destination = STARTED),
    6573
    6674    Transition(
     
    7684        msg = _("Reset to initial state"),
    7785        source = REQUESTED,
    78         destination = CREATED),
     86        destination = STARTED),
    7987
    8088    )
  • main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py

    r11964 r11967  
    4646
    4747CREATED = 'created'
     48STARTED = 'started'
    4849REQUESTED = 'requested'
    4950APPROVED = 'approved'
     
    8485registration_states_vocab = SimpleIkobaVocabulary(
    8586    (_('created'), CREATED),
     87    (_('started'), STARTED),
    8688    (_('requested'), REQUESTED),
    8789    (_('approved'), APPROVED),
  • main/waeup.ikoba/trunk/src/waeup/ikoba/permissions.py

    r11958 r11967  
    151151                     'waeup.importData',
    152152                     'waeup.exportData',
    153                      'waeup.viewTranscript',
    154153                     'waeup.managePortalConfiguration',
    155154                     'waeup.editUser',
     
    159158                     'waeup.manageCustomer', 'waeup.viewCustomersContainer',
    160159                     'waeup.payCustomer', 'waeup.uploadCustomerFile',
     160                     'waeup.triggerTransition',
    161161                     'waeup.viewCustomersTab'
    162162                     )
     
    176176                     #'waeup.importData',
    177177                     'waeup.exportData',
    178                      'waeup.viewTranscript',
    179178                     'waeup.managePortalConfiguration',
    180179                     #'waeup.editUser',
     
    184183                     'waeup.manageCustomer', 'waeup.viewCustomersContainer',
    185184                     'waeup.payCustomer', 'waeup.uploadCustomerFile',
     185                     'waeup.triggerTransition',
    186186                     'waeup.viewCustomersTab'
    187187                     )
Note: See TracChangeset for help on using the changeset viewer.