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

Last change on this file since 6346 was 6336, checked in by Henrik Bettermann, 14 years ago

Provide application login button (anonymous users only).

File size: 21.8 KB
Line 
1##
2## browser.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet & Henrik Bettermann
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""UI components for basic applicants and related components.
23"""
24import sys
25import grok
26
27from datetime import datetime
28from zope.formlib.widget import CustomWidgetFactory
29from zope.securitypolicy.interfaces import IPrincipalRoleManager
30from zope.traversing.browser import absoluteURL
31
32from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
33
34from waeup.sirp.browser import (
35    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
36from waeup.sirp.browser.breadcrumbs import Breadcrumb
37from waeup.sirp.browser.layout import NullValidator
38from waeup.sirp.browser.pages import add_local_role, del_local_roles
39from waeup.sirp.browser.resources import datepicker, tabs, datatable
40from waeup.sirp.browser.viewlets import (
41    ManageActionButton, PrimaryNavTab, LeftSidebarLink
42    )
43from waeup.sirp.image.browser.widget import (
44    ThumbnailWidget, EncodingImageFileWidget,
45    )
46from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
47from waeup.sirp.permissions import get_users_with_local_roles
48from waeup.sirp.university.interfaces import ICertificate
49from waeup.sirp.widgets.datewidget import (
50    FriendlyDateWidget, FriendlyDateDisplayWidget)
51from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
52from waeup.sirp.widgets.objectwidget import (
53    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
54from waeup.sirp.widgets.multilistwidget import (
55    MultiListWidget, MultiListDisplayWidget)
56from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
57from waeup.sirp.applicants.interfaces import (
58    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
59    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
60    )
61from waeup.sirp.applicants.workflow import create_workflow, INITIALIZED, STARTED
62
63
64results_widget = CustomWidgetFactory(
65    WAeUPObjectWidget, ResultEntry)
66
67results_display_widget = CustomWidgetFactory(
68    WAeUPObjectDisplayWidget, ResultEntry)
69
70list_results_widget = CustomWidgetFactory(
71    MultiListWidget, subwidget=results_widget)
72
73list_results_display_widget = CustomWidgetFactory(
74    MultiListDisplayWidget, subwidget=results_display_widget)
75
76TRANSITION_OBJECTS = create_workflow()
77
78#TRANSITION_DICT = dict([
79#    (transition_object.transition_id,transition_object.title)
80#    for transition_object in TRANSITION_OBJECTS])
81
82class ApplicantsRootPage(WAeUPPage):
83    grok.context(IApplicantsRoot)
84    grok.name('index')
85    grok.require('waeup.Public')
86    title = 'Applicants'
87    label = 'Application Section'
88    pnav = 3
89
90    def update(self):
91        super(ApplicantsRootPage, self).update()
92        datatable.need()
93        return
94
95class ManageApplicantsRootActionButton(ManageActionButton):
96    grok.context(IApplicantsRoot)
97    grok.view(ApplicantsRootPage)
98    grok.require('waeup.manageApplications')
99    text = 'Manage application section'
100
101class ApplicantsRootManageFormPage(WAeUPEditFormPage):
102    grok.context(IApplicantsRoot)
103    grok.name('manage')
104    grok.template('applicantsrootmanagepage')
105    title = 'Applicants'
106    label = 'Manage application section'
107    pnav = 3
108    grok.require('waeup.manageApplications')
109    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
110    tabtwoactions1 = ['Remove selected local roles']
111    tabtwoactions2 = ['Add local role']
112    subunits = 'Applicants Containers'
113
114    def update(self):
115        tabs.need()
116        datatable.need()
117        return super(ApplicantsRootManageFormPage, self).update()
118
119    def getLocalRoles(self):
120        roles = ILocalRolesAssignable(self.context)
121        return roles()
122
123    def getUsers(self):
124        """Get a list of all users.
125        """
126        for key, val in grok.getSite()['users'].items():
127            url = self.url(val)
128            yield(dict(url=url, name=key, val=val))
129
130    def getUsersWithLocalRoles(self):
131        return get_users_with_local_roles(self.context)
132
133    # ToDo: Show warning message before deletion
134    @grok.action('Remove selected')
135    def delApplicantsContainers(self, **data):
136        form = self.request.form
137        child_id = form['val_id']
138        if not isinstance(child_id, list):
139            child_id = [child_id]
140        deleted = []
141        for id in child_id:
142            try:
143                del self.context[id]
144                deleted.append(id)
145            except:
146                self.flash('Could not delete %s: %s: %s' % (
147                        id, sys.exc_info()[0], sys.exc_info()[1]))
148        if len(deleted):
149            self.flash('Successfully removed: %s' % ', '.join(deleted))
150        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
151        return
152
153    @grok.action('Add applicants container', validator=NullValidator)
154    def addApplicantsContainer(self, **data):
155        self.redirect(self.url(self.context, '@@add'))
156        return
157
158    @grok.action('Cancel', validator=NullValidator)
159    def cancel(self, **data):
160        self.redirect(self.url(self.context))
161        return
162
163    @grok.action('Add local role', validator=NullValidator)
164    def addLocalRole(self, **data):
165        return add_local_role(self,2, **data)
166
167    @grok.action('Remove selected local roles')
168    def delLocalRoles(self, **data):
169        return del_local_roles(self,2,**data)
170
171class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
172    grok.context(IApplicantsRoot)
173    grok.require('waeup.manageApplications')
174    grok.name('add')
175    grok.template('applicantscontaineraddpage')
176    title = 'Applicants'
177    label = 'Add applicants container'
178    pnav = 3
179
180    form_fields = grok.AutoFields(
181        IApplicantsContainerAdd).omit('code').omit('title')
182    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
183    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
184
185    def update(self):
186        datepicker.need() # Enable jQuery datepicker in date fields.
187        #from waeup.sirp.browser.resources import jqueryui
188        #jqueryui.need()
189        return super(ApplicantsContainerAddFormPage, self).update()
190
191    @grok.action('Add applicants container')
192    def addApplicantsContainer(self, **data):
193        year = data['year']
194        code = u'%s%s' % (data['prefix'], year)
195        prefix = application_types_vocab.getTerm(data['prefix'])
196        title = u'%s %s/%s' % (prefix.title, year, year + 1)
197        if code in self.context.keys():
198            self.flash(
199                'An applicants container for the same application '
200                'type and entrance year exists already in the database.')
201            return
202        # Add new applicants container...
203        provider = data['provider'][1]
204        container = provider.factory()
205        self.applyData(container, **data)
206        container.code = code
207        container.title = title
208        self.context[code] = container
209        self.flash('Added: "%s".' % code)
210        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
211        return
212
213    @grok.action('Cancel', validator=NullValidator)
214    def cancel(self, **data):
215        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
216
217class ApplicantsRootBreadcrumb(Breadcrumb):
218    """A breadcrumb for applicantsroot.
219    """
220    grok.context(IApplicantsRoot)
221    title = u'Application Section'
222
223class ApplicantsContainerBreadcrumb(Breadcrumb):
224    """A breadcrumb for applicantscontainers.
225    """
226    grok.context(IApplicantsContainer)
227
228class ApplicantBreadcrumb(Breadcrumb):
229    """A breadcrumb for applicants.
230    """
231    grok.context(IApplicant)
232
233    @property
234    def title(self):
235        """Get a title for a context.
236        """
237        return self.context.access_code
238
239class ApplicantsTab(PrimaryNavTab):
240    """Applicants tab in primary navigation.
241    """
242
243    grok.context(IWAeUPObject)
244    grok.order(3)
245    grok.require('waeup.Public')
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.order(1)
279    grok.context(IApplicantsContainer)
280    grok.view(ApplicantsContainerPage)
281    grok.require('waeup.manageApplications')
282    text = 'Manage applicants container'
283
284class LoginApplicantActionButton(ManageActionButton):
285    grok.order(2)
286    grok.context(IApplicantsContainer)
287    grok.view(ApplicantsContainerPage)
288    grok.require('waeup.Anonymous')
289    icon = 'login.png'
290    text = 'Login for applicants'
291    target = 'login'
292
293class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
294    grok.context(IApplicantsContainer)
295    grok.name('manage')
296    grok.template('applicantscontainermanagepage')
297    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
298    taboneactions = ['Save','Cancel']
299    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
300    tabthreeactions1 = ['Remove selected local roles']
301    tabthreeactions2 = ['Add local role']
302    # Use friendlier date widget...
303    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
304    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
305    grok.require('waeup.manageApplications')
306
307    @property
308    def title(self):
309        return "Applicants Container: %s" % self.context.title
310
311    @property
312    def label(self):
313        return 'Manage applicants container'
314
315    pnav = 3
316
317    def update(self):
318        datepicker.need() # Enable jQuery datepicker in date fields.
319        tabs.need()
320        datatable.need()  # Enable jQurey datatables for contents listing
321        return super(ApplicantsContainerManageFormPage, self).update()
322
323    def getLocalRoles(self):
324        roles = ILocalRolesAssignable(self.context)
325        return roles()
326
327    def getUsers(self):
328        """Get a list of all users.
329        """
330        for key, val in grok.getSite()['users'].items():
331            url = self.url(val)
332            yield(dict(url=url, name=key, val=val))
333
334    def getUsersWithLocalRoles(self):
335        return get_users_with_local_roles(self.context)
336
337    @grok.action('Save')
338    def apply(self, **data):
339        self.applyData(self.context, **data)
340        self.flash('Data saved.')
341        return
342
343    # ToDo: Show warning message before deletion
344    @grok.action('Remove selected')
345    def delApplicant(self, **data):
346        form = self.request.form
347        if form.has_key('val_id'):
348            child_id = form['val_id']
349        else:
350            self.flash('No applicant selected!')
351            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
352            return
353        if not isinstance(child_id, list):
354            child_id = [child_id]
355        deleted = []
356        for id in child_id:
357            try:
358                del self.context[id]
359                deleted.append(id)
360            except:
361                self.flash('Could not delete %s: %s: %s' % (
362                        id, sys.exc_info()[0], sys.exc_info()[1]))
363        if len(deleted):
364            self.flash('Successfully removed: %s' % ', '.join(deleted))
365        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
366        return
367
368    @grok.action('Add applicant', validator=NullValidator)
369    def addApplicant(self, **data):
370        self.redirect(self.url(self.context, 'addapplicant'))
371        return
372
373    @grok.action('Cancel', validator=NullValidator)
374    def cancel(self, **data):
375        self.redirect(self.url(self.context))
376        return
377
378    @grok.action('Add local role', validator=NullValidator)
379    def addLocalRole(self, **data):
380        return add_local_role(self,3, **data)
381
382    @grok.action('Remove selected local roles')
383    def delLocalRoles(self, **data):
384        return del_local_roles(self,3,**data)
385
386class LoginApplicant(WAeUPPage):
387    grok.context(IApplicantsContainer)
388    grok.name('login')
389    grok.require('waeup.Public')
390
391    @property
392    def title(self):
393        return u"Applicant Login: %s" % self.context.title
394
395    @property
396    def label(self):
397        return u'Login for applicants only'
398
399    pnav = 3
400
401    @property
402    def ac_prefix(self):
403        return self.context.ac_prefix
404
405    def update(self, SUBMIT=None):
406        self.ac_series = self.request.form.get('form.ac_series', None)
407        self.ac_number = self.request.form.get('form.ac_number', None)
408        if SUBMIT is None:
409            return
410        if self.request.principal.id == 'zope.anybody':
411            self.flash('Entered credentials are invalid.')
412            return
413        if not IApplicantPrincipal.providedBy(self.request.principal):
414            # Don't care if user is already authenticated as non-applicant
415            return
416        pin = self.request.principal.access_code
417        if pin not in self.context.keys():
418            # Create applicant record
419            applicant = Applicant()
420            applicant.access_code = pin
421            self.context[pin] = applicant
422        # Assign current principal the owner role on created applicant
423        # record
424        role_manager = IPrincipalRoleManager(self.context[pin])
425        role_manager.assignRoleToPrincipal(
426            'waeup.local.ApplicationOwner', self.request.principal.id)
427        # Assign current principal the PortalUser role
428        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
429        role_manager.assignRoleToPrincipal(
430            'waeup.PortalUser', self.request.principal.id)
431        state = IWorkflowState(self.context[pin]).getState()
432        if state == INITIALIZED:
433            IWorkflowInfo(self.context[pin]).fireTransition('start')
434        self.redirect(self.url(self.context[pin], 'edit'))
435        return
436
437class ApplicantAddFormPage(WAeUPAddFormPage):
438    """Add-form to add certificate to a department.
439    """
440    grok.context(IApplicantsContainer)
441    grok.require('waeup.manageApplications')
442    grok.name('addapplicant')
443    grok.template('applicantaddpage')
444    title = 'Applicants'
445    label = 'Add applicant'
446    pnav = 3
447
448    @property
449    def title(self):
450        return "Applicants Container: %s" % self.context.title
451
452    @property
453    def ac_prefix(self):
454        return self.context.ac_prefix
455
456    @grok.action('Create application record')
457    def addApplicant(self, **data):
458        ac_series = self.request.form.get('form.ac_series', None)
459        ac_number = self.request.form.get('form.ac_number', None)
460        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
461        if pin not in self.context.keys():
462            # Create applicant record
463            applicant = Applicant()
464            applicant.access_code = pin
465            self.context[pin] = applicant
466        self.redirect(self.url(self.context[pin], 'edit'))
467        return
468
469class AccessCodeLink(LeftSidebarLink):
470    grok.order(1)
471    grok.require('waeup.Public')
472
473    def render(self):
474        if not IApplicantPrincipal.providedBy(self.request.principal):
475            return ''
476        access_code = getattr(self.request.principal,'access_code',None)
477        if access_code:
478            applicant_object = get_applicant_data(access_code)
479            url = absoluteURL(applicant_object, self.request)
480            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
481                url,access_code)
482        return ''
483
484class DisplayApplicant(WAeUPDisplayFormPage):
485    grok.context(IApplicant)
486    grok.name('index')
487    grok.require('waeup.handleApplication')
488    form_fields = grok.AutoFields(IApplicant).omit(
489        'locked').omit('course_admitted')
490    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
491    form_fields['passport'].custom_widget = ThumbnailWidget
492    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
493    label = 'Applicant'
494    grok.template('form_display')
495    pnav = 3
496
497    @property
498    def title(self):
499        return '%s' % self.context.access_code
500
501    @property
502    def label(self):
503        container_title = self.context.__parent__.title
504        return '%s Application Record' % container_title
505
506    @property
507    def getCourseAdmitted(self):
508        course_admitted = self.context.course_admitted
509        #import pdb; pdb.set_trace()
510        if ICertificate.providedBy(course_admitted):
511            url = self.url(course_admitted)
512            title = course_admitted.title
513            code = course_admitted.code
514            return '<a href="%s">%s (%s)</a>' %(url,title,code)
515        return 'not yet admitted'
516
517class ApplicantsManageActionButton(ManageActionButton):
518    grok.context(IApplicant)
519    grok.view(DisplayApplicant)
520    grok.require('waeup.manageApplications')
521    text = 'Edit application record'
522    target = 'edit_full'
523
524class EditApplicantFull(WAeUPEditFormPage):
525    """A full edit view for applicant data.
526    """
527    grok.context(IApplicant)
528    grok.name('edit_full')
529    grok.require('waeup.manageApplications')
530    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
531    form_fields['passport'].custom_widget = EncodingImageFileWidget
532    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
533    grok.template('form_edit')
534    manage_applications = True
535    pnav = 3
536
537    def update(self):
538        datepicker.need() # Enable jQuery datepicker in date fields.
539        super(EditApplicantFull, self).update()
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    @property
552    def getTransitions(self):
553        allowed_transitions_ids = IWorkflowInfo(
554            self.context).getManualTransitionIds()
555        null_transition = [{'name': '', 'title':'No transition'}]
556        transitions = null_transition + [dict(
557            name=transition_object.transition_id,
558            title=transition_object.title)
559            for transition_object in TRANSITION_OBJECTS
560            if transition_object.transition_id in allowed_transitions_ids]
561        return transitions
562
563    @grok.action('Save')
564    def save(self, **data):
565        self.applyData(self.context, **data)
566        self.context._p_changed = True
567        form = self.request.form
568        if form.has_key('transition') and form['transition']:
569            transition_id = form['transition']
570            IWorkflowInfo(self.context).fireTransition(transition_id)
571        self.flash('Form has been 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.