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

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

Remove trash.

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