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

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

Implement ApplicantAddFormPage?.

File size: 21.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        self.redirect(self.url(self.context, 'addapplicant'))
362        return
363
364    @grok.action('Cancel', validator=NullValidator)
365    def cancel(self, **data):
366        self.redirect(self.url(self.context))
367        return
368
369    @grok.action('Add local role', validator=NullValidator)
370    def addLocalRole(self, **data):
371        return add_local_role(self,3, **data)
372
373    @grok.action('Remove selected local roles')
374    def delLocalRoles(self, **data):
375        return del_local_roles(self,3,**data)
376
377
378class LoginApplicant(WAeUPPage):
379    grok.context(IApplicantsContainer)
380    grok.name('login')
381    grok.require('waeup.Public')
382
383    @property
384    def title(self):
385        return u"Applicant Login: %s" % self.context.title
386
387    @property
388    def label(self):
389        return u'Login for applicants only'
390
391    pnav = 3
392
393    @property
394    def ac_prefix(self):
395        return self.context.ac_prefix
396
397    def update(self, SUBMIT=None):
398        self.ac_series = self.request.form.get('form.ac_series', None)
399        self.ac_number = self.request.form.get('form.ac_number', None)
400        if SUBMIT is None:
401            return
402        if self.request.principal.id == 'zope.anybody':
403            self.flash('Entered credentials are invalid.')
404            return
405        if not IApplicantPrincipal.providedBy(self.request.principal):
406            # Don't care if user is already authenticated as non-applicant
407            return
408        pin = self.request.principal.access_code
409        if pin not in self.context.keys():
410            # Create applicant record
411            applicant = Applicant()
412            applicant.access_code = pin
413            self.context[pin] = applicant
414        # Assign current principal the owner role on created applicant
415        # record
416        role_manager = IPrincipalRoleManager(self.context[pin])
417        role_manager.assignRoleToPrincipal(
418            'waeup.local.ApplicationOwner', self.request.principal.id)
419        # Assign current principal the PortalUser role
420        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
421        role_manager.assignRoleToPrincipal(
422            'waeup.PortalUser', self.request.principal.id)
423        state = IWorkflowState(self.context[pin]).getState()
424        if state == INITIALIZED:
425            IWorkflowInfo(self.context[pin]).fireTransition('start')
426        self.redirect(self.url(self.context[pin], 'edit'))
427        return
428
429class ApplicantAddFormPage(WAeUPAddFormPage):
430    """Add-form to add certificate to a department.
431    """
432    grok.context(IApplicantsContainer)
433    grok.require('waeup.manageApplications')
434    grok.name('addapplicant')
435    grok.template('applicantaddpage')
436    title = 'Applicants'
437    label = 'Add applicant'
438    pnav = 3
439
440    @property
441    def title(self):
442        return "Applicants Container: %s" % self.context.title
443
444    @property
445    def ac_prefix(self):
446        return self.context.ac_prefix
447
448    @grok.action('Create application record')
449    def addApplicant(self, **data):
450        ac_series = self.request.form.get('form.ac_series', None)
451        ac_number = self.request.form.get('form.ac_number', None)
452        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
453        if pin not in self.context.keys():
454            # Create applicant record
455            applicant = Applicant()
456            applicant.access_code = pin
457            self.context[pin] = applicant
458        self.redirect(self.url(self.context[pin], 'edit'))
459        return
460
461class AccessCodeLink(LeftSidebarLink):
462    grok.order(1)
463    grok.require('waeup.Public')
464
465    def render(self):
466        if not IApplicantPrincipal.providedBy(self.request.principal):
467            return ''
468        access_code = getattr(self.request.principal,'access_code',None)
469        if access_code:
470            applicant_object = get_applicant_data(access_code)
471            url = absoluteURL(applicant_object, self.request)
472            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
473                url,access_code)
474        return ''
475
476class DisplayApplicant(WAeUPDisplayFormPage):
477    grok.context(IApplicant)
478    grok.name('index')
479    grok.require('waeup.handleApplication')
480    form_fields = grok.AutoFields(IApplicant).omit(
481        'locked').omit('course_admitted')
482    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
483    form_fields['passport'].custom_widget = ThumbnailWidget
484    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
485    label = 'Applicant'
486    grok.template('form_display')
487    pnav = 3
488
489    @property
490    def title(self):
491        return '%s' % self.context.access_code
492
493    @property
494    def label(self):
495        container_title = self.context.__parent__.title
496        return '%s Application Record' % container_title
497
498    @property
499    def getCourseAdmitted(self):
500        course_admitted = self.context.course_admitted
501        #import pdb; pdb.set_trace()
502        if ICertificate.providedBy(course_admitted):
503            url = self.url(course_admitted)
504            title = course_admitted.title
505            code = course_admitted.code
506            return '<a href="%s">%s (%s)</a>' %(url,title,code)
507        return 'not yet admitted'
508
509    #@property
510    #def getApplicationState(self):
511    #    state = IWorkflowState(self.context).getState()
512    #    return state
513
514class ApplicantsManageActionButton(ManageActionButton):
515    grok.context(IApplicant)
516    grok.view(DisplayApplicant)
517    grok.require('waeup.manageApplications')
518    text = 'Edit application record'
519    target = 'edit_full'
520
521class EditApplicantFull(WAeUPEditFormPage):
522    """A full edit view for applicant data.
523    """
524    grok.context(IApplicant)
525    grok.name('edit_full')
526    grok.require('waeup.manageApplications')
527    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
528    form_fields['passport'].custom_widget = EncodingImageFileWidget
529    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
530    grok.template('form_edit')
531    manage_applications = True
532    pnav = 3
533
534    def update(self):
535        datepicker.need() # Enable jQuery datepicker in date fields.
536        super(EditApplicantFull, self).update()
537        return
538
539    @property
540    def title(self):
541        return self.context.access_code
542
543    @property
544    def label(self):
545        container_title = self.context.__parent__.title
546        return '%s Application Form' % container_title
547
548    @property
549    def getTransitions(self):
550        allowed_transitions_ids = IWorkflowInfo(
551            self.context).getManualTransitionIds()
552        null_transition = [{'name': '', 'title':'No transition'}]
553        transitions = null_transition + [dict(
554            name=transition_object.transition_id,
555            title=transition_object.title)
556            for transition_object in TRANSITION_OBJECTS
557            if transition_object.transition_id in allowed_transitions_ids]
558        #import pdb; pdb.set_trace()
559        return transitions
560
561    #@property
562    #def getApplicationState(self):
563    #    state = IWorkflowState(self.context).getState()
564    #    return state
565
566    @grok.action('Save')
567    def save(self, **data):
568        self.applyData(self.context, **data)
569        self.context._p_changed = True
570        form = self.request.form
571        if form.has_key('transition') and form['transition']:
572            transition_id = form['transition']
573            IWorkflowInfo(self.context).fireTransition(transition_id)
574        self.flash('Form has been saved.')
575        return
576
577class EditApplicantStudent(EditApplicantFull):
578    """An applicant-centered edit view for applicant data.
579    """
580    grok.context(IApplicantEdit)
581    grok.name('edit')
582    grok.require('waeup.handleApplication')
583    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
584    form_fields['passport'].custom_widget = EncodingImageFileWidget
585    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
586    grok.template('form_edit')
587    manage_applications = False
588
589
590    def emitLockMessage(self):
591        self.flash('The requested form is locked (read-only).')
592        self.redirect(self.url(self.context))
593        return
594
595    def update(self):
596        if self.context.locked:
597            self.redirect(self.url(self.context))
598            return
599        datepicker.need() # Enable jQuery datepicker in date fields.
600        super(EditApplicantStudent, self).update()
601        return
602
603    def dataNotComplete(self):
604        #import pdb; pdb.set_trace()
605        if not self.request.form.get('confirm_passport', False):
606            return 'Passport confirmation box not ticked.'
607        if len(self.errors) > 0:
608            return 'Form has errors.'
609        return False
610
611    @grok.action('Save')
612    def save(self, **data):
613        if self.context.locked:
614            self.emitLockMessage()
615            return
616        self.applyData(self.context, **data)
617        self.context._p_changed = True
618        self.flash('Form has been saved.')
619        return
620
621    @grok.action('Final Submit')
622    def finalsubmit(self, **data):
623        if self.context.locked:
624            self.emitLockMessage()
625            return
626        self.applyData(self.context, **data)
627        self.context._p_changed = True
628        if self.dataNotComplete():
629            self.flash(self.dataNotComplete())
630            return
631        state = IWorkflowState(self.context).getState()
632        # This shouldn't happen, but the application officer
633        # might have forgotten to lock the form after changing the state
634        if state != STARTED:
635            self.flash('This form cannot be submitted. Wrong state!')
636            return
637        IWorkflowInfo(self.context).fireTransition('submit')
638        self.context.locked = True
639        self.flash('Form has been submitted.')
640        self.redirect(self.url(self.context))
641        return
642
Note: See TracBrowser for help on using the repository browser.