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

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

First test implementation for applicants logger. Uli, please check.

File size: 21.9 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
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
75TRANSITION_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        #from waeup.sirp.browser.resources import jqueryui
187        #jqueryui.need()
188        return super(ApplicantsContainerAddFormPage, self).update()
189
190    @grok.action('Add applicants container')
191    def addApplicantsContainer(self, **data):
192        year = data['year']
193        code = u'%s%s' % (data['prefix'], year)
194        prefix = application_types_vocab.getTerm(data['prefix'])
195        title = u'%s %s/%s' % (prefix.title, year, year + 1)
196        if code in self.context.keys():
197            self.flash(
198                'An applicants container for the same application '
199                'type and entrance year exists already in the database.')
200            return
201        # Add new applicants container...
202        provider = data['provider'][1]
203        container = provider.factory()
204        self.applyData(container, **data)
205        container.code = code
206        container.title = title
207        self.context[code] = container
208        self.flash('Added: "%s".' % code)
209        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
210        return
211
212    @grok.action('Cancel', validator=NullValidator)
213    def cancel(self, **data):
214        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
215
216class ApplicantsRootBreadcrumb(Breadcrumb):
217    """A breadcrumb for applicantsroot.
218    """
219    grok.context(IApplicantsRoot)
220    title = u'Application Section'
221
222class ApplicantsContainerBreadcrumb(Breadcrumb):
223    """A breadcrumb for applicantscontainers.
224    """
225    grok.context(IApplicantsContainer)
226
227class ApplicantBreadcrumb(Breadcrumb):
228    """A breadcrumb for applicants.
229    """
230    grok.context(IApplicant)
231
232    @property
233    def title(self):
234        """Get a title for a context.
235        """
236        return self.context.access_code
237
238class ApplicantsTab(PrimaryNavTab):
239    """Applicants tab in primary navigation.
240    """
241
242    grok.context(IWAeUPObject)
243    grok.order(3)
244    grok.require('waeup.Public')
245    grok.template('primarynavtab')
246
247    pnav = 3
248    tab_title = u'Applicants'
249
250    @property
251    def link_target(self):
252        return self.view.application_url('applicants')
253
254class ApplicantsContainerPage(WAeUPDisplayFormPage):
255    """The standard view for regular applicant containers.
256    """
257    grok.context(IApplicantsContainer)
258    grok.name('index')
259    grok.require('waeup.Public')
260    grok.template('applicantscontainerpage')
261    pnav = 3
262
263    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
264    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
265    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
266    form_fields['description'].custom_widget = ReSTDisplayWidget
267
268    @property
269    def title(self):
270        return "Applicants Container: %s" % self.context.title
271
272    @property
273    def label(self):
274        return self.context.title
275
276class ApplicantsContainerManageActionButton(ManageActionButton):
277    grok.order(1)
278    grok.context(IApplicantsContainer)
279    grok.view(ApplicantsContainerPage)
280    grok.require('waeup.manageApplications')
281    text = 'Manage applicants container'
282
283class LoginApplicantActionButton(ManageActionButton):
284    grok.order(2)
285    grok.context(IApplicantsContainer)
286    grok.view(ApplicantsContainerPage)
287    grok.require('waeup.Anonymous')
288    icon = 'login.png'
289    text = 'Login for applicants'
290    target = 'login'
291
292class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
293    grok.context(IApplicantsContainer)
294    grok.name('manage')
295    grok.template('applicantscontainermanagepage')
296    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
297    taboneactions = ['Save','Cancel']
298    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
299    tabthreeactions1 = ['Remove selected local roles']
300    tabthreeactions2 = ['Add local role']
301    # Use friendlier date widget...
302    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
303    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
304    grok.require('waeup.manageApplications')
305
306    @property
307    def title(self):
308        return "Applicants Container: %s" % self.context.title
309
310    @property
311    def label(self):
312        return 'Manage applicants container'
313
314    pnav = 3
315
316    def update(self):
317        datepicker.need() # Enable jQuery datepicker in date fields.
318        tabs.need()
319        datatable.need()  # Enable jQurey datatables for contents listing
320        return super(ApplicantsContainerManageFormPage, self).update()
321
322    def getLocalRoles(self):
323        roles = ILocalRolesAssignable(self.context)
324        return roles()
325
326    def getUsers(self):
327        """Get a list of all users.
328        """
329        for key, val in grok.getSite()['users'].items():
330            url = self.url(val)
331            yield(dict(url=url, name=key, val=val))
332
333    def getUsersWithLocalRoles(self):
334        return get_users_with_local_roles(self.context)
335
336    @grok.action('Save')
337    def apply(self, **data):
338        self.applyData(self.context, **data)
339        self.flash('Data saved.')
340        return
341
342    # ToDo: Show warning message before deletion
343    @grok.action('Remove selected')
344    def delApplicant(self, **data):
345        form = self.request.form
346        if form.has_key('val_id'):
347            child_id = form['val_id']
348        else:
349            self.flash('No applicant selected!')
350            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
351            return
352        if not isinstance(child_id, list):
353            child_id = [child_id]
354        deleted = []
355        for id in child_id:
356            try:
357                del self.context[id]
358                deleted.append(id)
359            except:
360                self.flash('Could not delete %s: %s: %s' % (
361                        id, sys.exc_info()[0], sys.exc_info()[1]))
362        if len(deleted):
363            self.flash('Successfully removed: %s' % ', '.join(deleted))
364        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
365        return
366
367    @grok.action('Add applicant', validator=NullValidator)
368    def addApplicant(self, **data):
369        self.redirect(self.url(self.context, 'addapplicant'))
370        return
371
372    @grok.action('Cancel', validator=NullValidator)
373    def cancel(self, **data):
374        self.redirect(self.url(self.context))
375        return
376
377    @grok.action('Add local role', validator=NullValidator)
378    def addLocalRole(self, **data):
379        return add_local_role(self,3, **data)
380
381    @grok.action('Remove selected local roles')
382    def delLocalRoles(self, **data):
383        return del_local_roles(self,3,**data)
384
385class LoginApplicant(WAeUPPage):
386    grok.context(IApplicantsContainer)
387    grok.name('login')
388    grok.require('waeup.Public')
389
390    @property
391    def title(self):
392        return u"Applicant Login: %s" % self.context.title
393
394    @property
395    def label(self):
396        return u'Login for applicants only'
397
398    pnav = 3
399
400    @property
401    def ac_prefix(self):
402        return self.context.ac_prefix
403
404    def update(self, SUBMIT=None):
405        self.ac_series = self.request.form.get('form.ac_series', None)
406        self.ac_number = self.request.form.get('form.ac_number', None)
407        if SUBMIT is None:
408            return
409        if self.request.principal.id == 'zope.anybody':
410            self.flash('Entered credentials are invalid.')
411            return
412        if not IApplicantPrincipal.providedBy(self.request.principal):
413            # Don't care if user is already authenticated as non-applicant
414            return
415        pin = self.request.principal.access_code
416        if pin not in self.context.keys():
417            # Create applicant record
418            applicant = Applicant()
419            applicant.access_code = pin
420            self.context[pin] = applicant
421        # Assign current principal the owner role on created applicant
422        # record
423        role_manager = IPrincipalRoleManager(self.context[pin])
424        role_manager.assignRoleToPrincipal(
425            'waeup.local.ApplicationOwner', self.request.principal.id)
426        # Assign current principal the PortalUser role
427        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
428        role_manager.assignRoleToPrincipal(
429            'waeup.PortalUser', self.request.principal.id)
430        state = IWorkflowState(self.context[pin]).getState()
431        if state == INITIALIZED:
432            IWorkflowInfo(self.context[pin]).fireTransition('start')
433        self.redirect(self.url(self.context[pin], 'edit'))
434        return
435
436class ApplicantAddFormPage(WAeUPAddFormPage):
437    """Add-form to add certificate to a department.
438    """
439    grok.context(IApplicantsContainer)
440    grok.require('waeup.manageApplications')
441    grok.name('addapplicant')
442    grok.template('applicantaddpage')
443    title = 'Applicants'
444    label = 'Add applicant'
445    pnav = 3
446
447    @property
448    def title(self):
449        return "Applicants Container: %s" % self.context.title
450
451    @property
452    def ac_prefix(self):
453        return self.context.ac_prefix
454
455    @grok.action('Create application record')
456    def addApplicant(self, **data):
457        ac_series = self.request.form.get('form.ac_series', None)
458        ac_number = self.request.form.get('form.ac_number', None)
459        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
460        if pin not in self.context.keys():
461            # Create applicant record
462            applicant = Applicant()
463            applicant.access_code = pin
464            self.context[pin] = applicant
465        self.redirect(self.url(self.context[pin], 'edit'))
466        return
467
468class AccessCodeLink(LeftSidebarLink):
469    grok.order(1)
470    grok.require('waeup.Public')
471
472    def render(self):
473        if not IApplicantPrincipal.providedBy(self.request.principal):
474            return ''
475        access_code = getattr(self.request.principal,'access_code',None)
476        if access_code:
477            applicant_object = get_applicant_data(access_code)
478            url = absoluteURL(applicant_object, self.request)
479            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
480                url,access_code)
481        return ''
482
483class DisplayApplicant(WAeUPDisplayFormPage):
484    grok.context(IApplicant)
485    grok.name('index')
486    grok.require('waeup.handleApplication')
487    form_fields = grok.AutoFields(IApplicant).omit(
488        'locked').omit('course_admitted')
489    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
490    form_fields['passport'].custom_widget = ThumbnailWidget
491    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
492    label = 'Applicant'
493    grok.template('form_display')
494    pnav = 3
495
496    @property
497    def title(self):
498        return '%s' % self.context.access_code
499
500    @property
501    def label(self):
502        container_title = self.context.__parent__.title
503        return '%s Application Record' % container_title
504
505    @property
506    def getCourseAdmitted(self):
507        course_admitted = self.context.course_admitted
508        #import pdb; pdb.set_trace()
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        return
540
541    @property
542    def title(self):
543        return self.context.access_code
544
545    @property
546    def label(self):
547        container_title = self.context.__parent__.title
548        return '%s Application Form' % container_title
549
550    @property
551    def getTransitions(self):
552        allowed_transitions_ids = IWorkflowInfo(
553            self.context).getManualTransitionIds()
554        null_transition = [{'name': '', 'title':'No transition'}]
555        transitions = null_transition + [dict(
556            name=transition_object.transition_id,
557            title=transition_object.title)
558            for transition_object in TRANSITION_OBJECTS
559            if transition_object.transition_id in allowed_transitions_ids]
560        return 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            IWorkflowInfo(self.context).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        #import pdb; pdb.set_trace()
602        if not self.request.form.get('confirm_passport', False):
603            return 'Passport confirmation box not ticked.'
604        if len(self.errors) > 0:
605            return 'Form has errors.'
606        return False
607
608    @grok.action('Save')
609    def save(self, **data):
610        if self.context.locked:
611            self.emitLockMessage()
612            return
613        self.applyData(self.context, **data)
614        self.context._p_changed = True
615        self.flash('Form has been saved.')
616        return
617
618    @grok.action('Final Submit')
619    def finalsubmit(self, **data):
620        if self.context.locked:
621            self.emitLockMessage()
622            return
623        self.applyData(self.context, **data)
624        self.context._p_changed = True
625        if self.dataNotComplete():
626            self.flash(self.dataNotComplete())
627            return
628        state = IWorkflowState(self.context).getState()
629        # This shouldn't happen, but the application officer
630        # might have forgotten to lock the form after changing the state
631        if state != STARTED:
632            self.flash('This form cannot be submitted. Wrong state!')
633            return
634        IWorkflowInfo(self.context).fireTransition('submit')
635        self.context.locked = True
636        self.flash('Form has been submitted.')
637        self.redirect(self.url(self.context))
638        return
639
Note: See TracBrowser for help on using the repository browser.