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

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

Assign current applicant principal the PortalUser? role for the academic section to view his/her certificate.

File size: 20.8 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        # Assign current principal the PortalUser role
419        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
420        role_manager.assignRoleToPrincipal(
421            'waeup.PortalUser', self.request.principal.id)
422        state = IWorkflowState(self.context[pin]).getState()
423        if state == INITIALIZED:
424            IWorkflowInfo(self.context[pin]).fireTransition('start')
425        self.redirect(self.url(self.context[pin], 'edit'))
426        return
427
428class AccessCodeLink(LeftSidebarLink):
429    grok.order(1)
430    grok.require('waeup.Public')
431
432    def render(self):
433        if not IApplicantPrincipal.providedBy(self.request.principal):
434            return ''
435        access_code = getattr(self.request.principal,'access_code',None)
436        if access_code:
437            applicant_object = get_applicant_data(access_code)
438            url = absoluteURL(applicant_object, self.request)
439            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
440                url,access_code)
441        return ''
442
443class DisplayApplicant(WAeUPDisplayFormPage):
444    grok.context(IApplicant)
445    grok.name('index')
446    grok.require('waeup.handleApplication')
447    form_fields = grok.AutoFields(IApplicant).omit(
448        'locked').omit('course_admitted')
449    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
450    form_fields['passport'].custom_widget = ThumbnailWidget
451    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
452    label = 'Applicant'
453    grok.template('form_display')
454    pnav = 3
455
456    @property
457    def title(self):
458        return '%s' % self.context.access_code
459
460    @property
461    def label(self):
462        container_title = self.context.__parent__.title
463        return '%s Application Record' % container_title
464
465    @property
466    def getCourseAdmitted(self):
467        course_admitted = self.context.course_admitted
468        #import pdb; pdb.set_trace()
469        if ICertificate.providedBy(course_admitted):
470            url = self.url(course_admitted)
471            title = course_admitted.title
472            code = course_admitted.code
473            return '<a href="%s">%s (%s)</a>' %(url,title,code)
474        return 'not yet admitted'
475
476    #@property
477    #def getApplicationState(self):
478    #    state = IWorkflowState(self.context).getState()
479    #    return state
480
481class ApplicantsManageActionButton(ManageActionButton):
482    grok.context(IApplicant)
483    grok.view(DisplayApplicant)
484    grok.require('waeup.manageApplications')
485    text = 'Edit application record'
486    target = 'edit_full'
487
488class EditApplicantFull(WAeUPEditFormPage):
489    """A full edit view for applicant data.
490    """
491    grok.context(IApplicant)
492    grok.name('edit_full')
493    grok.require('waeup.manageApplications')
494    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
495    form_fields['passport'].custom_widget = EncodingImageFileWidget
496    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
497    grok.template('form_edit')
498    manage_applications = True
499    pnav = 3
500
501    def update(self):
502        datepicker.need() # Enable jQuery datepicker in date fields.
503        super(EditApplicantFull, self).update()
504        return
505
506    @property
507    def title(self):
508        return self.context.access_code
509
510    @property
511    def label(self):
512        container_title = self.context.__parent__.title
513        return '%s Application Form' % container_title
514
515    @property
516    def getTransitions(self):
517        allowed_transitions_ids = IWorkflowInfo(
518            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        self.flash('Form has been saved.')
542        return
543
544class EditApplicantStudent(EditApplicantFull):
545    """An applicant-centered edit view for applicant data.
546    """
547    grok.context(IApplicantEdit)
548    grok.name('edit')
549    grok.require('waeup.handleApplication')
550    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
551    form_fields['passport'].custom_widget = EncodingImageFileWidget
552    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
553    grok.template('form_edit')
554    manage_applications = False
555
556
557    def emitLockMessage(self):
558        self.flash('The requested form is locked (read-only).')
559        self.redirect(self.url(self.context))
560        return
561
562    def update(self):
563        if self.context.locked:
564            self.redirect(self.url(self.context))
565            return
566        datepicker.need() # Enable jQuery datepicker in date fields.
567        super(EditApplicantStudent, self).update()
568        return
569
570    def dataNotComplete(self):
571        #import pdb; pdb.set_trace()
572        if not self.request.form.get('confirm_passport', False):
573            return 'Passport confirmation box not ticked.'
574        if len(self.errors) > 0:
575            return 'Form has errors.'
576        return False
577
578    @grok.action('Save')
579    def save(self, **data):
580        if self.context.locked:
581            self.emitLockMessage()
582            return
583        self.applyData(self.context, **data)
584        self.context._p_changed = True
585        self.flash('Form has been saved.')
586        return
587
588    @grok.action('Final Submit')
589    def finalsubmit(self, **data):
590        if self.context.locked:
591            self.emitLockMessage()
592            return
593        self.applyData(self.context, **data)
594        self.context._p_changed = True
595        if self.dataNotComplete():
596            self.flash(self.dataNotComplete())
597            return
598        state = IWorkflowState(self.context).getState()
599        # This shouldn't happen, but the application officer
600        # might have forgotten to lock the form after changing the state
601        if state != STARTED:
602            self.flash('This form cannot be submitted. Wrong state!')
603            return
604        IWorkflowInfo(self.context).fireTransition('submit')
605        self.context.locked = True
606        self.flash('Form has been submitted.')
607        self.redirect(self.url(self.context))
608        return
609
Note: See TracBrowser for help on using the repository browser.