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

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

Fix _fillCustomFields and remove trash.

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