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

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

Implement pdf slip demo version.

File size: 22.7 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.securitypolicy.interfaces import IPrincipalRoleManager
30from zope.traversing.browser import absoluteURL
31
32from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
33
34from reportlab.pdfgen import canvas
35
36from waeup.sirp.browser import (
37    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
38from waeup.sirp.browser.breadcrumbs import Breadcrumb
39from waeup.sirp.browser.layout import NullValidator
40from waeup.sirp.browser.pages import add_local_role, del_local_roles
41from waeup.sirp.browser.resources import datepicker, tabs, datatable
42from waeup.sirp.browser.viewlets import (
43    ManageActionButton, PrimaryNavTab, LeftSidebarLink
44    )
45from waeup.sirp.image.browser.widget import (
46    ThumbnailWidget, EncodingImageFileWidget,
47    )
48from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
49from waeup.sirp.permissions import get_users_with_local_roles
50from waeup.sirp.university.interfaces import ICertificate
51from waeup.sirp.widgets.datewidget import (
52    FriendlyDateWidget, FriendlyDateDisplayWidget)
53from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
54from waeup.sirp.widgets.objectwidget import (
55    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
56from waeup.sirp.widgets.multilistwidget import (
57    MultiListWidget, MultiListDisplayWidget)
58from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
59from waeup.sirp.applicants.interfaces import (
60    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
61    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
62    )
63from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
64
65results_widget = CustomWidgetFactory(
66    WAeUPObjectWidget, ResultEntry)
67
68results_display_widget = CustomWidgetFactory(
69    WAeUPObjectDisplayWidget, ResultEntry)
70
71list_results_widget = CustomWidgetFactory(
72    MultiListWidget, subwidget=results_widget)
73
74list_results_display_widget = CustomWidgetFactory(
75    MultiListDisplayWidget, subwidget=results_display_widget)
76
77#TRANSITION_OBJECTS = create_workflow()
78
79#TRANSITION_DICT = dict([
80#    (transition_object.transition_id,transition_object.title)
81#    for transition_object in TRANSITION_OBJECTS])
82
83class ApplicantsRootPage(WAeUPPage):
84    grok.context(IApplicantsRoot)
85    grok.name('index')
86    grok.require('waeup.Public')
87    title = 'Applicants'
88    label = 'Application Section'
89    pnav = 3
90
91    def update(self):
92        super(ApplicantsRootPage, self).update()
93        datatable.need()
94        return
95
96class ManageApplicantsRootActionButton(ManageActionButton):
97    grok.context(IApplicantsRoot)
98    grok.view(ApplicantsRootPage)
99    grok.require('waeup.manageApplications')
100    text = 'Manage application section'
101
102class ApplicantsRootManageFormPage(WAeUPEditFormPage):
103    grok.context(IApplicantsRoot)
104    grok.name('manage')
105    grok.template('applicantsrootmanagepage')
106    title = 'Applicants'
107    label = 'Manage application section'
108    pnav = 3
109    grok.require('waeup.manageApplications')
110    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
111    tabtwoactions1 = ['Remove selected local roles']
112    tabtwoactions2 = ['Add local role']
113    subunits = 'Applicants Containers'
114
115    def update(self):
116        tabs.need()
117        datatable.need()
118        return super(ApplicantsRootManageFormPage, self).update()
119
120    def getLocalRoles(self):
121        roles = ILocalRolesAssignable(self.context)
122        return roles()
123
124    def getUsers(self):
125        """Get a list of all users.
126        """
127        for key, val in grok.getSite()['users'].items():
128            url = self.url(val)
129            yield(dict(url=url, name=key, val=val))
130
131    def getUsersWithLocalRoles(self):
132        return get_users_with_local_roles(self.context)
133
134    # ToDo: Show warning message before deletion
135    @grok.action('Remove selected')
136    def delApplicantsContainers(self, **data):
137        form = self.request.form
138        child_id = form['val_id']
139        if not isinstance(child_id, list):
140            child_id = [child_id]
141        deleted = []
142        for id in child_id:
143            try:
144                del self.context[id]
145                deleted.append(id)
146            except:
147                self.flash('Could not delete %s: %s: %s' % (
148                        id, sys.exc_info()[0], sys.exc_info()[1]))
149        if len(deleted):
150            self.flash('Successfully removed: %s' % ', '.join(deleted))
151        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
152        return
153
154    @grok.action('Add applicants container', validator=NullValidator)
155    def addApplicantsContainer(self, **data):
156        self.redirect(self.url(self.context, '@@add'))
157        return
158
159    @grok.action('Cancel', validator=NullValidator)
160    def cancel(self, **data):
161        self.redirect(self.url(self.context))
162        return
163
164    @grok.action('Add local role', validator=NullValidator)
165    def addLocalRole(self, **data):
166        return add_local_role(self,2, **data)
167
168    @grok.action('Remove selected local roles')
169    def delLocalRoles(self, **data):
170        return del_local_roles(self,2,**data)
171
172class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
173    grok.context(IApplicantsRoot)
174    grok.require('waeup.manageApplications')
175    grok.name('add')
176    grok.template('applicantscontaineraddpage')
177    title = 'Applicants'
178    label = 'Add applicants container'
179    pnav = 3
180
181    form_fields = grok.AutoFields(
182        IApplicantsContainerAdd).omit('code').omit('title')
183    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
184    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
185
186    def update(self):
187        datepicker.need() # Enable jQuery datepicker in date fields.
188        return super(ApplicantsContainerAddFormPage, self).update()
189
190    @grok.action('Add applicants container')
191    def addApplicantsContainer(self, **data):
192        year = data['year']
193        code = u'%s%s' % (data['prefix'], year)
194        prefix = application_types_vocab.getTerm(data['prefix'])
195        title = u'%s %s/%s' % (prefix.title, year, year + 1)
196        if code in self.context.keys():
197            self.flash(
198                'An applicants container for the same application '
199                'type and entrance year exists already in the database.')
200            return
201        # Add new applicants container...
202        provider = data['provider'][1]
203        container = provider.factory()
204        self.applyData(container, **data)
205        container.code = code
206        container.title = title
207        self.context[code] = container
208        self.flash('Added: "%s".' % code)
209        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
210        return
211
212    @grok.action('Cancel', validator=NullValidator)
213    def cancel(self, **data):
214        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
215
216class ApplicantsRootBreadcrumb(Breadcrumb):
217    """A breadcrumb for applicantsroot.
218    """
219    grok.context(IApplicantsRoot)
220    title = u'Application Section'
221
222class ApplicantsContainerBreadcrumb(Breadcrumb):
223    """A breadcrumb for applicantscontainers.
224    """
225    grok.context(IApplicantsContainer)
226
227class ApplicantBreadcrumb(Breadcrumb):
228    """A breadcrumb for applicants.
229    """
230    grok.context(IApplicant)
231
232    @property
233    def title(self):
234        """Get a title for a context.
235        """
236        return self.context.access_code
237
238class ApplicantsTab(PrimaryNavTab):
239    """Applicants tab in primary navigation.
240    """
241
242    grok.context(IWAeUPObject)
243    grok.order(3)
244    grok.require('waeup.Public')
245    grok.template('primarynavtab')
246
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 ApplicantsContainerPage(WAeUPDisplayFormPage):
255    """The standard view for regular applicant containers.
256    """
257    grok.context(IApplicantsContainer)
258    grok.name('index')
259    grok.require('waeup.Public')
260    grok.template('applicantscontainerpage')
261    pnav = 3
262
263    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
264    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
265    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
266    form_fields['description'].custom_widget = ReSTDisplayWidget
267
268    @property
269    def title(self):
270        return "Applicants Container: %s" % self.context.title
271
272    @property
273    def label(self):
274        return self.context.title
275
276class ApplicantsContainerManageActionButton(ManageActionButton):
277    grok.order(1)
278    grok.context(IApplicantsContainer)
279    grok.view(ApplicantsContainerPage)
280    grok.require('waeup.manageApplications')
281    text = 'Manage applicants container'
282
283class LoginApplicantActionButton(ManageActionButton):
284    grok.order(2)
285    grok.context(IApplicantsContainer)
286    grok.view(ApplicantsContainerPage)
287    grok.require('waeup.Anonymous')
288    icon = 'login.png'
289    text = 'Login for applicants'
290    target = 'login'
291
292class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
293    grok.context(IApplicantsContainer)
294    grok.name('manage')
295    grok.template('applicantscontainermanagepage')
296    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
297    taboneactions = ['Save','Cancel']
298    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
299    tabthreeactions1 = ['Remove selected local roles']
300    tabthreeactions2 = ['Add local role']
301    # Use friendlier date widget...
302    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
303    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
304    grok.require('waeup.manageApplications')
305
306    @property
307    def title(self):
308        return "Applicants Container: %s" % self.context.title
309
310    @property
311    def label(self):
312        return 'Manage applicants container'
313
314    pnav = 3
315
316    def update(self):
317        datepicker.need() # Enable jQuery datepicker in date fields.
318        tabs.need()
319        datatable.need()  # Enable jQurey datatables for contents listing
320        return super(ApplicantsContainerManageFormPage, self).update()
321
322    def getLocalRoles(self):
323        roles = ILocalRolesAssignable(self.context)
324        return roles()
325
326    def getUsers(self):
327        """Get a list of all users.
328        """
329        for key, val in grok.getSite()['users'].items():
330            url = self.url(val)
331            yield(dict(url=url, name=key, val=val))
332
333    def getUsersWithLocalRoles(self):
334        return get_users_with_local_roles(self.context)
335
336    @grok.action('Save')
337    def apply(self, **data):
338        self.applyData(self.context, **data)
339        self.flash('Data saved.')
340        return
341
342    # ToDo: Show warning message before deletion
343    @grok.action('Remove selected')
344    def delApplicant(self, **data):
345        form = self.request.form
346        if form.has_key('val_id'):
347            child_id = form['val_id']
348        else:
349            self.flash('No applicant selected!')
350            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
351            return
352        if not isinstance(child_id, list):
353            child_id = [child_id]
354        deleted = []
355        for id in child_id:
356            try:
357                del self.context[id]
358                deleted.append(id)
359            except:
360                self.flash('Could not delete %s: %s: %s' % (
361                        id, sys.exc_info()[0], sys.exc_info()[1]))
362        if len(deleted):
363            self.flash('Successfully removed: %s' % ', '.join(deleted))
364        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
365        return
366
367    @grok.action('Add applicant', validator=NullValidator)
368    def addApplicant(self, **data):
369        self.redirect(self.url(self.context, 'addapplicant'))
370        return
371
372    @grok.action('Cancel', validator=NullValidator)
373    def cancel(self, **data):
374        self.redirect(self.url(self.context))
375        return
376
377    @grok.action('Add local role', validator=NullValidator)
378    def addLocalRole(self, **data):
379        return add_local_role(self,3, **data)
380
381    @grok.action('Remove selected local roles')
382    def delLocalRoles(self, **data):
383        return del_local_roles(self,3,**data)
384
385class LoginApplicant(WAeUPPage):
386    grok.context(IApplicantsContainer)
387    grok.name('login')
388    grok.require('waeup.Public')
389
390    @property
391    def title(self):
392        return u"Applicant Login: %s" % self.context.title
393
394    @property
395    def label(self):
396        return u'Login for applicants only'
397
398    pnav = 3
399
400    @property
401    def ac_prefix(self):
402        return self.context.ac_prefix
403
404    def update(self, SUBMIT=None):
405        self.ac_series = self.request.form.get('form.ac_series', None)
406        self.ac_number = self.request.form.get('form.ac_number', None)
407        if SUBMIT is None:
408            return
409        if self.request.principal.id == 'zope.anybody':
410            self.flash('Entered credentials are invalid.')
411            return
412        if not IApplicantPrincipal.providedBy(self.request.principal):
413            # Don't care if user is already authenticated as non-applicant
414            return
415        pin = self.request.principal.access_code
416        if pin not in self.context.keys():
417            # Create applicant record
418            applicant = Applicant()
419            applicant.access_code = pin
420            self.context[pin] = applicant
421        # Assign current principal the owner role on created applicant
422        # record
423        role_manager = IPrincipalRoleManager(self.context[pin])
424        role_manager.assignRoleToPrincipal(
425            'waeup.local.ApplicationOwner', self.request.principal.id)
426        # Assign current principal the PortalUser role
427        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
428        role_manager.assignRoleToPrincipal(
429            'waeup.PortalUser', self.request.principal.id)
430        # XXX: disable for now. Pins will get a different workflow.
431        #state = IWorkflowState(self.context[pin]).getState()
432        #if state == INITIALIZED:
433        #    IWorkflowInfo(self.context[pin]).fireTransition('start')
434        self.redirect(self.url(self.context[pin], 'edit'))
435        return
436
437class ApplicantAddFormPage(WAeUPAddFormPage):
438    """Add-form to add certificate to a department.
439    """
440    grok.context(IApplicantsContainer)
441    grok.require('waeup.manageApplications')
442    grok.name('addapplicant')
443    grok.template('applicantaddpage')
444    title = 'Applicants'
445    label = 'Add applicant'
446    pnav = 3
447
448    @property
449    def title(self):
450        return "Applicants Container: %s" % self.context.title
451
452    @property
453    def ac_prefix(self):
454        return self.context.ac_prefix
455
456    @grok.action('Create application record')
457    def addApplicant(self, **data):
458        ac_series = self.request.form.get('form.ac_series', None)
459        ac_number = self.request.form.get('form.ac_number', None)
460        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
461        if pin not in self.context.keys():
462            # Create applicant record
463            applicant = Applicant()
464            applicant.access_code = pin
465            self.context[pin] = applicant
466        self.redirect(self.url(self.context[pin], 'edit'))
467        return
468
469class AccessCodeLink(LeftSidebarLink):
470    grok.order(1)
471    grok.require('waeup.Public')
472
473    def render(self):
474        if not IApplicantPrincipal.providedBy(self.request.principal):
475            return ''
476        access_code = getattr(self.request.principal,'access_code',None)
477        if access_code:
478            applicant_object = get_applicant_data(access_code)
479            url = absoluteURL(applicant_object, self.request)
480            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
481                url,access_code)
482        return ''
483
484class DisplayApplicant(WAeUPDisplayFormPage):
485    grok.context(IApplicant)
486    grok.name('index')
487    grok.require('waeup.handleApplication')
488    form_fields = grok.AutoFields(IApplicant).omit(
489        'locked').omit('course_admitted')
490    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
491    form_fields['passport'].custom_widget = ThumbnailWidget
492    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
493    label = 'Applicant'
494    grok.template('form_display')
495    pnav = 3
496
497    @property
498    def title(self):
499        return '%s' % self.context.access_code
500
501    @property
502    def label(self):
503        container_title = self.context.__parent__.title
504        return '%s Application Record' % container_title
505
506    def getCourseAdmitted(self):
507        """Return link, title and code in html format to the certificate
508           admitted.
509        """
510        course_admitted = self.context.course_admitted
511        if ICertificate.providedBy(course_admitted):
512            url = self.url(course_admitted)
513            title = course_admitted.title
514            code = course_admitted.code
515            return '<a href="%s">%s (%s)</a>' %(url,title,code)
516        return 'not yet admitted'
517
518class ApplicantsManageActionButton(ManageActionButton):
519    grok.context(IApplicant)
520    grok.view(DisplayApplicant)
521    grok.require('waeup.manageApplications')
522    text = 'Edit application record'
523    target = 'edit_full'
524
525class EditApplicantFull(WAeUPEditFormPage):
526    """A full edit view for applicant data.
527    """
528    grok.context(IApplicant)
529    grok.name('edit_full')
530    grok.require('waeup.manageApplications')
531    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
532    form_fields['passport'].custom_widget = EncodingImageFileWidget
533    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
534    grok.template('form_edit')
535    manage_applications = True
536    pnav = 3
537
538    def update(self):
539        datepicker.need() # Enable jQuery datepicker in date fields.
540        super(EditApplicantFull, self).update()
541        self.wf_info = IWorkflowInfo(self.context)
542        return
543
544    @property
545    def title(self):
546        return self.context.access_code
547
548    @property
549    def label(self):
550        container_title = self.context.__parent__.title
551        return '%s Application Form' % container_title
552
553    def getTransitions(self):
554        """Return a list of dicts of allowed transition ids and titles.
555
556        Each list entry provides keys ``name`` and ``title`` for
557        internal name and (human readable) title of a single
558        transition.
559        """
560        allowed_transitions = self.wf_info.getManualTransitions()
561        return [dict(name='', title='No transition')] +[
562            dict(name=x, title=y) for x, y in allowed_transitions]
563
564    @grok.action('Save')
565    def save(self, **data):
566        self.applyData(self.context, **data)
567        self.context._p_changed = True
568        form = self.request.form
569        if form.has_key('transition') and form['transition']:
570            transition_id = form['transition']
571            self.wf_info.fireTransition(transition_id)
572        self.flash('Form has been saved.')
573        self.context.getApplicantsRootLogger().info('Saved')
574        return
575
576class PDFActionButton(ManageActionButton):
577    grok.context(IApplicant)
578    grok.view(DisplayApplicant)
579    grok.require('waeup.handleApplication')
580    icon = 'actionicon_pdf.png'
581    text = 'Download pdf slip'
582    target = 'application_slip.pdf'
583
584class ExportPDFPage(grok.View):
585    """Deliver a PDF slip of the context.
586    """
587    grok.context(IApplicant)
588    grok.name('application_slip.pdf')
589    grok.require('waeup.handleApplication')
590
591    # Render a demo pdf pagge
592    def render(self):
593        #exporter = IWAeUPXMLExporter(self.context)
594        #xml = exporter.export().read()
595        pdf = canvas.Canvas("application_slip.pdf")
596        pdf.drawString(100,750,"Welcome to Reportlab!")
597        self.response.setHeader(
598            'Content-Type', 'application/pdf')
599        return pdf.getpdfdata()
600
601class EditApplicantStudent(EditApplicantFull):
602    """An applicant-centered edit view for applicant data.
603    """
604    grok.context(IApplicantEdit)
605    grok.name('edit')
606    grok.require('waeup.handleApplication')
607    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
608    form_fields['passport'].custom_widget = EncodingImageFileWidget
609    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
610    grok.template('form_edit')
611    manage_applications = False
612
613
614    def emitLockMessage(self):
615        self.flash('The requested form is locked (read-only).')
616        self.redirect(self.url(self.context))
617        return
618
619    def update(self):
620        if self.context.locked:
621            self.redirect(self.url(self.context))
622            return
623        datepicker.need() # Enable jQuery datepicker in date fields.
624        super(EditApplicantStudent, self).update()
625        return
626
627    def dataNotComplete(self):
628        if not self.request.form.get('confirm_passport', False):
629            return 'Passport confirmation box not ticked.'
630        if len(self.errors) > 0:
631            return 'Form has errors.'
632        return False
633
634    @grok.action('Save')
635    def save(self, **data):
636        if self.context.locked:
637            self.emitLockMessage()
638            return
639        self.applyData(self.context, **data)
640        self.context._p_changed = True
641        self.flash('Form has been saved.')
642        return
643
644    @grok.action('Final Submit')
645    def finalsubmit(self, **data):
646        if self.context.locked:
647            self.emitLockMessage()
648            return
649        self.applyData(self.context, **data)
650        self.context._p_changed = True
651        if self.dataNotComplete():
652            self.flash(self.dataNotComplete())
653            return
654        state = IWorkflowState(self.context).getState()
655        # This shouldn't happen, but the application officer
656        # might have forgotten to lock the form after changing the state
657        if state != STARTED:
658            self.flash('This form cannot be submitted. Wrong state!')
659            return
660        IWorkflowInfo(self.context).fireTransition('submit')
661        self.context.locked = True
662        self.flash('Form has been submitted.')
663        self.redirect(self.url(self.context))
664        return
665
Note: See TracBrowser for help on using the repository browser.