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

Last change on this file since 6309 was 6305, checked in by Henrik Bettermann, 14 years ago

Fill message attribute with workflow messages and display on forms.

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