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

Last change on this file since 6321 was 6321, checked in by uli, 13 years ago

Remove unused imports and reorder a bit.

File size: 20.6 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
78TRANSITION_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        state = IWorkflowState(self.context[pin]).getState()
419        if state == INITIALIZED:
420            IWorkflowInfo(self.context[pin]).fireTransition('start')
421            timestamp = datetime.now().strftime("%d/%m/%Y %H:%M")
422            transition_title = TRANSITION_DICT['start']
423            self.context[pin].messages += '<br />%s - %s' % (
424                timestamp,transition_title)
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    pnav = 3
499
500    def update(self):
501        datepicker.need() # Enable jQuery datepicker in date fields.
502        super(EditApplicantFull, self).update()
503        return
504
505    @property
506    def title(self):
507        return self.context.access_code
508
509    @property
510    def label(self):
511        container_title = self.context.__parent__.title
512        return '%s Application Form' % container_title
513
514    @property
515    def getTransitions(self):
516        allowed_transitions_ids = IWorkflowInfo(
517            self.context).getManualTransitionIds()
518        null_transition = [{'name': '', 'title':'No transition'}]
519        transitions = null_transition + [dict(
520            name=transition_object.transition_id,
521            title=transition_object.title)
522            for transition_object in TRANSITION_OBJECTS
523            if transition_object.transition_id in allowed_transitions_ids]
524        #import pdb; pdb.set_trace()
525        return transitions
526
527    @property
528    def getApplicationState(self):
529        state = IWorkflowState(self.context).getState()
530        return state
531
532    @grok.action('Save')
533    def save(self, **data):
534        self.applyData(self.context, **data)
535        self.context._p_changed = True
536        form = self.request.form
537        if form.has_key('transition') and form['transition']:
538            transition_id = form['transition']
539            IWorkflowInfo(self.context).fireTransition(transition_id)
540        self.flash('Form has been saved.')
541        return
542
543class EditApplicantStudent(EditApplicantFull):
544    """An applicant-centered edit view for applicant data.
545    """
546    grok.context(IApplicantEdit)
547    grok.name('edit')
548    grok.require('waeup.handleApplication')
549    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
550    form_fields['passport'].custom_widget = EncodingImageFileWidget
551    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
552    grok.template('form_edit')
553
554    def emitLockMessage(self):
555        self.flash('The requested form is locked (read-only).')
556        self.redirect(self.url(self.context))
557        return
558
559    def update(self):
560        if self.context.locked:
561            self.redirect(self.url(self.context))
562            return
563        datepicker.need() # Enable jQuery datepicker in date fields.
564        super(EditApplicantStudent, self).update()
565        return
566
567    def dataNotComplete(self):
568        if self.context.confirm_passport is not True:
569            return 'Passport confirmation box not ticked.'
570        if len(self.errors) > 0:
571            return 'Form has errors.'
572        return False
573
574    @property
575    def getTransitions(self):
576        return None
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        # Cannot happen but anyway ...
600        if state != STARTED:
601            self.flash('This form cannot be submitted.')
602            return
603        IWorkflowInfo(self.context).fireTransition('submit')
604        self.context.locked = True
605        self.flash('Form has been submitted.')
606        self.redirect(self.url(self.context))
607        return
608
Note: See TracBrowser for help on using the repository browser.