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

Last change on this file since 6805 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
Line 
1##
2## browser.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet & Henrik Bettermann
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.
12##
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.
17##
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##
22"""UI components for basic applicants and related components.
23"""
24import sys
25import grok
26
27from datetime import datetime
28from zope.formlib.widget import CustomWidgetFactory
29from zope.formlib.form import setUpEditWidgets
30from zope.securitypolicy.interfaces import IPrincipalRoleManager
31from zope.traversing.browser import absoluteURL
32
33from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
34from reportlab.pdfgen import canvas
35from reportlab.lib.units import cm
36from reportlab.lib.pagesizes import A4
37from reportlab.lib.styles import getSampleStyleSheet
38from reportlab.platypus import (Frame, Paragraph, Image,
39    Table, Spacer)
40from reportlab.platypus.tables import TableStyle
41
42from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
43from waeup.sirp.accesscodes.workflow import USED
44from waeup.sirp.browser import (
45    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
46from waeup.sirp.browser.breadcrumbs import Breadcrumb
47from waeup.sirp.browser.layout import NullValidator
48from waeup.sirp.browser.pages import add_local_role, del_local_roles
49from waeup.sirp.browser.resources import datepicker, tabs, datatable
50from waeup.sirp.browser.viewlets import (
51    ManageActionButton, PrimaryNavTab, LeftSidebarLink
52    )
53from waeup.sirp.image.browser.widget import (
54    ThumbnailWidget, EncodingImageFileWidget,
55    )
56from waeup.sirp.image.image import createWAeUPImageFile
57from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
58from waeup.sirp.permissions import get_users_with_local_roles
59from waeup.sirp.university.interfaces import ICertificate
60from waeup.sirp.widgets.datewidget import (
61    FriendlyDateWidget, FriendlyDateDisplayWidget)
62from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
63from waeup.sirp.widgets.objectwidget import (
64    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
65from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
66from waeup.sirp.applicants.interfaces import (
67    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
68    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
69    )
70from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
71
72results_widget = CustomWidgetFactory(
73    WAeUPObjectWidget, ResultEntry)
74
75results_display_widget = CustomWidgetFactory(
76    WAeUPObjectDisplayWidget, ResultEntry)
77
78class ApplicantsRootPage(WAeUPPage):
79    grok.context(IApplicantsRoot)
80    grok.name('index')
81    grok.require('waeup.Public')
82    title = 'Applicants'
83    label = 'Application Section'
84    pnav = 3
85
86    def update(self):
87        super(ApplicantsRootPage, self).update()
88        datatable.need()
89        return
90
91class ManageApplicantsRootActionButton(ManageActionButton):
92    grok.context(IApplicantsRoot)
93    grok.view(ApplicantsRootPage)
94    grok.require('waeup.manageApplications')
95    text = 'Manage application section'
96
97class ApplicantsRootManageFormPage(WAeUPEditFormPage):
98    grok.context(IApplicantsRoot)
99    grok.name('manage')
100    grok.template('applicantsrootmanagepage')
101    title = 'Applicants'
102    label = 'Manage application section'
103    pnav = 3
104    grok.require('waeup.manageApplications')
105    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
106    tabtwoactions1 = ['Remove selected local roles']
107    tabtwoactions2 = ['Add local role']
108    subunits = 'Applicants Containers'
109
110    def update(self):
111        tabs.need()
112        datatable.need()
113        return super(ApplicantsRootManageFormPage, self).update()
114
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
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))
146        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
147        return
148
149    @grok.action('Add applicants container', validator=NullValidator)
150    def addApplicantsContainer(self, **data):
151        self.redirect(self.url(self.context, '@@add'))
152        return
153
154    @grok.action('Cancel', validator=NullValidator)
155    def cancel(self, **data):
156        self.redirect(self.url(self.context))
157        return
158
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
167class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
168    grok.context(IApplicantsRoot)
169    grok.require('waeup.manageApplications')
170    grok.name('add')
171    grok.template('applicantscontaineraddpage')
172    title = 'Applicants'
173    label = 'Add applicants container'
174    pnav = 3
175
176    form_fields = grok.AutoFields(
177        IApplicantsContainerAdd).omit('code').omit('title')
178    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
179    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
180
181    def update(self):
182        datepicker.need() # Enable jQuery datepicker in date fields.
183        return super(ApplicantsContainerAddFormPage, self).update()
184
185    @grok.action('Add applicants container')
186    def addApplicantsContainer(self, **data):
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)
191        if code in self.context.keys():
192            self.flash(
193                'An applicants container for the same application '
194                'type and entrance year exists already in the database.')
195            return
196        # Add new applicants container...
197        provider = data['provider'][1]
198        container = provider.factory()
199        self.applyData(container, **data)
200        container.code = code
201        container.title = title
202        self.context[code] = container
203        self.flash('Added: "%s".' % code)
204        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
205        return
206
207    @grok.action('Cancel', validator=NullValidator)
208    def cancel(self, **data):
209        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
210
211class ApplicantsRootBreadcrumb(Breadcrumb):
212    """A breadcrumb for applicantsroot.
213    """
214    grok.context(IApplicantsRoot)
215    title = u'Applicants'
216
217class ApplicantsContainerBreadcrumb(Breadcrumb):
218    """A breadcrumb for applicantscontainers.
219    """
220    grok.context(IApplicantsContainer)
221
222class ApplicantBreadcrumb(Breadcrumb):
223    """A breadcrumb for applicants.
224    """
225    grok.context(IApplicant)
226
227    @property
228    def title(self):
229        """Get a title for a context.
230        """
231        return self.context.access_code
232
233class ApplicantsTab(PrimaryNavTab):
234    """Applicants tab in primary navigation.
235    """
236
237    grok.context(IWAeUPObject)
238    grok.order(3)
239    grok.require('waeup.Public')
240    grok.template('primarynavtab')
241
242    pnav = 3
243    tab_title = u'Applicants'
244
245    @property
246    def link_target(self):
247        return self.view.application_url('applicants')
248
249class ApplicantsContainerPage(WAeUPDisplayFormPage):
250    """The standard view for regular applicant containers.
251    """
252    grok.context(IApplicantsContainer)
253    grok.name('index')
254    grok.require('waeup.Public')
255    grok.template('applicantscontainerpage')
256    pnav = 3
257
258    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
259    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
260    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
261    form_fields['description'].custom_widget = ReSTDisplayWidget
262
263    @property
264    def title(self):
265        return "Applicants Container: %s" % self.context.title
266
267    @property
268    def label(self):
269        return self.context.title
270
271class ApplicantsContainerManageActionButton(ManageActionButton):
272    grok.order(1)
273    grok.context(IApplicantsContainer)
274    grok.view(ApplicantsContainerPage)
275    grok.require('waeup.manageApplications')
276    text = 'Manage applicants container'
277
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'
286
287class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
288    grok.context(IApplicantsContainer)
289    grok.name('manage')
290    grok.template('applicantscontainermanagepage')
291    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
292    taboneactions = ['Save','Cancel']
293    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
294    tabthreeactions1 = ['Remove selected local roles']
295    tabthreeactions2 = ['Add local role']
296    # Use friendlier date widget...
297    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
298    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
299    grok.require('waeup.manageApplications')
300
301    @property
302    def title(self):
303        return "Applicants Container: %s" % self.context.title
304
305    @property
306    def label(self):
307        return 'Manage applicants container'
308
309    pnav = 3
310
311    def update(self):
312        datepicker.need() # Enable jQuery datepicker in date fields.
313        tabs.need()
314        datatable.need()  # Enable jQurey datatables for contents listing
315        return super(ApplicantsContainerManageFormPage, self).update()
316
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
331    @grok.action('Save')
332    def apply(self, **data):
333        self.applyData(self.context, **data)
334        self.flash('Data saved.')
335        return
336
337    # ToDo: Show warning message before deletion
338    @grok.action('Remove selected')
339    def delApplicant(self, **data):
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
361
362    @grok.action('Add applicant', validator=NullValidator)
363    def addApplicant(self, **data):
364        self.redirect(self.url(self.context, 'addapplicant'))
365        return
366
367    @grok.action('Cancel', validator=NullValidator)
368    def cancel(self, **data):
369        self.redirect(self.url(self.context))
370        return
371
372    @grok.action('Add local role', validator=NullValidator)
373    def addLocalRole(self, **data):
374        return add_local_role(self,3, **data)
375
376    @grok.action('Remove selected local roles')
377    def delLocalRoles(self, **data):
378        return del_local_roles(self,3,**data)
379
380class LoginApplicant(WAeUPPage):
381    grok.context(IApplicantsContainer)
382    grok.name('login')
383    grok.require('waeup.Public')
384
385    @property
386    def title(self):
387        return u"Applicant Login: %s" % self.context.title
388
389    @property
390    def label(self):
391        return u"Login for '%s' applicants only" % self.context.title
392
393    pnav = 3
394
395    @property
396    def ac_prefix(self):
397        return self.context.ac_prefix
398
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)
402        if SUBMIT is None:
403            return
404        if self.request.principal.id == 'zope.anybody':
405            self.flash('Entered credentials are invalid.')
406            return
407        if not IApplicantPrincipal.providedBy(self.request.principal):
408            # Don't care if user is already authenticated as non-applicant
409            return
410
411        # From here we handle an applicant (not an officer browsing)
412        pin = self.request.principal.access_code
413
414        # Mark pin as used (this also fires a pin related transition)
415        if get_access_code(pin).state == USED:
416            pass
417        else:
418            comment = u"AC invalidated"
419            # Here we know that the ac is in state initialized so we do not
420            # expect an exception
421            invalidate_accesscode(pin,comment)
422
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
428
429        role_manager = IPrincipalRoleManager(self.context[pin])
430        role_manager.assignRoleToPrincipal(
431            'waeup.local.ApplicationOwner', self.request.principal.id)
432
433        # Assign current principal the PortalUser role
434        role_manager = IPrincipalRoleManager(grok.getSite())
435        role_manager.assignRoleToPrincipal(
436            'waeup.PortalUser', self.request.principal.id)
437
438        # Mark application as started
439        if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
440            IWorkflowInfo(self.context[pin]).fireTransition('start')
441
442        self.redirect(self.url(self.context[pin], 'edit'))
443        return
444
445class ApplicantAddFormPage(WAeUPAddFormPage):
446    """Add-form to add an applicant.
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)
469        if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
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:
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
481class AccessCodeViewLink(LeftSidebarLink):
482    grok.order(1)
483    grok.require('waeup.Public')
484    icon = 'actionicon_view.png'
485    title = 'View Record'
486    target = '/@@index'
487
488    @property
489    def url(self):
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)
495            return absoluteURL(applicant_object, self.request) + self.target
496        #else:
497        #    return ''
498
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
516        #else:
517        #    return ''
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
526class DisplayApplicant(WAeUPDisplayFormPage):
527    grok.context(IApplicant)
528    grok.name('index')
529    grok.require('waeup.handleApplication')
530    form_fields = grok.AutoFields(IApplicant).omit(
531        'locked').omit('course_admitted')
532    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
533    form_fields['passport'].custom_widget = ThumbnailWidget
534    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
535    label = 'Applicant'
536    grok.template('form_display')
537    pnav = 3
538
539    @property
540    def title(self):
541        if self.request.principal.title == 'Applicant':
542            return u'Your Application Record'
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
550    def getCourseAdmitted(self):
551        """Return link, title and code in html format to the certificate
552           admitted.
553        """
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
559            return '<a href="%s">%s - %s</a>' %(url,code,title)
560        return ''
561
562class PDFActionButton(ManageActionButton):
563    grok.context(IApplicant)
564    #grok.view(DisplayApplicant)
565    grok.require('waeup.manageApplications')
566    icon = 'actionicon_pdf.png'
567    text = 'Download application slip'
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
581    @property
582    def label(self):
583        container_title = self.context.__parent__.title
584        return '%s Application Record' % container_title
585
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
594            return '%s - %s' %(code,title)
595        return ''
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):
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            )
614
615        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
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')
621        width, height = A4
622        style = getSampleStyleSheet()
623        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
624
625        story = []
626        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
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 = []
632        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
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"]))
639        story.append(Spacer(1, 24))
640        self.setUpWidgets()
641        data = []
642        for widget in self.widgets:
643            f_label = '<font size=12>%s</font>:' % widget.label.strip()
644            f_label = Paragraph(f_label, style["Normal"])
645            if widget.name != 'form.passport':
646                f_text = '<font size=12>%s</font>' % widget()
647                f_text = Paragraph(f_text, style["Normal"])
648                data.append([f_label,f_text])
649            else:
650                filename = widget._data.file.name
651                im = Image(filename,width=4*cm, height=3*cm,kind='bound')
652                data.append([f_label,im])
653        f_label = '<font size=12>Admitted Course of Study:</font>'
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])
658        table = Table(data,style=SLIP_STYLE)
659        story.append(table)
660        frame_body.addFromList(story,pdf)
661
662        story = []
663        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
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
669        self.response.setHeader(
670            'Content-Type', 'application/pdf')
671        return pdf.getpdfdata()
672
673class ApplicantManageActionButton(ManageActionButton):
674    grok.context(IApplicant)
675    grok.view(DisplayApplicant)
676    grok.require('waeup.manageApplications')
677    text = 'Manage application record'
678    target = 'edit_full'
679
680class EditApplicantFull(WAeUPEditFormPage):
681    """A full edit view for applicant data.
682    """
683    grok.context(IApplicant)
684    grok.name('edit_full')
685    grok.require('waeup.manageApplications')
686    form_fields = grok.AutoFields(IApplicant)
687    form_fields['passport'].custom_widget = EncodingImageFileWidget
688    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
689    grok.template('form_edit')
690    manage_applications = True
691    pnav = 3
692
693    def update(self):
694        datepicker.need() # Enable jQuery datepicker in date fields.
695        super(EditApplicantFull, self).update()
696        self.wf_info = IWorkflowInfo(self.context)
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
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
727    def getTransitions(self):
728        """Return a list of dicts of allowed transition ids and titles.
729
730        Each list entry provides keys ``name`` and ``title`` for
731        internal name and (human readable) title of a single
732        transition.
733        """
734        allowed_transitions = self.wf_info.getManualTransitions()
735        return [dict(name='', title='No transition')] +[
736            dict(name=x, title=y) for x, y in allowed_transitions]
737
738    @grok.action('Save')
739    def save(self, **data):
740        changed_fields = self.applyData(self.context, **data)
741        changed_fields = changed_fields.values()
742        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
743        self.context._p_changed = True
744        form = self.request.form
745        if form.has_key('transition') and form['transition']:
746            transition_id = form['transition']
747            self.wf_info.fireTransition(transition_id)
748        self.flash('Form has been saved.')
749        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
750        if fields_string:
751            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
752        return
753
754class EditApplicantStudent(EditApplicantFull):
755    """An applicant-centered edit view for applicant data.
756    """
757    grok.context(IApplicantEdit)
758    grok.name('edit')
759    grok.require('waeup.handleApplication')
760    form_fields = grok.AutoFields(IApplicantEdit).omit(
761        'locked', 'course_admitted', 'student_id',
762        'screening_score',
763        )
764    form_fields['passport'].custom_widget = EncodingImageFileWidget
765    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
766    grok.template('form_edit')
767    manage_applications = False
768    title = u'Your Application Form'
769
770
771    def emitLockMessage(self):
772        self.flash('The requested form is locked (read-only).')
773        self.redirect(self.url(self.context))
774        return
775
776    def update(self):
777        if self.context.locked:
778            self.emitLockMessage()
779            return
780        datepicker.need() # Enable jQuery datepicker in date fields.
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
800        super(EditApplicantStudent, self).update()
801        return
802
803    def dataNotComplete(self):
804        if not self.request.form.get('confirm_passport', False):
805            return 'Passport confirmation box not ticked.'
806        #if len(self.errors) > 0:
807        #    return 'Form has errors.'
808        return False
809
810    @grok.action('Save')
811    def save(self, **data):
812        #if self.context.locked:
813        #    self.emitLockMessage()
814        #    return
815        self.applyData(self.context, **data)
816        self.context._p_changed = True
817        self.flash('Form has been saved.')
818        return
819
820    @grok.action('Final Submit')
821    def finalsubmit(self, **data):
822        #if self.context.locked:
823        #    self.emitLockMessage()
824        #    return
825        self.applyData(self.context, **data)
826        self.context._p_changed = True
827        if self.dataNotComplete():
828            self.flash(self.dataNotComplete())
829            return
830        state = IWorkflowState(self.context).getState()
831        # This shouldn't happen, but the application officer
832        # might have forgotten to lock the form after changing the state
833        if state != STARTED:
834            self.flash('This form cannot be submitted. Wrong state!')
835            return
836        IWorkflowInfo(self.context).fireTransition('submit')
837        self.context.application_date = datetime.now()
838        self.context.locked = True
839        self.flash('Form has been submitted.')
840        self.redirect(self.url(self.context))
841        return
842
843class ApplicantViewActionButton(ManageActionButton):
844    grok.context(IApplicant)
845    grok.view(EditApplicantFull)
846    grok.require('waeup.manageApplications')
847    icon = 'actionicon_view.png'
848    text = 'View application record'
849    target = 'index'
Note: See TracBrowser for help on using the repository browser.