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

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

Provide application state as a (property) method of Applicant. This allows displaying the state of Applicant objects on ApplicantsContainer? form pages.

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