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

Last change on this file since 6355 was 6355, checked in by uli, 13 years ago
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 INITIALIZED, STARTED
62
63results_widget = CustomWidgetFactory(
64    WAeUPObjectWidget, ResultEntry)
65
66results_display_widget = CustomWidgetFactory(
67    WAeUPObjectDisplayWidget, ResultEntry)
68
69list_results_widget = CustomWidgetFactory(
70    MultiListWidget, subwidget=results_widget)
71
72list_results_display_widget = CustomWidgetFactory(
73    MultiListDisplayWidget, subwidget=results_display_widget)
74
75#TRANSITION_OBJECTS = create_workflow()
76
77#TRANSITION_DICT = dict([
78#    (transition_object.transition_id,transition_object.title)
79#    for transition_object in TRANSITION_OBJECTS])
80
81class ApplicantsRootPage(WAeUPPage):
82    grok.context(IApplicantsRoot)
83    grok.name('index')
84    grok.require('waeup.Public')
85    title = 'Applicants'
86    label = 'Application Section'
87    pnav = 3
88
89    def update(self):
90        super(ApplicantsRootPage, self).update()
91        datatable.need()
92        return
93
94class ManageApplicantsRootActionButton(ManageActionButton):
95    grok.context(IApplicantsRoot)
96    grok.view(ApplicantsRootPage)
97    grok.require('waeup.manageApplications')
98    text = 'Manage application section'
99
100class ApplicantsRootManageFormPage(WAeUPEditFormPage):
101    grok.context(IApplicantsRoot)
102    grok.name('manage')
103    grok.template('applicantsrootmanagepage')
104    title = 'Applicants'
105    label = 'Manage application section'
106    pnav = 3
107    grok.require('waeup.manageApplications')
108    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
109    tabtwoactions1 = ['Remove selected local roles']
110    tabtwoactions2 = ['Add local role']
111    subunits = 'Applicants Containers'
112
113    def update(self):
114        tabs.need()
115        datatable.need()
116        return super(ApplicantsRootManageFormPage, self).update()
117
118    def getLocalRoles(self):
119        roles = ILocalRolesAssignable(self.context)
120        return roles()
121
122    def getUsers(self):
123        """Get a list of all users.
124        """
125        for key, val in grok.getSite()['users'].items():
126            url = self.url(val)
127            yield(dict(url=url, name=key, val=val))
128
129    def getUsersWithLocalRoles(self):
130        return get_users_with_local_roles(self.context)
131
132    # ToDo: Show warning message before deletion
133    @grok.action('Remove selected')
134    def delApplicantsContainers(self, **data):
135        form = self.request.form
136        child_id = form['val_id']
137        if not isinstance(child_id, list):
138            child_id = [child_id]
139        deleted = []
140        for id in child_id:
141            try:
142                del self.context[id]
143                deleted.append(id)
144            except:
145                self.flash('Could not delete %s: %s: %s' % (
146                        id, sys.exc_info()[0], sys.exc_info()[1]))
147        if len(deleted):
148            self.flash('Successfully removed: %s' % ', '.join(deleted))
149        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
150        return
151
152    @grok.action('Add applicants container', validator=NullValidator)
153    def addApplicantsContainer(self, **data):
154        self.redirect(self.url(self.context, '@@add'))
155        return
156
157    @grok.action('Cancel', validator=NullValidator)
158    def cancel(self, **data):
159        self.redirect(self.url(self.context))
160        return
161
162    @grok.action('Add local role', validator=NullValidator)
163    def addLocalRole(self, **data):
164        return add_local_role(self,2, **data)
165
166    @grok.action('Remove selected local roles')
167    def delLocalRoles(self, **data):
168        return del_local_roles(self,2,**data)
169
170class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
171    grok.context(IApplicantsRoot)
172    grok.require('waeup.manageApplications')
173    grok.name('add')
174    grok.template('applicantscontaineraddpage')
175    title = 'Applicants'
176    label = 'Add applicants container'
177    pnav = 3
178
179    form_fields = grok.AutoFields(
180        IApplicantsContainerAdd).omit('code').omit('title')
181    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
182    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
183
184    def update(self):
185        datepicker.need() # Enable jQuery datepicker in date fields.
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.Public')
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.order(1)
276    grok.context(IApplicantsContainer)
277    grok.view(ApplicantsContainerPage)
278    grok.require('waeup.manageApplications')
279    text = 'Manage applicants container'
280
281class LoginApplicantActionButton(ManageActionButton):
282    grok.order(2)
283    grok.context(IApplicantsContainer)
284    grok.view(ApplicantsContainerPage)
285    grok.require('waeup.Anonymous')
286    icon = 'login.png'
287    text = 'Login for applicants'
288    target = 'login'
289
290class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
291    grok.context(IApplicantsContainer)
292    grok.name('manage')
293    grok.template('applicantscontainermanagepage')
294    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
295    taboneactions = ['Save','Cancel']
296    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
297    tabthreeactions1 = ['Remove selected local roles']
298    tabthreeactions2 = ['Add local role']
299    # Use friendlier date widget...
300    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
301    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
302    grok.require('waeup.manageApplications')
303
304    @property
305    def title(self):
306        return "Applicants Container: %s" % self.context.title
307
308    @property
309    def label(self):
310        return 'Manage applicants container'
311
312    pnav = 3
313
314    def update(self):
315        datepicker.need() # Enable jQuery datepicker in date fields.
316        tabs.need()
317        datatable.need()  # Enable jQurey datatables for contents listing
318        return super(ApplicantsContainerManageFormPage, self).update()
319
320    def getLocalRoles(self):
321        roles = ILocalRolesAssignable(self.context)
322        return roles()
323
324    def getUsers(self):
325        """Get a list of all users.
326        """
327        for key, val in grok.getSite()['users'].items():
328            url = self.url(val)
329            yield(dict(url=url, name=key, val=val))
330
331    def getUsersWithLocalRoles(self):
332        return get_users_with_local_roles(self.context)
333
334    @grok.action('Save')
335    def apply(self, **data):
336        self.applyData(self.context, **data)
337        self.flash('Data saved.')
338        return
339
340    # ToDo: Show warning message before deletion
341    @grok.action('Remove selected')
342    def delApplicant(self, **data):
343        form = self.request.form
344        if form.has_key('val_id'):
345            child_id = form['val_id']
346        else:
347            self.flash('No applicant selected!')
348            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
349            return
350        if not isinstance(child_id, list):
351            child_id = [child_id]
352        deleted = []
353        for id in child_id:
354            try:
355                del self.context[id]
356                deleted.append(id)
357            except:
358                self.flash('Could not delete %s: %s: %s' % (
359                        id, sys.exc_info()[0], sys.exc_info()[1]))
360        if len(deleted):
361            self.flash('Successfully removed: %s' % ', '.join(deleted))
362        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
363        return
364
365    @grok.action('Add applicant', validator=NullValidator)
366    def addApplicant(self, **data):
367        self.redirect(self.url(self.context, 'addapplicant'))
368        return
369
370    @grok.action('Cancel', validator=NullValidator)
371    def cancel(self, **data):
372        self.redirect(self.url(self.context))
373        return
374
375    @grok.action('Add local role', validator=NullValidator)
376    def addLocalRole(self, **data):
377        return add_local_role(self,3, **data)
378
379    @grok.action('Remove selected local roles')
380    def delLocalRoles(self, **data):
381        return del_local_roles(self,3,**data)
382
383class LoginApplicant(WAeUPPage):
384    grok.context(IApplicantsContainer)
385    grok.name('login')
386    grok.require('waeup.Public')
387
388    @property
389    def title(self):
390        return u"Applicant Login: %s" % self.context.title
391
392    @property
393    def label(self):
394        return u'Login for applicants only'
395
396    pnav = 3
397
398    @property
399    def ac_prefix(self):
400        return self.context.ac_prefix
401
402    def update(self, SUBMIT=None):
403        self.ac_series = self.request.form.get('form.ac_series', None)
404        self.ac_number = self.request.form.get('form.ac_number', None)
405        if SUBMIT is None:
406            return
407        if self.request.principal.id == 'zope.anybody':
408            self.flash('Entered credentials are invalid.')
409            return
410        if not IApplicantPrincipal.providedBy(self.request.principal):
411            # Don't care if user is already authenticated as non-applicant
412            return
413        pin = self.request.principal.access_code
414        if pin not in self.context.keys():
415            # Create applicant record
416            applicant = Applicant()
417            applicant.access_code = pin
418            self.context[pin] = applicant
419        # Assign current principal the owner role on created applicant
420        # record
421        role_manager = IPrincipalRoleManager(self.context[pin])
422        role_manager.assignRoleToPrincipal(
423            'waeup.local.ApplicationOwner', self.request.principal.id)
424        # Assign current principal the PortalUser role
425        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
426        role_manager.assignRoleToPrincipal(
427            'waeup.PortalUser', self.request.principal.id)
428        # XXX: disable for now. Pins will get a different workflow.
429        #state = IWorkflowState(self.context[pin]).getState()
430        #if state == INITIALIZED:
431        #    IWorkflowInfo(self.context[pin]).fireTransition('start')
432        self.redirect(self.url(self.context[pin], 'edit'))
433        return
434
435class ApplicantAddFormPage(WAeUPAddFormPage):
436    """Add-form to add certificate to a department.
437    """
438    grok.context(IApplicantsContainer)
439    grok.require('waeup.manageApplications')
440    grok.name('addapplicant')
441    grok.template('applicantaddpage')
442    title = 'Applicants'
443    label = 'Add applicant'
444    pnav = 3
445
446    @property
447    def title(self):
448        return "Applicants Container: %s" % self.context.title
449
450    @property
451    def ac_prefix(self):
452        return self.context.ac_prefix
453
454    @grok.action('Create application record')
455    def addApplicant(self, **data):
456        ac_series = self.request.form.get('form.ac_series', None)
457        ac_number = self.request.form.get('form.ac_number', None)
458        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
459        if pin not in self.context.keys():
460            # Create applicant record
461            applicant = Applicant()
462            applicant.access_code = pin
463            self.context[pin] = applicant
464        self.redirect(self.url(self.context[pin], 'edit'))
465        return
466
467class AccessCodeLink(LeftSidebarLink):
468    grok.order(1)
469    grok.require('waeup.Public')
470
471    def render(self):
472        if not IApplicantPrincipal.providedBy(self.request.principal):
473            return ''
474        access_code = getattr(self.request.principal,'access_code',None)
475        if access_code:
476            applicant_object = get_applicant_data(access_code)
477            url = absoluteURL(applicant_object, self.request)
478            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
479                url,access_code)
480        return ''
481
482class DisplayApplicant(WAeUPDisplayFormPage):
483    grok.context(IApplicant)
484    grok.name('index')
485    grok.require('waeup.handleApplication')
486    form_fields = grok.AutoFields(IApplicant).omit(
487        'locked').omit('course_admitted')
488    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
489    form_fields['passport'].custom_widget = ThumbnailWidget
490    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
491    label = 'Applicant'
492    grok.template('form_display')
493    pnav = 3
494
495    @property
496    def title(self):
497        return '%s' % self.context.access_code
498
499    @property
500    def label(self):
501        container_title = self.context.__parent__.title
502        return '%s Application Record' % container_title
503
504    def getCourseAdmitted(self):
505        """Return link, title and code in html format to the certificate
506           admitted.
507        """
508        course_admitted = self.context.course_admitted
509        if ICertificate.providedBy(course_admitted):
510            url = self.url(course_admitted)
511            title = course_admitted.title
512            code = course_admitted.code
513            return '<a href="%s">%s (%s)</a>' %(url,title,code)
514        return 'not yet admitted'
515
516class ApplicantsManageActionButton(ManageActionButton):
517    grok.context(IApplicant)
518    grok.view(DisplayApplicant)
519    grok.require('waeup.manageApplications')
520    text = 'Edit application record'
521    target = 'edit_full'
522
523class EditApplicantFull(WAeUPEditFormPage):
524    """A full edit view for applicant data.
525    """
526    grok.context(IApplicant)
527    grok.name('edit_full')
528    grok.require('waeup.manageApplications')
529    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
530    form_fields['passport'].custom_widget = EncodingImageFileWidget
531    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
532    grok.template('form_edit')
533    manage_applications = True
534    pnav = 3
535
536    def update(self):
537        datepicker.need() # Enable jQuery datepicker in date fields.
538        super(EditApplicantFull, self).update()
539        self.wf_info = IWorkflowInfo(self.context)
540        return
541
542    @property
543    def title(self):
544        return self.context.access_code
545
546    @property
547    def label(self):
548        container_title = self.context.__parent__.title
549        return '%s Application Form' % container_title
550
551    def getTransitions(self):
552        """Return a list of dicts of allowed transition ids and titles.
553
554        Each list entry provides keys ``name`` and ``title`` for
555        internal name and (human readable) title of a single
556        transition.
557        """
558        allowed_transitions = self.wf_info.getManualTransitions()
559        return [dict(name='', title='No transition')] +[
560            dict(name=x, title=y) for x, y in allowed_transitions]
561
562    @grok.action('Save')
563    def save(self, **data):
564        self.applyData(self.context, **data)
565        self.context._p_changed = True
566        form = self.request.form
567        if form.has_key('transition') and form['transition']:
568            transition_id = form['transition']
569            self.wf_info.fireTransition(transition_id)
570        self.flash('Form has been saved.')
571        self.context.getApplicantsRootLogger().info('Saved')
572        return
573
574class EditApplicantStudent(EditApplicantFull):
575    """An applicant-centered edit view for applicant data.
576    """
577    grok.context(IApplicantEdit)
578    grok.name('edit')
579    grok.require('waeup.handleApplication')
580    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
581    form_fields['passport'].custom_widget = EncodingImageFileWidget
582    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
583    grok.template('form_edit')
584    manage_applications = False
585
586
587    def emitLockMessage(self):
588        self.flash('The requested form is locked (read-only).')
589        self.redirect(self.url(self.context))
590        return
591
592    def update(self):
593        if self.context.locked:
594            self.redirect(self.url(self.context))
595            return
596        datepicker.need() # Enable jQuery datepicker in date fields.
597        super(EditApplicantStudent, self).update()
598        return
599
600    def dataNotComplete(self):
601        if not self.request.form.get('confirm_passport', False):
602            return 'Passport confirmation box not ticked.'
603        if len(self.errors) > 0:
604            return 'Form has errors.'
605        return False
606
607    @grok.action('Save')
608    def save(self, **data):
609        if self.context.locked:
610            self.emitLockMessage()
611            return
612        self.applyData(self.context, **data)
613        self.context._p_changed = True
614        self.flash('Form has been saved.')
615        return
616
617    @grok.action('Final Submit')
618    def finalsubmit(self, **data):
619        if self.context.locked:
620            self.emitLockMessage()
621            return
622        self.applyData(self.context, **data)
623        self.context._p_changed = True
624        if self.dataNotComplete():
625            self.flash(self.dataNotComplete())
626            return
627        state = IWorkflowState(self.context).getState()
628        # This shouldn't happen, but the application officer
629        # might have forgotten to lock the form after changing the state
630        if state != STARTED:
631            self.flash('This form cannot be submitted. Wrong state!')
632            return
633        IWorkflowInfo(self.context).fireTransition('submit')
634        self.context.locked = True
635        self.flash('Form has been submitted.')
636        self.redirect(self.url(self.context))
637        return
638
Note: See TracBrowser for help on using the repository browser.