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

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

Remove trash.

  • Property svn:keywords set to Id
File size: 33.4 KB
Line 
1## $Id: browser.py 8014 2012-04-02 09:19:37Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for basic applicants and related components.
19"""
20import os
21import sys
22import grok
23from time import time
24from datetime import datetime, date
25from zope.component import getUtility, createObject, getAdapter
26from zope.i18n import translate
27from hurry.workflow.interfaces import (
28    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
29from waeup.kofa.applicants.interfaces import (
30    IApplicant, IApplicantEdit, IApplicantsRoot,
31    IApplicantsContainer, IApplicantsContainerAdd,
32    MAX_UPLOAD_SIZE, IApplicantOnlinePayment, IApplicantsUtils
33    )
34from waeup.kofa.applicants.workflow import INITIALIZED, STARTED, PAID, SUBMITTED
35from waeup.kofa.browser import (
36    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
37    DEFAULT_PASSPORT_IMAGE_PATH)
38from waeup.kofa.browser.interfaces import ICaptchaManager
39from waeup.kofa.browser.breadcrumbs import Breadcrumb
40from waeup.kofa.browser.layout import (
41    NullValidator, jsaction, action, UtilityView)
42from waeup.kofa.browser.pages import add_local_role, del_local_roles
43from waeup.kofa.browser.resources import datepicker, tabs, datatable, warning
44from waeup.kofa.interfaces import (
45    IKofaObject, ILocalRolesAssignable, IExtFileStore, IPDF,
46    IFileStoreNameChooser, IPasswordValidator, IUserAccount, IKofaUtils)
47from waeup.kofa.interfaces import MessageFactory as _
48from waeup.kofa.permissions import get_users_with_local_roles
49from waeup.kofa.students.interfaces import IStudentsUtils
50from waeup.kofa.utils.helpers import string_from_bytes, file_size
51from waeup.kofa.widgets.datewidget import (
52    FriendlyDateWidget, FriendlyDateDisplayWidget,
53    FriendlyDatetimeDisplayWidget)
54from waeup.kofa.widgets.phonewidget import PhoneWidget
55from waeup.kofa.widgets.restwidget import ReSTDisplayWidget
56
57grok.context(IKofaObject) # Make IKofaObject the default context
58
59class ApplicantsRootPage(KofaPage):
60    grok.context(IApplicantsRoot)
61    grok.name('index')
62    grok.require('waeup.Public')
63    label = _('Application Section')
64    pnav = 3
65
66    def update(self):
67        super(ApplicantsRootPage, self).update()
68        #datatable.need()
69        return
70
71class ApplicantsRootManageFormPage(KofaEditFormPage):
72    grok.context(IApplicantsRoot)
73    grok.name('manage')
74    grok.template('applicantsrootmanagepage')
75    label = _('Manage application section')
76    pnav = 3
77    grok.require('waeup.manageApplication')
78    taboneactions = [_('Add applicants container'), _('Remove selected'),
79        _('Cancel')]
80    tabtwoactions1 = [_('Remove selected local roles')]
81    tabtwoactions2 = [_('Add local role')]
82    subunits = _('Applicants Containers')
83
84    def update(self):
85        tabs.need()
86        datatable.need()
87        warning.need()
88        return super(ApplicantsRootManageFormPage, self).update()
89
90    def getLocalRoles(self):
91        roles = ILocalRolesAssignable(self.context)
92        return roles()
93
94    def getUsers(self):
95        """Get a list of all users.
96        """
97        for key, val in grok.getSite()['users'].items():
98            url = self.url(val)
99            yield(dict(url=url, name=key, val=val))
100
101    def getUsersWithLocalRoles(self):
102        return get_users_with_local_roles(self.context)
103
104    @jsaction(_('Remove selected'))
105    def delApplicantsContainers(self, **data):
106        form = self.request.form
107        child_id = form['val_id']
108        if not isinstance(child_id, list):
109            child_id = [child_id]
110        deleted = []
111        for id in child_id:
112            try:
113                del self.context[id]
114                deleted.append(id)
115            except:
116                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
117                        id, sys.exc_info()[0], sys.exc_info()[1]))
118        if len(deleted):
119            self.flash(_('Successfully removed: ${a}',
120                mapping = {'a':', '.join(deleted)}))
121        self.redirect(self.url(self.context, '@@manage'))
122        return
123
124    @action(_('Add applicants container'), validator=NullValidator)
125    def addApplicantsContainer(self, **data):
126        self.redirect(self.url(self.context, '@@add'))
127        return
128
129    @action(_('Cancel'), validator=NullValidator)
130    def cancel(self, **data):
131        self.redirect(self.url(self.context))
132        return
133
134    @action(_('Add local role'), validator=NullValidator)
135    def addLocalRole(self, **data):
136        return add_local_role(self,3, **data)
137
138    @action(_('Remove selected local roles'))
139    def delLocalRoles(self, **data):
140        return del_local_roles(self,3,**data)
141
142class ApplicantsContainerAddFormPage(KofaAddFormPage):
143    grok.context(IApplicantsRoot)
144    grok.require('waeup.manageApplication')
145    grok.name('add')
146    grok.template('applicantscontaineraddpage')
147    label = _('Add applicants container')
148    pnav = 3
149
150    form_fields = grok.AutoFields(
151        IApplicantsContainerAdd).omit('code').omit('title')
152    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
153    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
154
155    def update(self):
156        datepicker.need() # Enable jQuery datepicker in date fields.
157        return super(ApplicantsContainerAddFormPage, self).update()
158
159    @action(_('Add applicants container'))
160    def addApplicantsContainer(self, **data):
161        year = data['year']
162        code = u'%s%s' % (data['prefix'], year)
163        appcats_dict = getUtility(IApplicantsUtils).APP_TYPES_DICT
164        title = appcats_dict[data['prefix']][0]
165        title = u'%s %s/%s' % (title, year, year + 1)
166        if code in self.context.keys():
167            self.flash(
168                _('An applicants container for the same application type and entrance year exists already in the database.'))
169            return
170        # Add new applicants container...
171        container = createObject(u'waeup.ApplicantsContainer')
172        self.applyData(container, **data)
173        container.code = code
174        container.title = title
175        self.context[code] = container
176        self.flash(_('Added:') + ' "%s".' % code)
177        self.redirect(self.url(self.context, u'@@manage'))
178        return
179
180    @action(_('Cancel'), validator=NullValidator)
181    def cancel(self, **data):
182        self.redirect(self.url(self.context, '@@manage'))
183
184class ApplicantsRootBreadcrumb(Breadcrumb):
185    """A breadcrumb for applicantsroot.
186    """
187    grok.context(IApplicantsRoot)
188    title = _(u'Applicants')
189
190class ApplicantsContainerBreadcrumb(Breadcrumb):
191    """A breadcrumb for applicantscontainers.
192    """
193    grok.context(IApplicantsContainer)
194
195class ApplicantBreadcrumb(Breadcrumb):
196    """A breadcrumb for applicants.
197    """
198    grok.context(IApplicant)
199
200    @property
201    def title(self):
202        """Get a title for a context.
203        """
204        return self.context.application_number
205
206class OnlinePaymentBreadcrumb(Breadcrumb):
207    """A breadcrumb for payments.
208    """
209    grok.context(IApplicantOnlinePayment)
210
211    @property
212    def title(self):
213        return self.context.p_id
214
215class ApplicantsContainerPage(KofaDisplayFormPage):
216    """The standard view for regular applicant containers.
217    """
218    grok.context(IApplicantsContainer)
219    grok.name('index')
220    grok.require('waeup.Public')
221    grok.template('applicantscontainerpage')
222    pnav = 3
223
224    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
225    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
226    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
227    form_fields['description'].custom_widget = ReSTDisplayWidget
228
229    @property
230    def introduction(self):
231        # Here we know that the cookie has been set
232        lang = self.request.cookies.get('kofa.language')
233        html = self.context.description_dict.get(lang,'')
234        if html =='':
235            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
236            html = self.context.description_dict.get(portal_language,'')
237        if html =='':
238            return ''
239        else:
240            return html
241
242    @property
243    def label(self):
244        return "%s" % self.context.title
245
246class ApplicantsContainerManageFormPage(KofaEditFormPage):
247    grok.context(IApplicantsContainer)
248    grok.name('manage')
249    grok.template('applicantscontainermanagepage')
250    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
251    taboneactions = [_('Save'),_('Cancel')]
252    tabtwoactions = [_('Add applicant'), _('Remove selected'),_('Cancel')]
253    tabthreeactions1 = [_('Remove selected local roles')]
254    tabthreeactions2 = [_('Add local role')]
255    # Use friendlier date widget...
256    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
257    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
258    grok.require('waeup.manageApplication')
259
260    @property
261    def label(self):
262        return _('Manage applicants container')
263
264    pnav = 3
265
266    def update(self):
267        datepicker.need() # Enable jQuery datepicker in date fields.
268        tabs.need()
269        self.tab1 = self.tab2 = self.tab3 = ''
270        qs = self.request.get('QUERY_STRING', '')
271        if not qs:
272            qs = 'tab1'
273        setattr(self, qs, 'active')
274        warning.need()
275        datatable.need()  # Enable jQurey datatables for contents listing
276        return super(ApplicantsContainerManageFormPage, self).update()
277
278    def getLocalRoles(self):
279        roles = ILocalRolesAssignable(self.context)
280        return roles()
281
282    def getUsers(self):
283        """Get a list of all users.
284        """
285        for key, val in grok.getSite()['users'].items():
286            url = self.url(val)
287            yield(dict(url=url, name=key, val=val))
288
289    def getUsersWithLocalRoles(self):
290        return get_users_with_local_roles(self.context)
291
292    def _description(self):
293        view = ApplicantsContainerPage(
294            self.context,self.request)
295        view.setUpWidgets()
296        return view.widgets['description']()
297
298    @action(_('Save'), style='primary')
299    def save(self, **data):
300        self.applyData(self.context, **data)
301        self.context.description_dict = self._description()
302        self.flash(_('Form has been saved.'))
303        return
304
305    @jsaction(_('Remove selected'))
306    def delApplicant(self, **data):
307        form = self.request.form
308        if form.has_key('val_id'):
309            child_id = form['val_id']
310        else:
311            self.flash(_('No applicant selected!'))
312            self.redirect(self.url(self.context, '@@manage')+'?tab2')
313            return
314        if not isinstance(child_id, list):
315            child_id = [child_id]
316        deleted = []
317        for id in child_id:
318            try:
319                del self.context[id]
320                deleted.append(id)
321            except:
322                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
323                        id, sys.exc_info()[0], sys.exc_info()[1]))
324        if len(deleted):
325            self.flash(_('Successfully removed: ${a}',
326                mapping = {'a':', '.join(deleted)}))
327        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
328        return
329
330    @action(_('Add applicant'), validator=NullValidator)
331    def addApplicant(self, **data):
332        self.redirect(self.url(self.context, 'addapplicant'))
333        return
334
335    @action(_('Cancel'), validator=NullValidator)
336    def cancel(self, **data):
337        self.redirect(self.url(self.context))
338        return
339
340    @action(_('Add local role'), validator=NullValidator)
341    def addLocalRole(self, **data):
342        return add_local_role(self,3, **data)
343
344    @action(_('Remove selected local roles'))
345    def delLocalRoles(self, **data):
346        return del_local_roles(self,3,**data)
347
348class ApplicantAddFormPage(KofaAddFormPage):
349    """Add-form to add an applicant.
350    """
351    grok.context(IApplicantsContainer)
352    grok.require('waeup.manageApplication')
353    grok.name('addapplicant')
354    #grok.template('applicantaddpage')
355    form_fields = grok.AutoFields(IApplicant).select(
356        'firstname', 'middlename', 'lastname',
357        'email', 'phone')
358    form_fields['phone'].custom_widget = PhoneWidget
359    label = _('Add applicant')
360    pnav = 3
361
362    @action(_('Create application record'))
363    def addApplicant(self, **data):
364        applicant = createObject(u'waeup.Applicant')
365        self.applyData(applicant, **data)
366        self.context.addApplicant(applicant)
367        self.flash(_('Applicant record created.'))
368        self.redirect(
369            self.url(self.context[applicant.application_number], 'index'))
370        return
371
372class ApplicantDisplayFormPage(KofaDisplayFormPage):
373    """A display view for applicant data.
374    """
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(KofaDisplayFormPage):
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('simulate_callback')
509    grok.require('waeup.payApplicant')
510
511    # This update method simulates a valid callback und must be
512    # neutralized in the customization package.
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    prefix = 'form'
577
578    def render(self):
579        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
580            view=self)
581        self.response.setHeader(
582            'Content-Type', 'application/pdf')
583        return pdfstream
584
585def handle_img_upload(upload, context, view):
586    """Handle upload of applicant image.
587
588    Returns `True` in case of success or `False`.
589
590    Please note that file pointer passed in (`upload`) most probably
591    points to end of file when leaving this function.
592    """
593    size = file_size(upload)
594    if size > MAX_UPLOAD_SIZE:
595        view.flash(_('Uploaded image is too big!'))
596        return False
597    dummy, ext = os.path.splitext(upload.filename)
598    ext.lower()
599    if ext != '.jpg':
600        view.flash(_('jpg file extension expected.'))
601        return False
602    upload.seek(0) # file pointer moved when determining size
603    store = getUtility(IExtFileStore)
604    file_id = IFileStoreNameChooser(context).chooseName()
605    store.createFile(file_id, upload)
606    return True
607
608class ApplicantManageFormPage(KofaEditFormPage):
609    """A full edit view for applicant data.
610    """
611    grok.context(IApplicant)
612    grok.name('manage')
613    grok.require('waeup.manageApplication')
614    form_fields = grok.AutoFields(IApplicant)
615    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
616    form_fields['student_id'].for_display = True
617    form_fields['applicant_id'].for_display = True
618    form_fields['phone'].custom_widget = PhoneWidget
619    grok.template('applicanteditpage')
620    manage_applications = True
621    pnav = 3
622    display_actions = [[_('Save'), _('Final Submit')],
623        [_('Add online payment ticket'),_('Remove selected tickets')]]
624
625    def update(self):
626        datepicker.need() # Enable jQuery datepicker in date fields.
627        warning.need()
628        super(ApplicantManageFormPage, self).update()
629        self.wf_info = IWorkflowInfo(self.context)
630        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
631        self.passport_changed = None
632        upload = self.request.form.get('form.passport', None)
633        if upload:
634            # We got a fresh upload
635            self.passport_changed = handle_img_upload(
636                upload, self.context, self)
637        return
638
639    @property
640    def label(self):
641        container_title = self.context.__parent__.title
642        return _('${a} Application Form ${b}', mapping = {
643            'a':container_title, 'b':self.context.application_number})
644
645    def getTransitions(self):
646        """Return a list of dicts of allowed transition ids and titles.
647
648        Each list entry provides keys ``name`` and ``title`` for
649        internal name and (human readable) title of a single
650        transition.
651        """
652        allowed_transitions = self.wf_info.getManualTransitions()
653        return [dict(name='', title=_('No transition'))] +[
654            dict(name=x, title=y) for x, y in allowed_transitions]
655
656    @action(_('Save'), style='primary')
657    def save(self, **data):
658        form = self.request.form
659        password = form.get('password', None)
660        password_ctl = form.get('control_password', None)
661        if password:
662            validator = getUtility(IPasswordValidator)
663            errors = validator.validate_password(password, password_ctl)
664            if errors:
665                self.flash( ' '.join(errors))
666                return
667        if self.passport_changed is False:  # False is not None!
668            return # error during image upload. Ignore other values
669        changed_fields = self.applyData(self.context, **data)
670        # Turn list of lists into single list
671        if changed_fields:
672            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
673        else:
674            changed_fields = []
675        if self.passport_changed:
676            changed_fields.append('passport')
677        if password:
678            # Now we know that the form has no errors and can set password ...
679            IUserAccount(self.context).setPassword(password)
680            changed_fields.append('password')
681        fields_string = ' + '.join(changed_fields)
682        trans_id = form.get('transition', None)
683        if trans_id:
684            self.wf_info.fireTransition(trans_id)
685        self.flash(_('Form has been saved.'))
686        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
687        if fields_string:
688            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
689        return
690
691    def unremovable(self, ticket):
692        return False
693
694    # This method is also used by the ApplicantEditFormPage
695    def delPaymentTickets(self, **data):
696        form = self.request.form
697        if form.has_key('val_id'):
698            child_id = form['val_id']
699        else:
700            self.flash(_('No payment selected.'))
701            self.redirect(self.url(self.context))
702            return
703        if not isinstance(child_id, list):
704            child_id = [child_id]
705        deleted = []
706        for id in child_id:
707            # Applicants are not allowed to remove used payment tickets
708            if not self.unremovable(self.context[id]):
709                try:
710                    del self.context[id]
711                    deleted.append(id)
712                except:
713                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
714                            id, sys.exc_info()[0], sys.exc_info()[1]))
715        if len(deleted):
716            self.flash(_('Successfully removed: ${a}',
717                mapping = {'a':', '.join(deleted)}))
718            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
719            self.context.loggerInfo(
720                ob_class, 'removed: % s' % ', '.join(deleted))
721        return
722
723    # We explicitely want the forms to be validated before payment tickets
724    # can be created. If no validation is requested, use
725    # 'validator=NullValidator' in the action directive
726    @action(_('Add online payment ticket'))
727    def addPaymentTicket(self, **data):
728        self.redirect(self.url(self.context, '@@addafp'))
729        return
730
731    @jsaction(_('Remove selected tickets'))
732    def removePaymentTickets(self, **data):
733        self.delPaymentTickets(**data)
734        self.redirect(self.url(self.context) + '/@@manage')
735        return
736
737class ApplicantEditFormPage(ApplicantManageFormPage):
738    """An applicant-centered edit view for applicant data.
739    """
740    grok.context(IApplicantEdit)
741    grok.name('edit')
742    grok.require('waeup.handleApplication')
743    form_fields = grok.AutoFields(IApplicantEdit).omit(
744        'locked', 'course_admitted', 'student_id',
745        'screening_score', 'reg_number'
746        )
747    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
748    form_fields['phone'].custom_widget = PhoneWidget
749    form_fields['applicant_id'].for_display = True
750    grok.template('applicanteditpage')
751    manage_applications = False
752
753    @property
754    def display_actions(self):
755        state = IWorkflowState(self.context).getState()
756        if state == INITIALIZED:
757            actions = [[],[]]
758        elif state == STARTED:
759            actions = [[_('Save')],
760                [_('Add online payment ticket'),_('Remove selected tickets')]]
761        elif state == PAID:
762            actions = [[_('Save'), _('Final Submit')],
763                [_('Remove selected tickets')]]
764        else:
765            actions = [[],[]]
766        return actions
767
768    def unremovable(self, ticket):
769        state = IWorkflowState(self.context).getState()
770        return ticket.r_code or state in (INITIALIZED, SUBMITTED)
771
772    def emit_lock_message(self):
773        self.flash(_('The requested form is locked (read-only).'))
774        self.redirect(self.url(self.context))
775        return
776
777    def update(self):
778        if self.context.locked:
779            self.emit_lock_message()
780            return
781        super(ApplicantEditFormPage, self).update()
782        return
783
784    def dataNotComplete(self):
785        store = getUtility(IExtFileStore)
786        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
787            return _('No passport picture uploaded.')
788        if not self.request.form.get('confirm_passport', False):
789            return _('Passport picture confirmation box not ticked.')
790        return False
791
792    # We explicitely want the forms to be validated before payment tickets
793    # can be created. If no validation is requested, use
794    # 'validator=NullValidator' in the action directive
795    @action(_('Add online payment ticket'))
796    def addPaymentTicket(self, **data):
797        self.redirect(self.url(self.context, '@@addafp'))
798        return
799
800    @jsaction(_('Remove selected tickets'))
801    def removePaymentTickets(self, **data):
802        self.delPaymentTickets(**data)
803        self.redirect(self.url(self.context) + '/@@edit')
804        return
805
806    @action(_('Save'), style='primary')
807    def save(self, **data):
808        if self.passport_changed is False:  # False is not None!
809            return # error during image upload. Ignore other values
810        self.applyData(self.context, **data)
811        self.flash('Form has been saved.')
812        return
813
814    @action(_('Final Submit'))
815    def finalsubmit(self, **data):
816        if self.passport_changed is False:  # False is not None!
817            return # error during image upload. Ignore other values
818        if self.dataNotComplete():
819            self.flash(self.dataNotComplete())
820            return
821        self.applyData(self.context, **data)
822        state = IWorkflowState(self.context).getState()
823        # This shouldn't happen, but the application officer
824        # might have forgotten to lock the form after changing the state
825        if state != PAID:
826            self.flash(_('This form cannot be submitted. Wrong state!'))
827            return
828        IWorkflowInfo(self.context).fireTransition('submit')
829        self.context.application_date = datetime.now()
830        self.context.locked = True
831        self.flash(_('Form has been submitted.'))
832        self.redirect(self.url(self.context))
833        return
834
835class PassportImage(grok.View):
836    """Renders the passport image for applicants.
837    """
838    grok.name('passport.jpg')
839    grok.context(IApplicant)
840    grok.require('waeup.viewApplication')
841
842    def render(self):
843        # A filename chooser turns a context into a filename suitable
844        # for file storage.
845        image = getUtility(IExtFileStore).getFileByContext(self.context)
846        self.response.setHeader(
847            'Content-Type', 'image/jpeg')
848        if image is None:
849            # show placeholder image
850            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
851        return image
852
853class ApplicantRegistrationPage(KofaAddFormPage):
854    """Captcha'd registration page for applicants.
855    """
856    grok.context(IApplicantsContainer)
857    grok.name('register')
858    grok.require('waeup.Anonymous')
859    grok.template('applicantregister')
860    form_fields = grok.AutoFields(IApplicantEdit).select(
861        'firstname', 'middlename', 'lastname', 'email', 'phone')
862    form_fields['phone'].custom_widget = PhoneWidget
863
864    @property
865    def label(self):
866        return _('Register for ${a} Application',
867            mapping = {'a':self.context.title})
868
869    def update(self):
870        # Check if application has started ...
871        if not self.context.startdate or self.context.startdate > date.today():
872            self.flash(_('Application has not yet started.'))
873            self.redirect(self.url(self.context))
874            return
875        # ... or ended
876        if not self.context.enddate or self.context.enddate < date.today():
877            self.flash(_('Application has ended.'))
878            self.redirect(self.url(self.context))
879            return
880        # Handle captcha
881        self.captcha = getUtility(ICaptchaManager).getCaptcha()
882        self.captcha_result = self.captcha.verify(self.request)
883        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
884        return
885
886    @action(_('Get login credentials'), style='primary')
887    def register(self, **data):
888        if not self.captcha_result.is_valid:
889            # captcha will display error messages automatically.
890            # No need to flash something.
891            return
892        # Add applicant and create password
893        applicant = createObject(u'waeup.Applicant')
894        self.applyData(applicant, **data)
895        self.context.addApplicant(applicant)
896        kofa_utils = getUtility(IKofaUtils)
897        password = kofa_utils.genPassword()
898        IUserAccount(applicant).setPassword(password)
899        # Send email with credentials
900        login_url = self.url(grok.getSite(), 'login')
901        msg = _('You have successfully been registered for the')
902        if kofa_utils.sendCredentials(IUserAccount(applicant),
903            password, login_url, msg):
904            self.redirect(self.url(self.context, 'registration_complete',
905                                   data = dict(email=applicant.email)))
906            return
907        else:
908            self.flash(_('Email could not been sent. Please retry later.'))
909        return
910
911class ApplicantRegistrationEmailSent(KofaPage):
912    """Landing page after successful registration.
913    """
914    grok.name('registration_complete')
915    grok.require('waeup.Public')
916    grok.template('applicantregemailsent')
917    label = _('Your registration was successful.')
918
919    def update(self, email=None):
920        self.email = email
921        return
Note: See TracBrowser for help on using the repository browser.