source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py @ 7255

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

Remove unused imports.

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