Changeset 11967
- Timestamp:
- 16 Nov 2014, 11:53:02 (10 years ago)
- 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 19 19 """ 20 20 21 import sys 21 22 import grok 23 import pytz 24 from urllib import urlencode 25 from datetime import datetime 26 from zope.event import notify 27 from zope.i18n import translate 28 from zope.catalog.interfaces import ICatalog 29 from zope.component import queryUtility, getUtility, createObject 30 from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing 31 from zope.formlib.textwidgets import BytesDisplayWidget 32 from zope.security import checkPermission 33 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 22 34 from waeup.ikoba.interfaces import MessageFactory as _ 35 from waeup.ikoba.interfaces import IContactForm, IObjectHistory, IIkobaObject 23 36 from waeup.ikoba.browser.layout import ( 24 37 IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage, 25 IkobaForm, NullValidator) 38 IkobaForm, NullValidator, jsaction, action, UtilityView) 39 from waeup.ikoba.browser.pages import ContactAdminForm 26 40 from waeup.ikoba.browser.breadcrumbs import Breadcrumb 27 41 from waeup.ikoba.utils.helpers import get_current_principal, to_timezone, now 28 42 from waeup.ikoba.customers.interfaces import ( 29 ICustomer sContainer43 ICustomer, ICustomersContainer, ICustomerRequestPW 30 44 ) 31 45 from waeup.ikoba.customers.catalog import search 46 47 grok.context(IIkobaObject) 32 48 33 49 class CustomersBreadcrumb(Breadcrumb): … … 43 59 return None 44 60 return self.viewname 61 62 63 class 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 45 70 46 71 class CustomersContainerPage(IkobaPage): … … 141 166 mapping = {'a':', '.join(deleted)})) 142 167 return 168 169 class 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 189 class 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 214 class 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 235 class 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 260 class 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 298 class 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 342 class 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 373 class 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 392 class 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 411 class 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 424 class 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 511 class 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 190 190 """ 191 191 192 def export_ student(student, filepath=None):192 def export_customer(customer, filepath=None): 193 193 """Export data for a given customer. 194 194 """ … … 198 198 """ 199 199 200 class 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 50 50 grok.name('waeup.loginAsCustomer') 51 51 52 class TriggerTransition(grok.Permission): 53 grok.name('waeup.triggerTransition') 54 52 55 # Local role 53 56 class CustomerRecordOwner(grok.Role): … … 76 79 'waeup.manageCustomer', 'waeup.viewCustomersContainer', 77 80 'waeup.payCustomer', 'waeup.uploadCustomerFile', 78 'waeup.viewCustomersTab' )81 'waeup.viewCustomersTab', 'waeup.triggerTransition') 79 82 80 83 class CustomerImpersonator(grok.Role): -
main/waeup.ikoba/trunk/src/waeup/ikoba/customers/viewlets.py
r11964 r11967 18 18 19 19 import grok 20 from zope.i18n import translate 21 from zope.interface import Interface 20 22 from waeup.ikoba.interfaces import IIkobaObject 21 23 from waeup.ikoba.interfaces import MessageFactory as _ … … 25 27 default_primary_nav_template, default_filedisplay_template, 26 28 default_fileupload_template) 27 28 grok.context(IIkobaObject) # Make IKofaObject the default context 29 from waeup.ikoba.customers.interfaces import ( 30 ICustomer, ICustomersContainer) 31 from waeup.ikoba.customers.browser import ( 32 CustomersContainerPage, CustomersContainerManagePage, 33 CustomerBaseDisplayFormPage) 34 35 grok.context(IIkobaObject) # Make IIkobaObject the default context 29 36 grok.templatedir('browser_templates') 30 37 … … 99 106 ] 100 107 return targets 108 109 class CustomerManageSidebar(grok.ViewletManager): 110 grok.name('left_customermanage') 111 112 class 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 137 class CustomerManageBaseLink(CustomerManageLink): 138 grok.order(2) 139 link = 'index' 140 text = _(u'Base Data') 141 142 class CustomerManageHistoryLink(CustomerManageLink): 143 grok.order(8) 144 link = 'history' 145 text = _(u'History') 146 147 class 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 154 class 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 162 class 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 171 class 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 179 class 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 188 class 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 197 class 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 217 class 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 25 25 from waeup.ikoba.interfaces import ( 26 26 IObjectHistory, IIkobaWorkflowInfo, IIkobaUtils, 27 CREATED, REQUESTED, APPROVED)27 STARTED, CREATED, REQUESTED, APPROVED) 28 28 from waeup.ikoba.interfaces import MessageFactory as _ 29 29 from waeup.ikoba.workflow import IkobaWorkflow, IkobaWorkflowInfo … … 32 32 33 33 34 IMPORTABLE_STATES = ( REQUESTED, APPROVED)34 IMPORTABLE_STATES = (STARTED, REQUESTED, APPROVED) 35 35 36 36 REGISTRATION_TRANSITIONS = ( … … 44 44 45 45 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( 46 54 transition_id = 'request', 47 55 title = _('Request registration'), 48 56 msg = _('Customer registration requested'), 49 source = REQUESTED,50 destination = APPROVED),57 source = STARTED, 58 destination = REQUESTED), 51 59 52 60 Transition( … … 62 70 msg = _('Reset to initial customer state'), 63 71 source = APPROVED, 64 destination = CREATED),72 destination = STARTED), 65 73 66 74 Transition( … … 76 84 msg = _("Reset to initial state"), 77 85 source = REQUESTED, 78 destination = CREATED),86 destination = STARTED), 79 87 80 88 ) -
main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py
r11964 r11967 46 46 47 47 CREATED = 'created' 48 STARTED = 'started' 48 49 REQUESTED = 'requested' 49 50 APPROVED = 'approved' … … 84 85 registration_states_vocab = SimpleIkobaVocabulary( 85 86 (_('created'), CREATED), 87 (_('started'), STARTED), 86 88 (_('requested'), REQUESTED), 87 89 (_('approved'), APPROVED), -
main/waeup.ikoba/trunk/src/waeup/ikoba/permissions.py
r11958 r11967 151 151 'waeup.importData', 152 152 'waeup.exportData', 153 'waeup.viewTranscript',154 153 'waeup.managePortalConfiguration', 155 154 'waeup.editUser', … … 159 158 'waeup.manageCustomer', 'waeup.viewCustomersContainer', 160 159 'waeup.payCustomer', 'waeup.uploadCustomerFile', 160 'waeup.triggerTransition', 161 161 'waeup.viewCustomersTab' 162 162 ) … … 176 176 #'waeup.importData', 177 177 'waeup.exportData', 178 'waeup.viewTranscript',179 178 'waeup.managePortalConfiguration', 180 179 #'waeup.editUser', … … 184 183 'waeup.manageCustomer', 'waeup.viewCustomersContainer', 185 184 'waeup.payCustomer', 'waeup.uploadCustomerFile', 185 'waeup.triggerTransition', 186 186 'waeup.viewCustomersTab' 187 187 )
Note: See TracChangeset for help on using the changeset viewer.