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

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

Apply PhoneWidget? all over Kofa.

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