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

Last change on this file since 7984 was 7903, checked in by uli, 13 years ago

Make description_dict a simple attribute.

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