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

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

Use ApplicantsContainerFactory?.

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