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

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

Add more fields to IStudentStudyCourse and corresponding vocabs (work in progress) + some other minor changes.

File size: 30.2 KB
RevLine 
[5273]1##
2## browser.py
3## Login : <uli@pu.smp.net>
[6153]4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
[5273]5## $Id$
[6078]6##
[6063]7## Copyright (C) 2010 Uli Fouquet & Henrik Bettermann
[5273]8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
[6078]12##
[5273]13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
[6078]17##
[5273]18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
[5824]22"""UI components for basic applicants and related components.
[5273]23"""
[6082]24import sys
[5273]25import grok
26
[6305]27from datetime import datetime
[6081]28from zope.formlib.widget import CustomWidgetFactory
[6358]29from zope.formlib.form import setUpEditWidgets
[5937]30from zope.securitypolicy.interfaces import IPrincipalRoleManager
[6153]31from zope.traversing.browser import absoluteURL
[6081]32
[6303]33from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6357]34from reportlab.pdfgen import canvas
[6364]35from reportlab.lib.units import cm
[6390]36from reportlab.lib.pagesizes import A4
[6364]37from reportlab.lib.styles import getSampleStyleSheet
38from reportlab.platypus import (Frame, Paragraph, Image,
39    Table, Spacer)
40from reportlab.platypus.tables import TableStyle
[6357]41
[6469]42from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
43from waeup.sirp.accesscodes.workflow import USED
[5273]44from waeup.sirp.browser import (
[6321]45    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
[6081]46from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6103]47from waeup.sirp.browser.layout import NullValidator
[6321]48from waeup.sirp.browser.pages import add_local_role, del_local_roles
[6013]49from waeup.sirp.browser.resources import datepicker, tabs, datatable
[6153]50from waeup.sirp.browser.viewlets import (
51    ManageActionButton, PrimaryNavTab, LeftSidebarLink
52    )
[6081]53from waeup.sirp.image.browser.widget import (
54    ThumbnailWidget, EncodingImageFileWidget,
[5822]55    )
[6598]56from waeup.sirp.image.image import createWAeUPImageFile
[6184]57from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
[6321]58from waeup.sirp.permissions import get_users_with_local_roles
[6254]59from waeup.sirp.university.interfaces import ICertificate
[6054]60from waeup.sirp.widgets.datewidget import (
61    FriendlyDateWidget, FriendlyDateDisplayWidget)
[6084]62from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
[5303]63from waeup.sirp.widgets.objectwidget import (
[5301]64    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
[6153]65from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
[6081]66from waeup.sirp.applicants.interfaces import (
[6321]67    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
68    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
[5686]69    )
[6353]70from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
[5320]71
[5273]72results_widget = CustomWidgetFactory(
[5301]73    WAeUPObjectWidget, ResultEntry)
[5273]74
75results_display_widget = CustomWidgetFactory(
[5301]76    WAeUPObjectDisplayWidget, ResultEntry)
[5273]77
[6067]78class ApplicantsRootPage(WAeUPPage):
[5822]79    grok.context(IApplicantsRoot)
80    grok.name('index')
[6153]81    grok.require('waeup.Public')
[5822]82    title = 'Applicants'
[6069]83    label = 'Application Section'
[5843]84    pnav = 3
[6012]85
86    def update(self):
[6067]87        super(ApplicantsRootPage, self).update()
[6012]88        datatable.need()
89        return
90
[5828]91class ManageApplicantsRootActionButton(ManageActionButton):
92    grok.context(IApplicantsRoot)
[6067]93    grok.view(ApplicantsRootPage)
[6198]94    grok.require('waeup.manageApplications')
[6069]95    text = 'Manage application section'
[5828]96
[6069]97class ApplicantsRootManageFormPage(WAeUPEditFormPage):
[5828]98    grok.context(IApplicantsRoot)
99    grok.name('manage')
[6107]100    grok.template('applicantsrootmanagepage')
[6069]101    title = 'Applicants'
102    label = 'Manage application section'
[5843]103    pnav = 3
[6198]104    grok.require('waeup.manageApplications')
[6069]105    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
[6184]106    tabtwoactions1 = ['Remove selected local roles']
107    tabtwoactions2 = ['Add local role']
[6069]108    subunits = 'Applicants Containers'
[6078]109
[6069]110    def update(self):
111        tabs.need()
[6108]112        datatable.need()
[6069]113        return super(ApplicantsRootManageFormPage, self).update()
[5828]114
[6184]115    def getLocalRoles(self):
116        roles = ILocalRolesAssignable(self.context)
117        return roles()
118
119    def getUsers(self):
120        """Get a list of all users.
121        """
122        for key, val in grok.getSite()['users'].items():
123            url = self.url(val)
124            yield(dict(url=url, name=key, val=val))
125
126    def getUsersWithLocalRoles(self):
127        return get_users_with_local_roles(self.context)
128
[6069]129    # ToDo: Show warning message before deletion
130    @grok.action('Remove selected')
131    def delApplicantsContainers(self, **data):
132        form = self.request.form
133        child_id = form['val_id']
134        if not isinstance(child_id, list):
135            child_id = [child_id]
136        deleted = []
137        for id in child_id:
138            try:
139                del self.context[id]
140                deleted.append(id)
141            except:
142                self.flash('Could not delete %s: %s: %s' % (
143                        id, sys.exc_info()[0], sys.exc_info()[1]))
144        if len(deleted):
145            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6078]146        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
147        return
[5828]148
[6069]149    @grok.action('Add applicants container', validator=NullValidator)
150    def addApplicantsContainer(self, **data):
151        self.redirect(self.url(self.context, '@@add'))
[6078]152        return
153
[6069]154    @grok.action('Cancel', validator=NullValidator)
155    def cancel(self, **data):
156        self.redirect(self.url(self.context))
[6078]157        return
158
[6184]159    @grok.action('Add local role', validator=NullValidator)
160    def addLocalRole(self, **data):
161        return add_local_role(self,2, **data)
162
163    @grok.action('Remove selected local roles')
164    def delLocalRoles(self, **data):
165        return del_local_roles(self,2,**data)
166
[6069]167class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
[5822]168    grok.context(IApplicantsRoot)
[6198]169    grok.require('waeup.manageApplications')
[5822]170    grok.name('add')
[6107]171    grok.template('applicantscontaineraddpage')
[6069]172    title = 'Applicants'
173    label = 'Add applicants container'
[5843]174    pnav = 3
[6078]175
[6103]176    form_fields = grok.AutoFields(
177        IApplicantsContainerAdd).omit('code').omit('title')
[6083]178    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
179    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6078]180
[6083]181    def update(self):
182        datepicker.need() # Enable jQuery datepicker in date fields.
183        return super(ApplicantsContainerAddFormPage, self).update()
184
[6069]185    @grok.action('Add applicants container')
186    def addApplicantsContainer(self, **data):
[6103]187        year = data['year']
188        code = u'%s%s' % (data['prefix'], year)
189        prefix = application_types_vocab.getTerm(data['prefix'])
190        title = u'%s %s/%s' % (prefix.title, year, year + 1)
[6087]191        if code in self.context.keys():
[6105]192            self.flash(
193                'An applicants container for the same application '
194                'type and entrance year exists already in the database.')
[5822]195            return
196        # Add new applicants container...
[6083]197        provider = data['provider'][1]
[5822]198        container = provider.factory()
[6069]199        self.applyData(container, **data)
[6087]200        container.code = code
201        container.title = title
202        self.context[code] = container
[6105]203        self.flash('Added: "%s".' % code)
[6069]204        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
[5822]205        return
[6078]206
[6103]207    @grok.action('Cancel', validator=NullValidator)
[6069]208    def cancel(self, **data):
[6103]209        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
[6078]210
[5845]211class ApplicantsRootBreadcrumb(Breadcrumb):
212    """A breadcrumb for applicantsroot.
213    """
214    grok.context(IApplicantsRoot)
[6654]215    title = u'Applicants'
[6078]216
[5845]217class ApplicantsContainerBreadcrumb(Breadcrumb):
218    """A breadcrumb for applicantscontainers.
219    """
220    grok.context(IApplicantsContainer)
[6319]221
[6153]222class ApplicantBreadcrumb(Breadcrumb):
223    """A breadcrumb for applicants.
224    """
225    grok.context(IApplicant)
[6319]226
[6153]227    @property
228    def title(self):
229        """Get a title for a context.
230        """
231        return self.context.access_code
[5828]232
233class ApplicantsTab(PrimaryNavTab):
[6153]234    """Applicants tab in primary navigation.
[5828]235    """
[6078]236
[5828]237    grok.context(IWAeUPObject)
238    grok.order(3)
[6336]239    grok.require('waeup.Public')
[5828]240    grok.template('primarynavtab')
241
[5843]242    pnav = 3
[5828]243    tab_title = u'Applicants'
244
245    @property
246    def link_target(self):
247        return self.view.application_url('applicants')
248
[6029]249class ApplicantsContainerPage(WAeUPDisplayFormPage):
[5830]250    """The standard view for regular applicant containers.
251    """
252    grok.context(IApplicantsContainer)
253    grok.name('index')
[6153]254    grok.require('waeup.Public')
[6029]255    grok.template('applicantscontainerpage')
[5850]256    pnav = 3
[6053]257
[6105]258    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
[6054]259    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
260    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
[6084]261    form_fields['description'].custom_widget = ReSTDisplayWidget
[6053]262
[5837]263    @property
264    def title(self):
[6087]265        return "Applicants Container: %s" % self.context.title
[5837]266
267    @property
268    def label(self):
[6087]269        return self.context.title
[5830]270
[6107]271class ApplicantsContainerManageActionButton(ManageActionButton):
[6336]272    grok.order(1)
[5832]273    grok.context(IApplicantsContainer)
274    grok.view(ApplicantsContainerPage)
[6198]275    grok.require('waeup.manageApplications')
[6070]276    text = 'Manage applicants container'
[5832]277
[6336]278class LoginApplicantActionButton(ManageActionButton):
279    grok.order(2)
280    grok.context(IApplicantsContainer)
281    grok.view(ApplicantsContainerPage)
282    grok.require('waeup.Anonymous')
283    icon = 'login.png'
284    text = 'Login for applicants'
285    target = 'login'
[5832]286
[6107]287class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
[5837]288    grok.context(IApplicantsContainer)
[5850]289    grok.name('manage')
[6107]290    grok.template('applicantscontainermanagepage')
[6105]291    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
292    taboneactions = ['Save','Cancel']
293    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
[6184]294    tabthreeactions1 = ['Remove selected local roles']
295    tabthreeactions2 = ['Add local role']
[5844]296    # Use friendlier date widget...
[6054]297    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
298    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6198]299    grok.require('waeup.manageApplications')
[5850]300
301    @property
302    def title(self):
[6087]303        return "Applicants Container: %s" % self.context.title
[6078]304
[5850]305    @property
306    def label(self):
[6087]307        return 'Manage applicants container'
[5850]308
[5845]309    pnav = 3
[5837]310
311    def update(self):
[5850]312        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]313        tabs.need()
[6015]314        datatable.need()  # Enable jQurey datatables for contents listing
[6107]315        return super(ApplicantsContainerManageFormPage, self).update()
[5837]316
[6184]317    def getLocalRoles(self):
318        roles = ILocalRolesAssignable(self.context)
319        return roles()
320
321    def getUsers(self):
322        """Get a list of all users.
323        """
324        for key, val in grok.getSite()['users'].items():
325            url = self.url(val)
326            yield(dict(url=url, name=key, val=val))
327
328    def getUsersWithLocalRoles(self):
329        return get_users_with_local_roles(self.context)
330
[5850]331    @grok.action('Save')
[5837]332    def apply(self, **data):
333        self.applyData(self.context, **data)
334        self.flash('Data saved.')
335        return
[6078]336
[6105]337    # ToDo: Show warning message before deletion
338    @grok.action('Remove selected')
339    def delApplicant(self, **data):
[6189]340        form = self.request.form
341        if form.has_key('val_id'):
342            child_id = form['val_id']
343        else:
344            self.flash('No applicant selected!')
345            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
346            return
347        if not isinstance(child_id, list):
348            child_id = [child_id]
349        deleted = []
350        for id in child_id:
351            try:
352                del self.context[id]
353                deleted.append(id)
354            except:
355                self.flash('Could not delete %s: %s: %s' % (
356                        id, sys.exc_info()[0], sys.exc_info()[1]))
357        if len(deleted):
358            self.flash('Successfully removed: %s' % ', '.join(deleted))
359        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
360        return
[6105]361
362    @grok.action('Add applicant', validator=NullValidator)
363    def addApplicant(self, **data):
[6327]364        self.redirect(self.url(self.context, 'addapplicant'))
365        return
[6105]366
367    @grok.action('Cancel', validator=NullValidator)
[5837]368    def cancel(self, **data):
369        self.redirect(self.url(self.context))
370        return
[5886]371
[6184]372    @grok.action('Add local role', validator=NullValidator)
373    def addLocalRole(self, **data):
374        return add_local_role(self,3, **data)
[6105]375
[6184]376    @grok.action('Remove selected local roles')
377    def delLocalRoles(self, **data):
378        return del_local_roles(self,3,**data)
379
[5886]380class LoginApplicant(WAeUPPage):
381    grok.context(IApplicantsContainer)
382    grok.name('login')
[6153]383    grok.require('waeup.Public')
[5886]384
[6110]385    @property
386    def title(self):
387        return u"Applicant Login: %s" % self.context.title
[6078]388
[5886]389    @property
390    def label(self):
[6724]391        return u"Login for '%s' applicants only" % self.context.title
[5886]392
393    pnav = 3
[6319]394
[6110]395    @property
396    def ac_prefix(self):
397        return self.context.ac_prefix
[6078]398
[5896]399    def update(self, SUBMIT=None):
400        self.ac_series = self.request.form.get('form.ac_series', None)
401        self.ac_number = self.request.form.get('form.ac_number', None)
[5886]402        if SUBMIT is None:
403            return
[5894]404        if self.request.principal.id == 'zope.anybody':
[6105]405            self.flash('Entered credentials are invalid.')
[5886]406            return
[5894]407        if not IApplicantPrincipal.providedBy(self.request.principal):
408            # Don't care if user is already authenticated as non-applicant
409            return
[6377]410
411        # From here we handle an applicant (not an officer browsing)
[5905]412        pin = self.request.principal.access_code
[6375]413
414        # Mark pin as used (this also fires a pin related transition)
[6470]415        if get_access_code(pin).state == USED:
[6469]416            pass
417        else:
[6471]418            comment = u"AC invalidated"
[6469]419            # Here we know that the ac is in state initialized so we do not
420            # expect an exception
[6471]421            invalidate_accesscode(pin,comment)
[6375]422
[6377]423        if not pin in self.context.keys():
424            # Create applicant record
425            applicant = Applicant()
426            applicant.access_code = pin
427            self.context[pin] = applicant
[6359]428
[6377]429        role_manager = IPrincipalRoleManager(self.context[pin])
[6394]430        role_manager.assignRoleToPrincipal(
431            'waeup.local.ApplicationOwner', self.request.principal.id)
[6359]432
[6394]433        # Assign current principal the PortalUser role
[6397]434        role_manager = IPrincipalRoleManager(grok.getSite())
[6394]435        role_manager.assignRoleToPrincipal(
436            'waeup.PortalUser', self.request.principal.id)
[6377]437
438        # Mark application as started
439        if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
440            IWorkflowInfo(self.context[pin]).fireTransition('start')
441
[5937]442        self.redirect(self.url(self.context[pin], 'edit'))
[5886]443        return
[6319]444
[6327]445class ApplicantAddFormPage(WAeUPAddFormPage):
[6622]446    """Add-form to add an applicant.
[6327]447    """
448    grok.context(IApplicantsContainer)
449    grok.require('waeup.manageApplications')
450    grok.name('addapplicant')
451    grok.template('applicantaddpage')
452    title = 'Applicants'
453    label = 'Add applicant'
454    pnav = 3
455
456    @property
457    def title(self):
458        return "Applicants Container: %s" % self.context.title
459
460    @property
461    def ac_prefix(self):
462        return self.context.ac_prefix
463
464    @grok.action('Create application record')
465    def addApplicant(self, **data):
466        ac_series = self.request.form.get('form.ac_series', None)
467        ac_number = self.request.form.get('form.ac_number', None)
468        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
[6459]469        if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
[6383]470            self.flash('%s is not a valid access code.' % pin)
471            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
472            return
473        else:
[6327]474            # Create applicant record
475            applicant = Applicant()
476            applicant.access_code = pin
477            self.context[pin] = applicant
478        self.redirect(self.url(self.context[pin], 'edit'))
479        return
480
[6396]481class AccessCodeViewLink(LeftSidebarLink):
[6153]482    grok.order(1)
483    grok.require('waeup.Public')
[6396]484    icon = 'actionicon_view.png'
485    title = 'View Record'
486    target = '/@@index'
[5886]487
[6396]488    @property
489    def url(self):
[6153]490        if not IApplicantPrincipal.providedBy(self.request.principal):
491            return ''
492        access_code = getattr(self.request.principal,'access_code',None)
493        if access_code:
494            applicant_object = get_applicant_data(access_code)
[6396]495            return absoluteURL(applicant_object, self.request) + self.target
[6595]496        #else:
497        #    return ''
[6153]498
[6396]499class AccessCodeEditLink(AccessCodeViewLink):
500    grok.order(2)
501    grok.require('waeup.Public')
502    icon = 'actionicon_modify.png'
503    title = 'Edit Record'
504    target = '/@@edit'
505
506    @property
507    def url(self):
508        if not IApplicantPrincipal.providedBy(self.request.principal):
509            return ''
510        access_code = getattr(self.request.principal,'access_code',None)
511        if access_code:
512            applicant_object = get_applicant_data(access_code)
513            if applicant_object.locked:
514                return ''
515            return absoluteURL(applicant_object, self.request) + self.target
[6595]516        #else:
517        #    return ''
[6396]518
519class AccessCodeSlipLink(AccessCodeViewLink):
520    grok.order(3)
521    grok.require('waeup.Public')
522    icon = 'actionicon_pdf.png'
523    title = 'Download Slip'
524    target = '/application_slip.pdf'
525
[5273]526class DisplayApplicant(WAeUPDisplayFormPage):
527    grok.context(IApplicant)
528    grok.name('index')
[6198]529    grok.require('waeup.handleApplication')
[6320]530    form_fields = grok.AutoFields(IApplicant).omit(
531        'locked').omit('course_admitted')
[6196]532    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
[5919]533    form_fields['passport'].custom_widget = ThumbnailWidget
[6054]534    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[5273]535    label = 'Applicant'
[6254]536    grok.template('form_display')
[5843]537    pnav = 3
[5273]538
[6196]539    @property
540    def title(self):
[6465]541        if self.request.principal.title == 'Applicant':
542            return u'Your Application Record'
[6196]543        return '%s' % self.context.access_code
544
545    @property
546    def label(self):
547        container_title = self.context.__parent__.title
548        return '%s Application Record' % container_title
549
[6254]550    def getCourseAdmitted(self):
[6355]551        """Return link, title and code in html format to the certificate
552           admitted.
[6351]553        """
[6254]554        course_admitted = self.context.course_admitted
555        if ICertificate.providedBy(course_admitted):
556            url = self.url(course_admitted)
557            title = course_admitted.title
558            code = course_admitted.code
[6366]559            return '<a href="%s">%s - %s</a>' %(url,code,title)
[6457]560        return ''
[6254]561
[6358]562class PDFActionButton(ManageActionButton):
563    grok.context(IApplicant)
[6396]564    #grok.view(DisplayApplicant)
565    grok.require('waeup.manageApplications')
[6358]566    icon = 'actionicon_pdf.png'
[6367]567    text = 'Download application slip'
[6358]568    target = 'application_slip.pdf'
569
570class ExportPDFPage(grok.View):
571    """Deliver a PDF slip of the context.
572    """
573    grok.context(IApplicant)
574    grok.name('application_slip.pdf')
575    grok.require('waeup.handleApplication')
576    form_fields = grok.AutoFields(IApplicant).omit(
577        'locked').omit('course_admitted')
578    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
579    prefix = 'form'
580
[6363]581    @property
582    def label(self):
583        container_title = self.context.__parent__.title
584        return '%s Application Record' % container_title
585
[6358]586    def getCourseAdmitted(self):
587        """Return title and code in html format to the certificate
588           admitted.
589        """
590        course_admitted = self.context.course_admitted
591        if ICertificate.providedBy(course_admitted):
592            title = course_admitted.title
593            code = course_admitted.code
[6365]594            return '%s - %s' %(code,title)
[6462]595        return ''
[6358]596
597    def setUpWidgets(self, ignore_request=False):
598        self.adapters = {}
599        self.widgets = setUpEditWidgets(
600            self.form_fields, self.prefix, self.context, self.request,
601            adapters=self.adapters, for_display=True,
602            ignore_request=ignore_request
603            )
604
605    def render(self):
[6364]606        # (0,0),(-1,-1) = whole table
607        # (0,0),(0,-1) = first column
608        # (-1,0),(-1,-1) = last column
609        # (0,0),(-1,0) = first row
610        # (0,-1),(-1,-1) = last row
611        SLIP_STYLE = TableStyle(
612            [('VALIGN',(0,0),(-1,-1),'TOP')]
613            )
[6358]614
615        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
[6364]616        pdf.setTitle(self.label)
617        pdf.setSubject('Application')
618        pdf.setAuthor('%s (%s)' % (self.request.principal.title,
619            self.request.principal.id))
620        pdf.setCreator('WAeUP SIRP')
[6358]621        width, height = A4
622        style = getSampleStyleSheet()
[6365]623        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
[6363]624
[6358]625        story = []
[6365]626        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
[6363]627        header_title = getattr(grok.getSite(), 'name', u'Sample University')
628        story.append(Paragraph(header_title, style["Heading1"]))
629        frame_header.addFromList(story,pdf)
630
631        story = []
[6365]632        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
[6364]633        story.append(Paragraph(self.label, style["Heading2"]))
634        #story.append(HRFlowable())
635        story.append(Spacer(1, 18))
636        for msg in self.context.history.messages:
637            f_msg = '<font face="Courier" size=10>%s</font>' % msg
638            story.append(Paragraph(f_msg, style["Normal"]))
[6363]639        story.append(Spacer(1, 24))
[6358]640        self.setUpWidgets()
[6363]641        data = []
[6358]642        for widget in self.widgets:
[6462]643            f_label = '<font size=12>%s</font>:' % widget.label.strip()
[6363]644            f_label = Paragraph(f_label, style["Normal"])
[6358]645            if widget.name != 'form.passport':
[6363]646                f_text = '<font size=12>%s</font>' % widget()
647                f_text = Paragraph(f_text, style["Normal"])
648                data.append([f_label,f_text])
[6360]649            else:
650                filename = widget._data.file.name
[6364]651                im = Image(filename,width=4*cm, height=3*cm,kind='bound')
[6363]652                data.append([f_label,im])
[6462]653        f_label = '<font size=12>Admitted Course of Study:</font>'
[6363]654        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
655        f_label = Paragraph(f_label, style["Normal"])
656        f_text = Paragraph(f_text, style["Normal"])
657        data.append([f_label,f_text])
[6364]658        table = Table(data,style=SLIP_STYLE)
[6363]659        story.append(table)
660        frame_body.addFromList(story,pdf)
661
[6364]662        story = []
[6365]663        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
[6364]664        timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
665        f_text = '<font size=10>%s</font>' % timestamp
666        story.append(Paragraph(f_text, style["Normal"]))
667        frame_footer.addFromList(story,pdf)
668
[6358]669        self.response.setHeader(
670            'Content-Type', 'application/pdf')
671        return pdf.getpdfdata()
672
[6383]673class ApplicantManageActionButton(ManageActionButton):
[6198]674    grok.context(IApplicant)
675    grok.view(DisplayApplicant)
676    grok.require('waeup.manageApplications')
[6383]677    text = 'Manage application record'
[6198]678    target = 'edit_full'
679
[6196]680class EditApplicantFull(WAeUPEditFormPage):
681    """A full edit view for applicant data.
682    """
683    grok.context(IApplicant)
684    grok.name('edit_full')
[6198]685    grok.require('waeup.manageApplications')
[6476]686    form_fields = grok.AutoFields(IApplicant)
[6196]687    form_fields['passport'].custom_widget = EncodingImageFileWidget
688    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
689    grok.template('form_edit')
[6322]690    manage_applications = True
[6196]691    pnav = 3
692
693    def update(self):
694        datepicker.need() # Enable jQuery datepicker in date fields.
695        super(EditApplicantFull, self).update()
[6353]696        self.wf_info = IWorkflowInfo(self.context)
[6598]697        upload = self.request.form.get('form.passport', None)
698        if upload:
699            # We got a fresh upload
700            image = createWAeUPImageFile(upload.filename, upload)
701            # This would normally be totally illegal. We set context
702            # data without the complete form data being
703            # verified. Normally data is set in methods like `save`
704            # only, which is only called when all form data is correct
705            # (and if not, the context is not touched). With images
706            # and their uploads the problem then is, that the uploaded
707            # image won't be shown in a partially erranous form
708            # because the new uploaded image is still not part of the
709            # context obeject and the image widget cannot work around
710            # that (it has no access to the new data). For this reason
711            # we set the image here and not in 'save()' or other
712            # methods.
713            self.context.passport = image
714            upload.seek(0)
715            self.passport_changed = True
[6196]716        return
717
718    @property
719    def title(self):
720        return self.context.access_code
721
722    @property
723    def label(self):
724        container_title = self.context.__parent__.title
725        return '%s Application Form' % container_title
726
[6303]727    def getTransitions(self):
[6351]728        """Return a list of dicts of allowed transition ids and titles.
[6353]729
730        Each list entry provides keys ``name`` and ``title`` for
731        internal name and (human readable) title of a single
732        transition.
[6349]733        """
[6353]734        allowed_transitions = self.wf_info.getManualTransitions()
[6355]735        return [dict(name='', title='No transition')] +[
736            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]737
[6196]738    @grok.action('Save')
739    def save(self, **data):
[6475]740        changed_fields = self.applyData(self.context, **data)
741        changed_fields = changed_fields.values()
[6582]742        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
[6196]743        self.context._p_changed = True
[6303]744        form = self.request.form
745        if form.has_key('transition') and form['transition']:
[6305]746            transition_id = form['transition']
[6353]747            self.wf_info.fireTransition(transition_id)
[6196]748        self.flash('Form has been saved.')
[6475]749        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
[6644]750        if fields_string:
751            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
[6196]752        return
753
754class EditApplicantStudent(EditApplicantFull):
[5982]755    """An applicant-centered edit view for applicant data.
756    """
[6196]757    grok.context(IApplicantEdit)
[5273]758    grok.name('edit')
[6198]759    grok.require('waeup.handleApplication')
[6459]760    form_fields = grok.AutoFields(IApplicantEdit).omit(
[6476]761        'locked', 'course_admitted', 'student_id',
[6459]762        'screening_score',
763        )
[5686]764    form_fields['passport'].custom_widget = EncodingImageFileWidget
[6054]765    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6196]766    grok.template('form_edit')
[6322]767    manage_applications = False
[6465]768    title = u'Your Application Form'
[5484]769
[6322]770
[5941]771    def emitLockMessage(self):
[6105]772        self.flash('The requested form is locked (read-only).')
[5941]773        self.redirect(self.url(self.context))
774        return
[6078]775
[5686]776    def update(self):
[5941]777        if self.context.locked:
[6367]778            self.emitLockMessage()
[5941]779            return
[6040]780        datepicker.need() # Enable jQuery datepicker in date fields.
[6598]781        upload = self.request.form.get('form.passport', None)
782        if upload:
783            # We got a fresh upload
784            image = createWAeUPImageFile(upload.filename, upload)
785            # This would normally be totally illegal. We set context
786            # data without the complete form data being
787            # verified. Normally data is set in methods like `save`
788            # only, which is only called when all form data is correct
789            # (and if not, the context is not touched). With images
790            # and their uploads the problem then is, that the uploaded
791            # image won't be shown in a partially erranous form
792            # because the new uploaded image is still not part of the
793            # context obeject and the image widget cannot work around
794            # that (it has no access to the new data). For this reason
795            # we set the image here and not in 'save()' or other
796            # methods.
797            self.context.passport = image
798            upload.seek(0)
799            self.passport_changed = True
[5982]800        super(EditApplicantStudent, self).update()
[5686]801        return
[5952]802
[6196]803    def dataNotComplete(self):
[6322]804        if not self.request.form.get('confirm_passport', False):
[6196]805            return 'Passport confirmation box not ticked.'
[6595]806        #if len(self.errors) > 0:
807        #    return 'Form has errors.'
[6196]808        return False
[5952]809
[5273]810    @grok.action('Save')
811    def save(self, **data):
[6595]812        #if self.context.locked:
813        #    self.emitLockMessage()
814        #    return
[5273]815        self.applyData(self.context, **data)
816        self.context._p_changed = True
[6196]817        self.flash('Form has been saved.')
[5273]818        return
819
[5484]820    @grok.action('Final Submit')
821    def finalsubmit(self, **data):
[6595]822        #if self.context.locked:
823        #    self.emitLockMessage()
824        #    return
[5273]825        self.applyData(self.context, **data)
[5484]826        self.context._p_changed = True
[6196]827        if self.dataNotComplete():
828            self.flash(self.dataNotComplete())
[5941]829            return
[6303]830        state = IWorkflowState(self.context).getState()
[6322]831        # This shouldn't happen, but the application officer
832        # might have forgotten to lock the form after changing the state
[6303]833        if state != STARTED:
[6322]834            self.flash('This form cannot be submitted. Wrong state!')
[6303]835            return
836        IWorkflowInfo(self.context).fireTransition('submit')
[6476]837        self.context.application_date = datetime.now()
[5941]838        self.context.locked = True
[6196]839        self.flash('Form has been submitted.')
840        self.redirect(self.url(self.context))
[5273]841        return
[5941]842
[6367]843class ApplicantViewActionButton(ManageActionButton):
844    grok.context(IApplicant)
[6383]845    grok.view(EditApplicantFull)
[6396]846    grok.require('waeup.manageApplications')
[6383]847    icon = 'actionicon_view.png'
[6367]848    text = 'View application record'
[6598]849    target = 'index'
Note: See TracBrowser for help on using the repository browser.