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

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

Cut overlong lines.

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