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

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

Define formatDatetime (for displaying datetime objects in data tables) centrally in layout.py.

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