source: main/waeup.sirp/branches/ulif-groktoolkit-1.4/src/waeup/sirp/applicants/browser.py @ 10222

Last change on this file since 10222 was 7710, checked in by Henrik Bettermann, 13 years ago

Backup work in progress

  • Property svn:keywords set to Id
File size: 33.9 KB
Line 
1## $Id: browser.py 7710 2012-02-27 16:14:12Z henrik $
2##
3## Copyright (C) 2011 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 basic applicants and related components.
19"""
20import os
21import sys
22import grok
23from time import time
24from datetime import datetime, date
25from zope.component import getUtility, createObject, getAdapter
26from zope.formlib.form import setUpEditWidgets
27from hurry.workflow.interfaces import (
28    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
29from waeup.sirp.applicants.interfaces import (
30    IApplicant, IApplicantEdit, IApplicantsRoot,
31    IApplicantsContainer, IApplicantsContainerAdd,
32    MAX_UPLOAD_SIZE, IApplicantOnlinePayment, IApplicantsUtils
33    )
34from waeup.sirp.applicants.workflow import INITIALIZED, STARTED, PAID, SUBMITTED
35from waeup.sirp.browser import (
36    SIRPPage, SIRPEditFormPage, SIRPAddFormPage, SIRPDisplayFormPage,
37    DEFAULT_PASSPORT_IMAGE_PATH)
38from waeup.sirp.browser.interfaces import ICaptchaManager
39from waeup.sirp.browser.breadcrumbs import Breadcrumb
40from waeup.sirp.browser.layout import (
41    NullValidator, jsaction, action, UtilityView)
42from waeup.sirp.browser.pages import add_local_role, del_local_roles
43from waeup.sirp.browser.resources import datepicker, tabs, datatable, warning
44from waeup.sirp.interfaces import (
45    ISIRPObject, ILocalRolesAssignable, IExtFileStore, IPDF,
46    IFileStoreNameChooser, IPasswordValidator, IUserAccount, ISIRPUtils)
47from waeup.sirp.interfaces import MessageFactory as _
48from waeup.sirp.permissions import get_users_with_local_roles
49from waeup.sirp.students.interfaces import IStudentsUtils
50from waeup.sirp.utils.helpers import string_from_bytes, file_size
51from waeup.sirp.widgets.datewidget import (
52    FriendlyDateWidget, FriendlyDateDisplayWidget,
53    FriendlyDatetimeDisplayWidget)
54from waeup.sirp.widgets.phonewidget import PhoneWidget
55from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
56
57grok.context(ISIRPObject) # Make ISIRPObject the default context
58
59class ApplicantsRootPage(SIRPPage):
60    grok.context(IApplicantsRoot)
61    grok.name('index')
62    grok.require('waeup.Public')
63    label = _('Application Section')
64    pnav = 3
65
66    def update(self):
67        super(ApplicantsRootPage, self).update()
68        #datatable.need()
69        return
70
71class ApplicantsRootManageFormPage(SIRPEditFormPage):
72    grok.context(IApplicantsRoot)
73    grok.name('manage')
74    grok.template('applicantsrootmanagepage')
75    label = _('Manage application section')
76    pnav = 3
77    grok.require('waeup.manageApplication')
78    taboneactions = [_('Add applicants container'), _('Remove selected'),
79        _('Cancel')]
80    tabtwoactions1 = [_('Remove selected local roles')]
81    tabtwoactions2 = [_('Add local role')]
82    subunits = _('Applicants Containers')
83
84    def update(self):
85        tabs.need()
86        datatable.need()
87        warning.need()
88        return super(ApplicantsRootManageFormPage, self).update()
89
90    def getLocalRoles(self):
91        roles = ILocalRolesAssignable(self.context)
92        return roles()
93
94    def getUsers(self):
95        """Get a list of all users.
96        """
97        for key, val in grok.getSite()['users'].items():
98            url = self.url(val)
99            yield(dict(url=url, name=key, val=val))
100
101    def getUsersWithLocalRoles(self):
102        return get_users_with_local_roles(self.context)
103
104    @jsaction(_('Remove selected'))
105    def delApplicantsContainers(self, **data):
106        form = self.request.form
107        child_id = form['val_id']
108        if not isinstance(child_id, list):
109            child_id = [child_id]
110        deleted = []
111        for id in child_id:
112            try:
113                del self.context[id]
114                deleted.append(id)
115            except:
116                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
117                        id, sys.exc_info()[0], sys.exc_info()[1]))
118        if len(deleted):
119            self.flash(_('Successfully removed:') + ' %s' % ', '.join(deleted))
120        self.redirect(self.url(self.context, '@@manage'))
121        return
122
123    @action(_('Add applicants container'), validator=NullValidator)
124    def addApplicantsContainer(self, **data):
125        self.redirect(self.url(self.context, '@@add'))
126        return
127
128    @action(_('Cancel'), validator=NullValidator)
129    def cancel(self, **data):
130        self.redirect(self.url(self.context))
131        return
132
133    @action(_('Add local role'), validator=NullValidator)
134    def addLocalRole(self, **data):
135        return add_local_role(self,3, **data)
136
137    @action(_('Remove selected local roles'))
138    def delLocalRoles(self, **data):
139        return del_local_roles(self,3,**data)
140
141class ApplicantsContainerAddFormPage(SIRPAddFormPage):
142    grok.context(IApplicantsRoot)
143    grok.require('waeup.manageApplication')
144    grok.name('add')
145    grok.template('applicantscontaineraddpage')
146    label = _('Add applicants container')
147    pnav = 3
148
149    form_fields = grok.AutoFields(
150        IApplicantsContainerAdd).omit('code').omit('title', 'description_dict')
151    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
152    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
153
154    def update(self):
155        datepicker.need() # Enable jQuery datepicker in date fields.
156        return super(ApplicantsContainerAddFormPage, self).update()
157
158    @action(_('Add applicants container'))
159    def addApplicantsContainer(self, **data):
160        year = data['year']
161        code = u'%s%s' % (data['prefix'], year)
162        appcats_dict = getUtility(IApplicantsUtils).getApplicationTypeDict()
163        title = appcats_dict[data['prefix']][0]
164        title = u'%s %s/%s' % (title, year, year + 1)
165        if code in self.context.keys():
166            self.flash(
167                _('An applicants container for the same application type and entrance year exists already in the database.'))
168            return
169        # Add new applicants container...
170        provider = data['provider'][1]
171        container = provider.factory()
172        self.applyData(container, **data)
173        container.code = code
174        container.title = title
175        self.context[code] = container
176        self.flash(_('Added:') + ' "%s".' % code)
177        self.redirect(self.url(self.context, u'@@manage'))
178        return
179
180    @action(_('Cancel'), validator=NullValidator)
181    def cancel(self, **data):
182        self.redirect(self.url(self.context, '@@manage'))
183
184class ApplicantsRootBreadcrumb(Breadcrumb):
185    """A breadcrumb for applicantsroot.
186    """
187    grok.context(IApplicantsRoot)
188    title = _(u'Applicants')
189
190class ApplicantsContainerBreadcrumb(Breadcrumb):
191    """A breadcrumb for applicantscontainers.
192    """
193    grok.context(IApplicantsContainer)
194
195class ApplicantBreadcrumb(Breadcrumb):
196    """A breadcrumb for applicants.
197    """
198    grok.context(IApplicant)
199
200    @property
201    def title(self):
202        """Get a title for a context.
203        """
204        return self.context.application_number
205
206class OnlinePaymentBreadcrumb(Breadcrumb):
207    """A breadcrumb for payments.
208    """
209    grok.context(IApplicantOnlinePayment)
210
211    @property
212    def title(self):
213        return self.context.p_id
214
215class ApplicantsContainerPage(SIRPDisplayFormPage):
216    """The standard view for regular applicant containers.
217    """
218    grok.context(IApplicantsContainer)
219    grok.name('index')
220    grok.require('waeup.Public')
221    grok.template('applicantscontainerpage')
222    pnav = 3
223
224    form_fields = grok.AutoFields(IApplicantsContainer).omit(
225        'title', 'description_dict')
226    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
227    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
228    form_fields['description'].custom_widget = ReSTDisplayWidget
229
230    @property
231    def introduction(self):
232        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
233        lang = self.request.cookies.get('sirp.language', portal_language)
234        html = self.context.description_dict.get(lang,'')
235        if html =='':
236            html = self.context.description_dict.get(portal_language,'')
237        if html =='':
238            return ''
239        else:
240            return html
241
242    @property
243    def label(self):
244        return "%s" % self.context.title
245
246class ApplicantsContainerManageFormPage(SIRPEditFormPage):
247    grok.context(IApplicantsContainer)
248    grok.name('manage')
249    grok.template('applicantscontainermanagepage')
250    form_fields = grok.AutoFields(IApplicantsContainer).omit(
251        'title', 'description_dict')
252    taboneactions = [_('Save'),_('Cancel')]
253    tabtwoactions = [_('Add applicant'), _('Remove selected'),_('Cancel')]
254    tabthreeactions1 = [_('Remove selected local roles')]
255    tabthreeactions2 = [_('Add local role')]
256    # Use friendlier date widget...
257    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
258    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
259    grok.require('waeup.manageApplication')
260
261    @property
262    def label(self):
263        return _('Manage applicants container')
264
265    pnav = 3
266
267    def update(self):
268        datepicker.need() # Enable jQuery datepicker in date fields.
269        tabs.need()
270        self.tab1 = self.tab2 = self.tab3 = ''
271        qs = self.request.get('QUERY_STRING', '')
272        if not qs:
273            qs = 'tab1'
274        setattr(self, qs, 'active')
275        warning.need()
276        datatable.need()  # Enable jQurey datatables for contents listing
277        return super(ApplicantsContainerManageFormPage, self).update()
278
279    def getLocalRoles(self):
280        roles = ILocalRolesAssignable(self.context)
281        return roles()
282
283    def getUsers(self):
284        """Get a list of all users.
285        """
286        for key, val in grok.getSite()['users'].items():
287            url = self.url(val)
288            yield(dict(url=url, name=key, val=val))
289
290    def getUsersWithLocalRoles(self):
291        return get_users_with_local_roles(self.context)
292
293    def _description(self):
294        view = ApplicantsContainerPage(
295            self.context,self.request)
296        view.setUpWidgets()
297        return view.widgets['description']()
298
299    @action('Save', style='primary')
300    def save(self, **data):
301        self.applyData(self.context, **data)
302        self.context.description_dict = self._description()
303        self.flash(_('Form has been saved.'))
304        return
305
306    @jsaction(_('Remove selected'))
307    def delApplicant(self, **data):
308        form = self.request.form
309        if form.has_key('val_id'):
310            child_id = form['val_id']
311        else:
312            self.flash(_('No applicant selected!'))
313            self.redirect(self.url(self.context, '@@manage')+'?tab2')
314            return
315        if not isinstance(child_id, list):
316            child_id = [child_id]
317        deleted = []
318        for id in child_id:
319            try:
320                del self.context[id]
321                deleted.append(id)
322            except:
323                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
324                        id, sys.exc_info()[0], sys.exc_info()[1]))
325        if len(deleted):
326            self.flash(_('Successfully removed:') + ' %s' % ', '.join(deleted))
327        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
328        return
329
330    @action(_('Add applicant'), validator=NullValidator)
331    def addApplicant(self, **data):
332        self.redirect(self.url(self.context, 'addapplicant'))
333        return
334
335    @action(_('Cancel'), validator=NullValidator)
336    def cancel(self, **data):
337        self.redirect(self.url(self.context))
338        return
339
340    @action(_('Add local role'), validator=NullValidator)
341    def addLocalRole(self, **data):
342        return add_local_role(self,3, **data)
343
344    @action(_('Remove selected local roles'))
345    def delLocalRoles(self, **data):
346        return del_local_roles(self,3,**data)
347
348class ApplicantAddFormPage(SIRPAddFormPage):
349    """Add-form to add an applicant.
350    """
351    grok.context(IApplicantsContainer)
352    grok.require('waeup.manageApplication')
353    grok.name('addapplicant')
354    #grok.template('applicantaddpage')
355    form_fields = grok.AutoFields(IApplicant).select(
356        'firstname', 'middlename', 'lastname',
357        'email', 'phone')
358    label = 'Add applicant'
359    pnav = 3
360
361    @action('Create application record')
362    def addApplicant(self, **data):
363        applicant = createObject(u'waeup.Applicant')
364        self.applyData(applicant, **data)
365        self.context.addApplicant(applicant)
366        self.flash('Applicant record created.')
367        self.redirect(
368            self.url(self.context[applicant.application_number], 'index'))
369        return
370
371class ApplicantDisplayFormPage(SIRPDisplayFormPage):
372    grok.context(IApplicant)
373    grok.name('index')
374    grok.require('waeup.viewApplication')
375    grok.template('applicantdisplaypage')
376    form_fields = grok.AutoFields(IApplicant).omit(
377        'locked', 'course_admitted', 'password')
378    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
379    label = 'Applicant'
380    pnav = 3
381
382    def update(self):
383        self.passport_url = self.url(self.context, 'passport.jpg')
384        # Mark application as started if applicant logs in for the first time
385        usertype = getattr(self.request.principal, 'user_type', None)
386        if usertype == 'applicant' and \
387            IWorkflowState(self.context).getState() == INITIALIZED:
388            IWorkflowInfo(self.context).fireTransition('start')
389        return
390
391    @property
392    def hasPassword(self):
393        if self.context.password:
394            return 'set'
395        return 'unset'
396
397    @property
398    def label(self):
399        container_title = self.context.__parent__.title
400        return '%s Application Record %s' % (
401            container_title, self.context.application_number)
402
403    def getCourseAdmitted(self):
404        """Return link, title and code in html format to the certificate
405           admitted.
406        """
407        course_admitted = self.context.course_admitted
408        if getattr(course_admitted, '__parent__',None):
409            url = self.url(course_admitted)
410            title = course_admitted.title
411            code = course_admitted.code
412            return '<a href="%s">%s - %s</a>' %(url,code,title)
413        return ''
414
415class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
416    grok.context(IApplicant)
417    grok.name('base')
418    form_fields = grok.AutoFields(IApplicant).select(
419        'applicant_id', 'firstname', 'lastname','email', 'course1')
420
421class CreateStudentPage(UtilityView, grok.View):
422    """Create a student object from applicatnt data
423    and copy applicant object.
424    """
425    grok.context(IApplicant)
426    grok.name('createstudent')
427    grok.require('waeup.manageStudent')
428
429    def update(self):
430        msg = self.context.createStudent()[1]
431        self.flash(msg)
432        self.redirect(self.url(self.context))
433        return
434
435    def render(self):
436        return
437
438class AcceptanceFeePaymentAddPage(UtilityView, grok.View):
439    """ Page to add an online payment ticket
440    """
441    grok.context(IApplicant)
442    grok.name('addafp')
443    grok.require('waeup.payApplicant')
444
445    def update(self):
446        p_category = 'acceptance'
447        session = str(self.context.__parent__.year)
448        try:
449            academic_session = grok.getSite()['configuration'][session]
450        except KeyError:
451            self.flash('Session configuration object is not available.')
452            return
453        timestamp = "%d" % int(time()*1000)
454        for key in self.context.keys():
455            ticket = self.context[key]
456            if ticket.p_state == 'paid':
457                  self.flash(
458                      'This type of payment has already been made.')
459                  self.redirect(self.url(self.context))
460                  return
461        payment = createObject(u'waeup.ApplicantOnlinePayment')
462        payment.p_id = "p%s" % timestamp
463        payment.p_item = self.context.__parent__.title
464        payment.p_year = self.context.__parent__.year
465        payment.p_category = p_category
466        payment.amount_auth = academic_session.acceptance_fee
467        payment.surcharge_1 = academic_session.surcharge_1
468        payment.surcharge_2 = academic_session.surcharge_2
469        payment.surcharge_3 = academic_session.surcharge_3
470        self.context[payment.p_id] = payment
471        self.flash('Payment ticket created.')
472        return
473
474    def render(self):
475        usertype = getattr(self.request.principal, 'user_type', None)
476        if usertype == 'applicant':
477            self.redirect(self.url(self.context, '@@edit'))
478            return
479        self.redirect(self.url(self.context, '@@manage'))
480        return
481
482
483class OnlinePaymentDisplayFormPage(SIRPDisplayFormPage):
484    """ Page to view an online payment ticket
485    """
486    grok.context(IApplicantOnlinePayment)
487    grok.name('index')
488    grok.require('waeup.viewApplication')
489    form_fields = grok.AutoFields(IApplicantOnlinePayment)
490    form_fields[
491        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
492    form_fields[
493        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
494    pnav = 3
495
496    @property
497    def label(self):
498        return '%s: Online Payment Ticket %s' % (
499            self.context.__parent__.display_fullname,self.context.p_id)
500
501class OnlinePaymentCallbackPage(UtilityView, grok.View):
502    """ Callback view
503    """
504    grok.context(IApplicantOnlinePayment)
505    grok.name('callback')
506    grok.require('waeup.payApplicant')
507
508    # This update method simulates a valid callback und must be
509    # specified in the customization package. The parameters must be taken
510    # from the incoming request.
511    def update(self):
512        self.wf_info = IWorkflowInfo(self.context.__parent__)
513        try:
514            self.wf_info.fireTransition('pay')
515        except InvalidTransitionError:
516            self.flash('Error: %s' % sys.exc_info()[1])
517            return
518        self.context.r_amount_approved = self.context.amount_auth
519        self.context.r_card_num = u'0000'
520        self.context.r_code = u'00'
521        self.context.p_state = 'paid'
522        self.context.payment_date = datetime.now()
523        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
524        self.context.__parent__.loggerInfo(
525            ob_class, 'valid callback: %s' % self.context.p_id)
526        self.flash('Valid callback received.')
527        return
528
529    def render(self):
530        self.redirect(self.url(self.context, '@@index'))
531        return
532
533class ExportPDFPaymentSlipPage(UtilityView, grok.View):
534    """Deliver a PDF slip of the context.
535    """
536    grok.context(IApplicantOnlinePayment)
537    grok.name('payment_receipt.pdf')
538    grok.require('waeup.viewApplication')
539    form_fields = grok.AutoFields(IApplicantOnlinePayment)
540    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
541    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
542    prefix = 'form'
543    title = 'Payment Data'
544
545    @property
546    def label(self):
547        return 'Online Payment Receipt %s' % self.context.p_id
548
549    def render(self):
550        if self.context.p_state != 'paid':
551            self.flash('Ticket not yet paid.')
552            self.redirect(self.url(self.context))
553            return
554        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
555            self.request)
556        students_utils = getUtility(IStudentsUtils)
557        return students_utils.renderPDF(self,'payment_receipt.pdf',
558            self.context.__parent__, applicantview)
559
560class ExportPDFPage(UtilityView, grok.View):
561    """Deliver a PDF slip of the context.
562    """
563    grok.context(IApplicant)
564    grok.name('application_slip.pdf')
565    grok.require('waeup.viewApplication')
566    form_fields = grok.AutoFields(IApplicant).omit(
567        'locked', 'course_admitted')
568    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
569    prefix = 'form'
570
571    @property
572    def label(self):
573        container_title = self.context.__parent__.title
574        return '%s Application Record %s' % (
575            container_title, self.context.application_number)
576
577    def getCourseAdmitted(self):
578        """Return title and code in html format to the certificate
579           admitted.
580        """
581        course_admitted = self.context.course_admitted
582        #if ICertificate.providedBy(course_admitted):
583        if getattr(course_admitted, '__parent__',None):
584            title = course_admitted.title
585            code = course_admitted.code
586            return '%s - %s' %(code,title)
587        return ''
588
589    def setUpWidgets(self, ignore_request=False):
590        self.adapters = {}
591        self.widgets = setUpEditWidgets(
592            self.form_fields, self.prefix, self.context, self.request,
593            adapters=self.adapters, for_display=True,
594            ignore_request=ignore_request
595            )
596
597    def render(self):
598        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
599            view=self)
600        self.response.setHeader(
601            'Content-Type', 'application/pdf')
602        return pdfstream
603
604def handle_img_upload(upload, context, view):
605    """Handle upload of applicant image.
606
607    Returns `True` in case of success or `False`.
608
609    Please note that file pointer passed in (`upload`) most probably
610    points to end of file when leaving this function.
611    """
612    size = file_size(upload)
613    if size > MAX_UPLOAD_SIZE:
614        view.flash('Uploaded image is too big!')
615        return False
616    dummy, ext = os.path.splitext(upload.filename)
617    ext.lower()
618    if ext != '.jpg':
619        view.flash('jpg file extension expected.')
620        return False
621    upload.seek(0) # file pointer moved when determining size
622    store = getUtility(IExtFileStore)
623    file_id = IFileStoreNameChooser(context).chooseName()
624    store.createFile(file_id, upload)
625    return True
626
627class ApplicantManageFormPage(SIRPEditFormPage):
628    """A full edit view for applicant data.
629    """
630    grok.context(IApplicant)
631    grok.name('manage')
632    grok.require('waeup.manageApplication')
633    form_fields = grok.AutoFields(IApplicant)
634    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
635    form_fields['student_id'].for_display = True
636    form_fields['applicant_id'].for_display = True
637    form_fields['phone'].custom_widget = PhoneWidget
638    grok.template('applicanteditpage')
639    manage_applications = True
640    pnav = 3
641    display_actions = [['Save', 'Final Submit'],
642                       ['Add online payment ticket','Remove selected tickets']]
643
644    def update(self):
645        datepicker.need() # Enable jQuery datepicker in date fields.
646        warning.need()
647        super(ApplicantManageFormPage, self).update()
648        self.wf_info = IWorkflowInfo(self.context)
649        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
650        self.passport_changed = None
651        upload = self.request.form.get('form.passport', None)
652        if upload:
653            # We got a fresh upload
654            self.passport_changed = handle_img_upload(
655                upload, self.context, self)
656        return
657
658    @property
659    def label(self):
660        container_title = self.context.__parent__.title
661        return '%s Application Form %s' % (
662            container_title, self.context.application_number)
663
664    def getTransitions(self):
665        """Return a list of dicts of allowed transition ids and titles.
666
667        Each list entry provides keys ``name`` and ``title`` for
668        internal name and (human readable) title of a single
669        transition.
670        """
671        allowed_transitions = self.wf_info.getManualTransitions()
672        return [dict(name='', title=_('No transition'))] +[
673            dict(name=x, title=y) for x, y in allowed_transitions]
674
675    @action('Save', style='primary')
676    def save(self, **data):
677        form = self.request.form
678        password = form.get('password', None)
679        password_ctl = form.get('control_password', None)
680        if password:
681            validator = getUtility(IPasswordValidator)
682            errors = validator.validate_password(password, password_ctl)
683            if errors:
684                self.flash( ' '.join(errors))
685                return
686        if self.passport_changed is False:  # False is not None!
687            return # error during image upload. Ignore other values
688        changed_fields = self.applyData(self.context, **data)
689        # Turn list of lists into single list
690        if changed_fields:
691            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
692        else:
693            changed_fields = []
694        if self.passport_changed:
695            changed_fields.append('passport')
696        if password:
697            # Now we know that the form has no errors and can set password ...
698            IUserAccount(self.context).setPassword(password)
699            changed_fields.append('password')
700        fields_string = ' + '.join(changed_fields)
701        trans_id = form.get('transition', None)
702        if trans_id:
703            self.wf_info.fireTransition(trans_id)
704        self.flash('Form has been saved.')
705        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
706        if fields_string:
707            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
708        return
709
710    def unremovable(self, ticket):
711        return False
712
713    # This method is also used by the ApplicantEditFormPage
714    def delPaymentTickets(self, **data):
715        form = self.request.form
716        if form.has_key('val_id'):
717            child_id = form['val_id']
718        else:
719            self.flash('No payment selected.')
720            self.redirect(self.url(self.context))
721            return
722        if not isinstance(child_id, list):
723            child_id = [child_id]
724        deleted = []
725        for id in child_id:
726            # Applicants are not allowed to remove used payment tickets
727            if not self.unremovable(self.context[id]):
728                try:
729                    del self.context[id]
730                    deleted.append(id)
731                except:
732                    self.flash('Could not delete %s: %s: %s' % (
733                            id, sys.exc_info()[0], sys.exc_info()[1]))
734        if len(deleted):
735            self.flash('Successfully removed: %s' % ', '.join(deleted))
736            ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
737            self.context.loggerInfo(
738                ob_class, 'removed: % s' % ', '.join(deleted))
739        return
740
741    # We explicitely want the forms to be validated before payment tickets
742    # can be created. If no validation is requested, use
743    # 'validator=NullValidator' in the action directive
744    @action('Add online payment ticket')
745    def addPaymentTicket(self, **data):
746        self.redirect(self.url(self.context, '@@addafp'))
747        return
748
749    @jsaction('Remove selected tickets')
750    def removePaymentTickets(self, **data):
751        self.delPaymentTickets(**data)
752        self.redirect(self.url(self.context) + '/@@manage')
753        return
754
755class ApplicantEditFormPage(ApplicantManageFormPage):
756    """An applicant-centered edit view for applicant data.
757    """
758    grok.context(IApplicantEdit)
759    grok.name('edit')
760    grok.require('waeup.handleApplication')
761    form_fields = grok.AutoFields(IApplicantEdit).omit(
762        'locked', 'course_admitted', 'student_id',
763        'screening_score', 'reg_number'
764        )
765    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
766    form_fields['phone'].custom_widget = PhoneWidget
767    form_fields['applicant_id'].for_display = True
768    grok.template('applicanteditpage')
769    manage_applications = False
770
771    @property
772    def display_actions(self):
773        state = IWorkflowState(self.context).getState()
774        if state == INITIALIZED:
775            actions = [[],[]]
776        elif state == STARTED:
777            actions = [['Save'],
778                       ['Add online payment ticket','Remove selected tickets']]
779        elif state == PAID:
780            actions = [['Save', 'Final Submit'],
781                       ['Remove selected tickets']]
782        else:
783            actions = [[],[]]
784        return actions
785
786    def unremovable(self, ticket):
787        state = IWorkflowState(self.context).getState()
788        return ticket.r_code or state in (INITIALIZED, SUBMITTED)
789
790    def emit_lock_message(self):
791        self.flash('The requested form is locked (read-only).')
792        self.redirect(self.url(self.context))
793        return
794
795    def update(self):
796        if self.context.locked:
797            self.emit_lock_message()
798            return
799        super(ApplicantEditFormPage, self).update()
800        return
801
802    def dataNotComplete(self):
803        store = getUtility(IExtFileStore)
804        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
805            return 'No passport picture uploaded.'
806        if not self.request.form.get('confirm_passport', False):
807            return 'Passport picture confirmation box not ticked.'
808        return False
809
810    # We explicitely want the forms to be validated before payment tickets
811    # can be created. If no validation is requested, use
812    # 'validator=NullValidator' in the action directive
813    @action('Add online payment ticket')
814    def addPaymentTicket(self, **data):
815        self.redirect(self.url(self.context, '@@addafp'))
816        return
817
818    @jsaction('Remove selected tickets')
819    def removePaymentTickets(self, **data):
820        self.delPaymentTickets(**data)
821        self.redirect(self.url(self.context) + '/@@edit')
822        return
823
824    @action('Save')
825    def save(self, **data):
826        if self.passport_changed is False:  # False is not None!
827            return # error during image upload. Ignore other values
828        self.applyData(self.context, **data)
829        self.flash('Form has been saved.')
830        return
831
832    @action('Final Submit')
833    def finalsubmit(self, **data):
834        if self.passport_changed is False:  # False is not None!
835            return # error during image upload. Ignore other values
836        if self.dataNotComplete():
837            self.flash(self.dataNotComplete())
838            return
839        self.applyData(self.context, **data)
840        state = IWorkflowState(self.context).getState()
841        # This shouldn't happen, but the application officer
842        # might have forgotten to lock the form after changing the state
843        if state != PAID:
844            self.flash('This form cannot be submitted. Wrong state!')
845            return
846        IWorkflowInfo(self.context).fireTransition('submit')
847        self.context.application_date = datetime.now()
848        self.context.locked = True
849        self.flash('Form has been submitted.')
850        self.redirect(self.url(self.context))
851        return
852
853class PassportImage(grok.View):
854    """Renders the passport image for applicants.
855    """
856    grok.name('passport.jpg')
857    grok.context(IApplicant)
858    grok.require('waeup.viewApplication')
859
860    def render(self):
861        # A filename chooser turns a context into a filename suitable
862        # for file storage.
863        image = getUtility(IExtFileStore).getFileByContext(self.context)
864        self.response.setHeader(
865            'Content-Type', 'image/jpeg')
866        if image is None:
867            # show placeholder image
868            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
869        return image
870
871class ApplicantRegistrationPage(SIRPAddFormPage):
872    """Captcha'd registration page for applicants.
873    """
874    grok.context(IApplicantsContainer)
875    grok.name('register')
876    grok.require('waeup.Anonymous')
877    grok.template('applicantregister')
878    form_fields = grok.AutoFields(IApplicantEdit).select(
879        'firstname', 'middlename', 'lastname', 'email', 'phone')
880    form_fields['phone'].custom_widget = PhoneWidget
881
882    @property
883    def label(self):
884        return "Register for %s Application" % self.context.title
885
886    def update(self):
887        # Check if application has started ...
888        if not self.context.startdate or self.context.startdate > date.today():
889            self.flash('Application has not yet started.')
890            self.redirect(self.url(self.context))
891            return
892        # ... or ended
893        if not self.context.enddate or self.context.enddate < date.today():
894            self.flash('Application has ended.')
895            self.redirect(self.url(self.context))
896            return
897        # Handle captcha
898        self.captcha = getUtility(ICaptchaManager).getCaptcha()
899        self.captcha_result = self.captcha.verify(self.request)
900        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
901        return
902
903    @action('Get login credentials', style='primary')
904    def register(self, **data):
905        if not self.captcha_result.is_valid:
906            # captcha will display error messages automatically.
907            # No need to flash something.
908            return
909        # Add applicant and create password
910        applicant = createObject('waeup.Applicant')
911        self.applyData(applicant, **data)
912        self.context.addApplicant(applicant)
913        sirp_utils = getUtility(ISIRPUtils)
914        password = sirp_utils.genPassword()
915        IUserAccount(applicant).setPassword(password)
916        # Send email with credentials
917        login_url = self.url(grok.getSite(), 'login')
918        msg = 'You have successfully been registered for the'
919        if sirp_utils.sendCredentials(IUserAccount(applicant),
920            password, login_url, msg):
921            self.redirect(self.url(self.context, 'registration_complete',
922                                   data = dict(email=applicant.email)))
923            return
924        else:
925            self.flash('Email could not been sent. Please retry later.')
926        return
927
928class ApplicantRegistrationEmailSent(SIRPPage):
929    """Landing page after successful registration.
930    """
931    grok.name('registration_complete')
932    grok.require('waeup.Public')
933    grok.template('applicantregemailsent')
934    label = 'Your registration was successful'
935
936    def update(self, email=None):
937        self.email = email
938        return
Note: See TracBrowser for help on using the repository browser.