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

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

Doing the same (see last revisions) in the applicants package.

  • Property svn:keywords set to Id
File size: 34.8 KB
Line 
1## $Id: browser.py 7844 2012-03-12 11:38:09Z 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    label = _('Add applicant')
363    pnav = 3
364
365    @action(_('Create application record'))
366    def addApplicant(self, **data):
367        applicant = createObject(u'waeup.Applicant')
368        self.applyData(applicant, **data)
369        self.context.addApplicant(applicant)
370        self.flash(_('Applicant record created.'))
371        self.redirect(
372            self.url(self.context[applicant.application_number], 'index'))
373        return
374
375class ApplicantDisplayFormPage(KofaDisplayFormPage):
376    grok.context(IApplicant)
377    grok.name('index')
378    grok.require('waeup.viewApplication')
379    grok.template('applicantdisplaypage')
380    form_fields = grok.AutoFields(IApplicant).omit(
381        'locked', 'course_admitted', 'password')
382    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
383    label = _('Applicant')
384    pnav = 3
385
386    def update(self):
387        self.passport_url = self.url(self.context, 'passport.jpg')
388        # Mark application as started if applicant logs in for the first time
389        usertype = getattr(self.request.principal, 'user_type', None)
390        if usertype == 'applicant' and \
391            IWorkflowState(self.context).getState() == INITIALIZED:
392            IWorkflowInfo(self.context).fireTransition('start')
393        return
394
395    @property
396    def hasPassword(self):
397        if self.context.password:
398            return _('set')
399        return _('unset')
400
401    @property
402    def label(self):
403        container_title = self.context.__parent__.title
404        return _('${a} Application Record ${b}', mapping = {
405            'a':container_title, 'b':self.context.application_number})
406
407    def getCourseAdmitted(self):
408        """Return link, title and code in html format to the certificate
409           admitted.
410        """
411        course_admitted = self.context.course_admitted
412        if getattr(course_admitted, '__parent__',None):
413            url = self.url(course_admitted)
414            title = course_admitted.title
415            code = course_admitted.code
416            return '<a href="%s">%s - %s</a>' %(url,code,title)
417        return ''
418
419class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
420    grok.context(IApplicant)
421    grok.name('base')
422    form_fields = grok.AutoFields(IApplicant).select(
423        'applicant_id', 'firstname', 'lastname','email', 'course1')
424
425class CreateStudentPage(UtilityView, grok.View):
426    """Create a student object from applicatnt data
427    and copy applicant object.
428    """
429    grok.context(IApplicant)
430    grok.name('createstudent')
431    grok.require('waeup.manageStudent')
432
433    def update(self):
434        msg = self.context.createStudent()[1]
435        self.flash(msg)
436        self.redirect(self.url(self.context))
437        return
438
439    def render(self):
440        return
441
442class AcceptanceFeePaymentAddPage(UtilityView, grok.View):
443    """ Page to add an online payment ticket
444    """
445    grok.context(IApplicant)
446    grok.name('addafp')
447    grok.require('waeup.payApplicant')
448
449    def update(self):
450        p_category = 'acceptance'
451        session = str(self.context.__parent__.year)
452        try:
453            academic_session = grok.getSite()['configuration'][session]
454        except KeyError:
455            self.flash(_('Session configuration object is not available.'))
456            return
457        timestamp = "%d" % int(time()*1000)
458        for key in self.context.keys():
459            ticket = self.context[key]
460            if ticket.p_state == 'paid':
461                  self.flash(
462                      _('This type of payment has already been made.'))
463                  self.redirect(self.url(self.context))
464                  return
465        payment = createObject(u'waeup.ApplicantOnlinePayment')
466        payment.p_id = "p%s" % timestamp
467        payment.p_item = self.context.__parent__.title
468        payment.p_year = self.context.__parent__.year
469        payment.p_category = p_category
470        payment.amount_auth = academic_session.acceptance_fee
471        payment.surcharge_1 = academic_session.surcharge_1
472        payment.surcharge_2 = academic_session.surcharge_2
473        payment.surcharge_3 = academic_session.surcharge_3
474        self.context[payment.p_id] = payment
475        self.flash(_('Payment ticket created.'))
476        return
477
478    def render(self):
479        usertype = getattr(self.request.principal, 'user_type', None)
480        if usertype == 'applicant':
481            self.redirect(self.url(self.context, '@@edit'))
482            return
483        self.redirect(self.url(self.context, '@@manage'))
484        return
485
486
487class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
488    """ Page to view an online payment ticket
489    """
490    grok.context(IApplicantOnlinePayment)
491    grok.name('index')
492    grok.require('waeup.viewApplication')
493    form_fields = grok.AutoFields(IApplicantOnlinePayment)
494    form_fields[
495        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
496    form_fields[
497        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
498    pnav = 3
499
500    @property
501    def label(self):
502        return _('${a}: Online Payment Ticket ${b}', mapping = {
503            'a':self.context.__parent__.display_fullname, 'b':self.context.p_id})
504
505class OnlinePaymentCallbackPage(UtilityView, grok.View):
506    """ Callback view
507    """
508    grok.context(IApplicantOnlinePayment)
509    grok.name('callback')
510    grok.require('waeup.payApplicant')
511
512    # This update method simulates a valid callback und must be
513    # specified in the customization package. The parameters must be taken
514    # from the incoming request.
515    def update(self):
516        self.wf_info = IWorkflowInfo(self.context.__parent__)
517        try:
518            self.wf_info.fireTransition('pay')
519        except InvalidTransitionError:
520            self.flash('Error: %s' % sys.exc_info()[1])
521            return
522        self.context.r_amount_approved = self.context.amount_auth
523        self.context.r_card_num = u'0000'
524        self.context.r_code = u'00'
525        self.context.p_state = 'paid'
526        self.context.payment_date = datetime.now()
527        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
528        self.context.__parent__.loggerInfo(
529            ob_class, 'valid callback: %s' % self.context.p_id)
530        self.flash(_('Valid callback received.'))
531        return
532
533    def render(self):
534        self.redirect(self.url(self.context, '@@index'))
535        return
536
537class ExportPDFPaymentSlipPage(UtilityView, grok.View):
538    """Deliver a PDF slip of the context.
539    """
540    grok.context(IApplicantOnlinePayment)
541    grok.name('payment_receipt.pdf')
542    grok.require('waeup.viewApplication')
543    form_fields = grok.AutoFields(IApplicantOnlinePayment)
544    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
545    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
546    prefix = 'form'
547
548    @property
549    def title(self):
550        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
551        return translate(_('Payment Data'), 'waeup.kofa',
552            target_language=portal_language)
553
554    @property
555    def label(self):
556        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
557        return translate(_('Online Payment Receipt'),
558            'waeup.kofa', target_language=portal_language) \
559            + ' %s' % self.context.p_id
560
561    def render(self):
562        if self.context.p_state != 'paid':
563            self.flash(_('Ticket not yet paid.'))
564            self.redirect(self.url(self.context))
565            return
566        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
567            self.request)
568        students_utils = getUtility(IStudentsUtils)
569        return students_utils.renderPDF(self,'payment_receipt.pdf',
570            self.context.__parent__, applicantview)
571
572class ExportPDFPage(UtilityView, grok.View):
573    """Deliver a PDF slip of the context.
574    """
575    grok.context(IApplicant)
576    grok.name('application_slip.pdf')
577    grok.require('waeup.viewApplication')
578    form_fields = grok.AutoFields(IApplicant).omit(
579        'locked', 'course_admitted')
580    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
581    prefix = 'form'
582
583    #@property
584    #def label(self):
585    #    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
586    #    container_title = self.context.__parent__.title
587    #    label = translate('Application Record',
588    #        'waeup.kofa', target_language=portal_language)
589    #    return container_title +  label + self.context.application_number
590
591    #def getCourseAdmitted(self):
592    #    """Return title and code in html format to the certificate
593    #       admitted.
594    #    """
595    #    course_admitted = self.context.course_admitted
596    #    #if ICertificate.providedBy(course_admitted):
597    #    if getattr(course_admitted, '__parent__',None):
598    #        title = course_admitted.title
599    #        code = course_admitted.code
600    #        return '%s - %s' %(code,title)
601    #    return ''
602
603    #def setUpWidgets(self, ignore_request=False):
604    #    self.adapters = {}
605    #    self.widgets = setUpEditWidgets(
606    #        self.form_fields, self.prefix, self.context, self.request,
607    #        adapters=self.adapters, for_display=True,
608    #        ignore_request=ignore_request
609    #        )
610
611    def render(self):
612        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
613            view=self)
614        self.response.setHeader(
615            'Content-Type', 'application/pdf')
616        return pdfstream
617
618def handle_img_upload(upload, context, view):
619    """Handle upload of applicant image.
620
621    Returns `True` in case of success or `False`.
622
623    Please note that file pointer passed in (`upload`) most probably
624    points to end of file when leaving this function.
625    """
626    size = file_size(upload)
627    if size > MAX_UPLOAD_SIZE:
628        view.flash(_('Uploaded image is too big!'))
629        return False
630    dummy, ext = os.path.splitext(upload.filename)
631    ext.lower()
632    if ext != '.jpg':
633        view.flash(_('jpg file extension expected.'))
634        return False
635    upload.seek(0) # file pointer moved when determining size
636    store = getUtility(IExtFileStore)
637    file_id = IFileStoreNameChooser(context).chooseName()
638    store.createFile(file_id, upload)
639    return True
640
641class ApplicantManageFormPage(KofaEditFormPage):
642    """A full edit view for applicant data.
643    """
644    grok.context(IApplicant)
645    grok.name('manage')
646    grok.require('waeup.manageApplication')
647    form_fields = grok.AutoFields(IApplicant)
648    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
649    form_fields['student_id'].for_display = True
650    form_fields['applicant_id'].for_display = True
651    form_fields['phone'].custom_widget = PhoneWidget
652    grok.template('applicanteditpage')
653    manage_applications = True
654    pnav = 3
655    display_actions = [[_('Save'), _('Final Submit')],
656        [_('Add online payment ticket'),_('Remove selected tickets')]]
657
658    def update(self):
659        datepicker.need() # Enable jQuery datepicker in date fields.
660        warning.need()
661        super(ApplicantManageFormPage, self).update()
662        self.wf_info = IWorkflowInfo(self.context)
663        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
664        self.passport_changed = None
665        upload = self.request.form.get('form.passport', None)
666        if upload:
667            # We got a fresh upload
668            self.passport_changed = handle_img_upload(
669                upload, self.context, self)
670        return
671
672    @property
673    def label(self):
674        container_title = self.context.__parent__.title
675        return _('${a} Application Form ${b}', mapping = {
676            'a':container_title, 'b':self.context.application_number})
677
678    def getTransitions(self):
679        """Return a list of dicts of allowed transition ids and titles.
680
681        Each list entry provides keys ``name`` and ``title`` for
682        internal name and (human readable) title of a single
683        transition.
684        """
685        allowed_transitions = self.wf_info.getManualTransitions()
686        return [dict(name='', title=_('No transition'))] +[
687            dict(name=x, title=y) for x, y in allowed_transitions]
688
689    @action(_('Save'), style='primary')
690    def save(self, **data):
691        form = self.request.form
692        password = form.get('password', None)
693        password_ctl = form.get('control_password', None)
694        if password:
695            validator = getUtility(IPasswordValidator)
696            errors = validator.validate_password(password, password_ctl)
697            if errors:
698                self.flash( ' '.join(errors))
699                return
700        if self.passport_changed is False:  # False is not None!
701            return # error during image upload. Ignore other values
702        changed_fields = self.applyData(self.context, **data)
703        # Turn list of lists into single list
704        if changed_fields:
705            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
706        else:
707            changed_fields = []
708        if self.passport_changed:
709            changed_fields.append('passport')
710        if password:
711            # Now we know that the form has no errors and can set password ...
712            IUserAccount(self.context).setPassword(password)
713            changed_fields.append('password')
714        fields_string = ' + '.join(changed_fields)
715        trans_id = form.get('transition', None)
716        if trans_id:
717            self.wf_info.fireTransition(trans_id)
718        self.flash(_('Form has been saved.'))
719        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
720        if fields_string:
721            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
722        return
723
724    def unremovable(self, ticket):
725        return False
726
727    # This method is also used by the ApplicantEditFormPage
728    def delPaymentTickets(self, **data):
729        form = self.request.form
730        if form.has_key('val_id'):
731            child_id = form['val_id']
732        else:
733            self.flash(_('No payment selected.'))
734            self.redirect(self.url(self.context))
735            return
736        if not isinstance(child_id, list):
737            child_id = [child_id]
738        deleted = []
739        for id in child_id:
740            # Applicants are not allowed to remove used payment tickets
741            if not self.unremovable(self.context[id]):
742                try:
743                    del self.context[id]
744                    deleted.append(id)
745                except:
746                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
747                            id, sys.exc_info()[0], sys.exc_info()[1]))
748        if len(deleted):
749            self.flash(_('Successfully removed: ${a}',
750                mapping = {'a':', '.join(deleted)}))
751            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
752            self.context.loggerInfo(
753                ob_class, 'removed: % s' % ', '.join(deleted))
754        return
755
756    # We explicitely want the forms to be validated before payment tickets
757    # can be created. If no validation is requested, use
758    # 'validator=NullValidator' in the action directive
759    @action(_('Add online payment ticket'))
760    def addPaymentTicket(self, **data):
761        self.redirect(self.url(self.context, '@@addafp'))
762        return
763
764    @jsaction(_('Remove selected tickets'))
765    def removePaymentTickets(self, **data):
766        self.delPaymentTickets(**data)
767        self.redirect(self.url(self.context) + '/@@manage')
768        return
769
770class ApplicantEditFormPage(ApplicantManageFormPage):
771    """An applicant-centered edit view for applicant data.
772    """
773    grok.context(IApplicantEdit)
774    grok.name('edit')
775    grok.require('waeup.handleApplication')
776    form_fields = grok.AutoFields(IApplicantEdit).omit(
777        'locked', 'course_admitted', 'student_id',
778        'screening_score', 'reg_number'
779        )
780    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
781    form_fields['phone'].custom_widget = PhoneWidget
782    form_fields['applicant_id'].for_display = True
783    grok.template('applicanteditpage')
784    manage_applications = False
785
786    @property
787    def display_actions(self):
788        state = IWorkflowState(self.context).getState()
789        if state == INITIALIZED:
790            actions = [[],[]]
791        elif state == STARTED:
792            actions = [[_('Save')],
793                [_('Add online payment ticket'),_('Remove selected tickets')]]
794        elif state == PAID:
795            actions = [[_('Save'), _('Final Submit')],
796                [_('Remove selected tickets')]]
797        else:
798            actions = [[],[]]
799        return actions
800
801    def unremovable(self, ticket):
802        state = IWorkflowState(self.context).getState()
803        return ticket.r_code or state in (INITIALIZED, SUBMITTED)
804
805    def emit_lock_message(self):
806        self.flash(_('The requested form is locked (read-only).'))
807        self.redirect(self.url(self.context))
808        return
809
810    def update(self):
811        if self.context.locked:
812            self.emit_lock_message()
813            return
814        super(ApplicantEditFormPage, self).update()
815        return
816
817    def dataNotComplete(self):
818        store = getUtility(IExtFileStore)
819        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
820            return _('No passport picture uploaded.')
821        if not self.request.form.get('confirm_passport', False):
822            return _('Passport picture confirmation box not ticked.')
823        return False
824
825    # We explicitely want the forms to be validated before payment tickets
826    # can be created. If no validation is requested, use
827    # 'validator=NullValidator' in the action directive
828    @action(_('Add online payment ticket'))
829    def addPaymentTicket(self, **data):
830        self.redirect(self.url(self.context, '@@addafp'))
831        return
832
833    @jsaction(_('Remove selected tickets'))
834    def removePaymentTickets(self, **data):
835        self.delPaymentTickets(**data)
836        self.redirect(self.url(self.context) + '/@@edit')
837        return
838
839    @action(_('Save'))
840    def save(self, **data):
841        if self.passport_changed is False:  # False is not None!
842            return # error during image upload. Ignore other values
843        self.applyData(self.context, **data)
844        self.flash('Form has been saved.')
845        return
846
847    @action(_('Final Submit'))
848    def finalsubmit(self, **data):
849        if self.passport_changed is False:  # False is not None!
850            return # error during image upload. Ignore other values
851        if self.dataNotComplete():
852            self.flash(self.dataNotComplete())
853            return
854        self.applyData(self.context, **data)
855        state = IWorkflowState(self.context).getState()
856        # This shouldn't happen, but the application officer
857        # might have forgotten to lock the form after changing the state
858        if state != PAID:
859            self.flash(_('This form cannot be submitted. Wrong state!'))
860            return
861        IWorkflowInfo(self.context).fireTransition('submit')
862        self.context.application_date = datetime.now()
863        self.context.locked = True
864        self.flash(_('Form has been submitted.'))
865        self.redirect(self.url(self.context))
866        return
867
868class PassportImage(grok.View):
869    """Renders the passport image for applicants.
870    """
871    grok.name('passport.jpg')
872    grok.context(IApplicant)
873    grok.require('waeup.viewApplication')
874
875    def render(self):
876        # A filename chooser turns a context into a filename suitable
877        # for file storage.
878        image = getUtility(IExtFileStore).getFileByContext(self.context)
879        self.response.setHeader(
880            'Content-Type', 'image/jpeg')
881        if image is None:
882            # show placeholder image
883            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
884        return image
885
886class ApplicantRegistrationPage(KofaAddFormPage):
887    """Captcha'd registration page for applicants.
888    """
889    grok.context(IApplicantsContainer)
890    grok.name('register')
891    grok.require('waeup.Anonymous')
892    grok.template('applicantregister')
893    form_fields = grok.AutoFields(IApplicantEdit).select(
894        'firstname', 'middlename', 'lastname', 'email', 'phone')
895    form_fields['phone'].custom_widget = PhoneWidget
896
897    @property
898    def label(self):
899        return _('Register for ${a} Application',
900            mapping = {'a':self.context.title})
901
902    def update(self):
903        # Check if application has started ...
904        if not self.context.startdate or self.context.startdate > date.today():
905            self.flash(_('Application has not yet started.'))
906            self.redirect(self.url(self.context))
907            return
908        # ... or ended
909        if not self.context.enddate or self.context.enddate < date.today():
910            self.flash(_('Application has ended.'))
911            self.redirect(self.url(self.context))
912            return
913        # Handle captcha
914        self.captcha = getUtility(ICaptchaManager).getCaptcha()
915        self.captcha_result = self.captcha.verify(self.request)
916        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
917        return
918
919    @action(_('Get login credentials'), style='primary')
920    def register(self, **data):
921        if not self.captcha_result.is_valid:
922            # captcha will display error messages automatically.
923            # No need to flash something.
924            return
925        # Add applicant and create password
926        applicant = createObject('waeup.Applicant')
927        self.applyData(applicant, **data)
928        self.context.addApplicant(applicant)
929        kofa_utils = getUtility(IKofaUtils)
930        password = kofa_utils.genPassword()
931        IUserAccount(applicant).setPassword(password)
932        # Send email with credentials
933        login_url = self.url(grok.getSite(), 'login')
934        msg = _('You have successfully been registered for the')
935        if kofa_utils.sendCredentials(IUserAccount(applicant),
936            password, login_url, msg):
937            self.redirect(self.url(self.context, 'registration_complete',
938                                   data = dict(email=applicant.email)))
939            return
940        else:
941            self.flash(_('Email could not been sent. Please retry later.'))
942        return
943
944class ApplicantRegistrationEmailSent(KofaPage):
945    """Landing page after successful registration.
946    """
947    grok.name('registration_complete')
948    grok.require('waeup.Public')
949    grok.template('applicantregemailsent')
950    label = _('Your registration was successful.')
951
952    def update(self, email=None):
953        self.email = email
954        return
Note: See TracBrowser for help on using the repository browser.