source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py @ 8555

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

Extend warning message before submitting the application form.

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