source: main/waeup.sirp/branches/ulif-schoolgrades/src/waeup/sirp/applicants/browser.py @ 7766

Last change on this file since 7766 was 7761, checked in by uli, 13 years ago

Some stuff to start school grade support. Messy, but a beginning. At least manage and edit of applicants provide some school grade selections.

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