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

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

Control workflow in views.

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