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

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

Add a confirm_passport attribute to applicants and reflect that change in form.

File size: 13.7 KB
Line 
1##
2## browser.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet
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 grok
25
26from datetime import datetime
27from hurry.jquery import jquery
28from hurry.jqueryui import jqueryui
29from zope.component import getUtility, getAllUtilitiesRegisteredFor
30from zope.formlib.widgets import FileWidget, DateWidget
31from zope.securitypolicy.interfaces import IPrincipalRoleManager
32from waeup.sirp.browser import (
33    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage,
34    WAeUPDisplayFormPage, NullValidator)
35from waeup.sirp.browser.pages import LoginPage
36from waeup.sirp.interfaces import IWAeUPObject
37from waeup.sirp.browser.resources import datepicker
38from waeup.sirp.browser.viewlets import (
39    AddActionButton, ManageActionButton, PrimaryNavTab,
40    )
41from waeup.sirp.browser.breadcrumbs import Breadcrumb
42from waeup.sirp.applicants import get_applicant_data, ResultEntry, Applicant
43from waeup.sirp.applicants.interfaces import (
44    IApplicant, IApplicantPrincipal, IApplicantPDEEditData,
45    IApplicantsRoot, IApplicantsContainer, IApplicantsContainerProvider,
46    )
47from waeup.sirp.widgets.passportwidget import (
48    PassportWidget, PassportDisplayWidget
49    )
50#from zope.formlib.objectwidget import ObjectWidget
51from zope.formlib.sequencewidget import ListSequenceWidget, SequenceDisplayWidget
52from zope.formlib.widget import CustomWidgetFactory
53from waeup.sirp.utils.helpers import ReST2HTML
54from waeup.sirp.widgets.objectwidget import (
55    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
56from waeup.sirp.widgets.multilistwidget import (
57    MultiListWidget, MultiListDisplayWidget)
58from waeup.sirp.image.browser.widget import (
59    ThumbnailWidget, EncodingImageFileWidget,
60    )
61
62results_widget = CustomWidgetFactory(
63    WAeUPObjectWidget, ResultEntry)
64
65results_display_widget = CustomWidgetFactory(
66    WAeUPObjectDisplayWidget, ResultEntry)
67
68#list_results_widget = CustomWidgetFactory(
69#    ListSequenceWidget, subwidget=results_widget)
70
71list_results_widget = CustomWidgetFactory(
72    MultiListWidget, subwidget=results_widget)
73
74list_results_display_widget = CustomWidgetFactory(
75    MultiListDisplayWidget, subwidget=results_display_widget)
76
77#: A date widget that renders with CSS class `datepicker` thus
78#  enabling : the jQuery datepicker for this field if the form loaded
79#  the jQuery code.
80FriendlyDateWidget = CustomWidgetFactory(
81    DateWidget, cssClass='datepicker')
82
83class ApplicationsPage(WAeUPPage):
84    grok.context(IApplicantsRoot)
85    grok.name('index')
86    title = 'Applicants'
87    pnav = 3
88   
89    def getApplications(self):
90        """Get a list of all stored applicant containers.
91        """
92        for key, val in self.context.items():
93            url = self.url(val)
94            yield(dict(url=url, name=key, container=val))
95
96class ManageApplicantsRootActionButton(ManageActionButton):
97    grok.context(IApplicantsRoot)
98    grok.view(ApplicationsPage)
99    text = 'Manage applicant containers'
100
101class ApplicantsRootEditPage(WAeUPPage):
102    grok.context(IApplicantsRoot)
103    grok.name('manage')
104    grok.template('applicantsrooteditpage')
105    title = 'Edit applicants containers'
106    pnav = 3
107
108    def update(self, entries=None, DELETE=None, CANCEL=None):
109        if CANCEL is not None:
110            self.redirect(self.url(self.context))
111            return
112        if DELETE is None:
113            return
114        if entries is None:
115            return
116        if not isinstance(entries, list):
117            entries = [entries]
118        for name in entries:
119            del self.context[name]
120            self.flash('Deleted "%s"' % name)
121        return
122
123    def getApplications(self):
124        """Get a list of all stored applicant containers.
125        """
126        for key, val in self.context.items():
127            url = self.url(val)
128            yield(dict(url=url, name=key))
129
130class AddApplicantsContainerActionButton(AddActionButton):
131    grok.context(IApplicantsRoot)
132    grok.view(ApplicantsRootEditPage)
133    text = 'Add applicants container'
134
135class AddApplicantsContainer(WAeUPPage):
136    grok.context(IApplicantsRoot)
137    grok.name('add')
138    grok.template('addcontainer')
139    title = 'Add applicants container'
140    pnav = 3
141
142    def update(self, providername=None, name=None, title=None,
143               description=None, ADD=None, CANCEL=None):
144        if CANCEL is not None:
145            self.redirect(self.url(self.context))
146            return
147        if ADD is None:
148            return
149        if not name:
150            self.flash('Error: you must give a name')
151            return
152        if name in self.context.keys():
153            self.flash('A container of the given name already exists')
154            return
155        # Add new applicants container...
156        provider = getUtility(IApplicantsContainerProvider,
157                              name=providername)
158        container = provider.factory()
159        if title:
160            container.title = title
161        if description:
162            container.description = description
163        self.context[name] = container
164        self.flash('Added "%s".' % name)
165        self.redirect(self.url(self.context))
166        return
167       
168    def getContainerProviders(self):
169        """Get a list of applicants container providers.
170
171        Applicants container providers are named utilities that help
172        to create applicants containers of different types
173        (JAMB-based, non-JAMB-based, etc.).
174
175        The list returned contains dicts::
176
177          {'name': <utility_name>,
178           'provider': <provider instance>}
179
180        where `utility_name` is the name under which the respective
181        provider utility is registered and `provider` is the real
182        provider instance.
183
184        The `utility_name` can be used to lookup the utility anew (for
185        instance after submitting a form) and the `provider` instance
186        can be used to create new instances of the respective
187        applicants container type.
188        """
189        providers = getAllUtilitiesRegisteredFor(IApplicantsContainerProvider)
190        result = [
191            {'name': getattr(x, 'grokcore.component.directive.name'),
192             'provider': x}
193             for x in providers
194            ]
195        return result
196
197class ApplicantsRootBreadcrumb(Breadcrumb):
198    """A breadcrumb for applicantsroot.
199    """
200    grok.context(IApplicantsRoot)
201    title = u'Applicants'
202   
203class ApplicantsContainerBreadcrumb(Breadcrumb):
204    """A breadcrumb for applicantscontainers.
205    """
206    grok.context(IApplicantsContainer)
207
208class ApplicantsTab(PrimaryNavTab):
209    """Faculties-tab in primary navigation.
210    """
211    grok.context(IWAeUPObject)
212    grok.order(3)
213    grok.require('waeup.View')
214    grok.template('primarynavtab')
215
216    pnav = 3
217    tab_title = u'Applicants'
218
219    @property
220    def link_target(self):
221        return self.view.application_url('applicants')
222
223class ApplicantsContainerPage(WAeUPPage):
224    """The standard view for regular applicant containers.
225    """
226    grok.context(IApplicantsContainer)
227    grok.name('index')
228    pnav = 3
229   
230    @property
231    def title(self):
232        return "Applicants Container: %s" % getattr(
233            self.context, '__name__', 'unnamed')
234
235    @property
236    def label(self):
237        return self.title
238
239    def descriptionToHTML(self):
240        return ReST2HTML(self.context.description)
241
242class ManageApplicantsContainerActionButton(ManageActionButton):
243    grok.context(IApplicantsContainer)
244    grok.view(ApplicantsContainerPage)
245    text = 'Manage'
246
247
248class ManageApplicantsContainer(WAeUPEditFormPage):
249    grok.context(IApplicantsContainer)
250    grok.name('manage')
251    form_fields = grok.AutoFields(IApplicantsContainer)
252    # Use friendlier date widget...
253    form_fields['startdate'].custom_widget = FriendlyDateWidget
254    form_fields['enddate'].custom_widget = FriendlyDateWidget
255
256    @property
257    def title(self):
258        return "Manage applicants container: %s" % getattr(
259            self.context, '__name__', 'unnamed')
260   
261    @property
262    def label(self):
263        return self.title
264
265    pnav = 3
266
267    def update(self):
268        datepicker.need() # Enable jQuery datepicker in date fields.
269        return super(ManageApplicantsContainer, self).update()
270
271    @grok.action('Save')
272    def apply(self, **data):
273        self.applyData(self.context, **data)
274        self.flash('Data saved.')
275        return
276       
277    @grok.action('Back')
278    def cancel(self, **data):
279        self.redirect(self.url(self.context))
280        return
281
282class LoginApplicant(WAeUPPage):
283    grok.context(IApplicantsContainer)
284    grok.name('login')
285    grok.require('zope.Public')
286
287    title = u'Login'
288   
289    @property
290    def label(self):
291        return self.title
292
293    pnav = 3
294    prefix = u'APP'
295   
296    def update(self, SUBMIT=None):
297        self.ac_series = self.request.form.get('form.ac_series', None)
298        self.ac_number = self.request.form.get('form.ac_number', None)
299        if SUBMIT is None:
300            return
301
302        if self.request.principal.id == 'zope.anybody':
303            self.flash('Entered credentials are invalid')
304            return
305
306        if not IApplicantPrincipal.providedBy(self.request.principal):
307            # Don't care if user is already authenticated as non-applicant
308            return
309
310        pin = self.request.principal.access_code
311        if pin not in self.context.keys():
312            # Create applicant record
313            applicant = Applicant()
314            applicant.access_code = pin
315            self.context[pin] = applicant
316           
317        # Assign current principal the owner role on created applicant
318        # record
319        role_manager = IPrincipalRoleManager(self.context)
320        role_manager.assignRoleToPrincipal(
321            'waeup.ApplicationOwner', self.request.principal.id)
322        self.redirect(self.url(self.context[pin], 'edit'))
323        return
324
325#class AddApplicant(WAeUPAddFormPage):
326#    grok.context(IApplicantsContainer)
327#    grok.name('add')
328#    form_fields = grok.AutoFields(IApplicant)
329#    form_fields['fst_sit_results'].custom_widget = list_results_widget
330#    form_fields['passport'].custom_widget = EncodingImageFileWidget
331#    label = 'Add Applicant'
332#    title = 'Add Applicant'
333#    pnav = 1
334#
335#    @grok.action('Add applicant')
336#    def addApplicant(self, **data):
337#        from waeup.sirp.jambtables.applicants import Applicant
338#        applicant = Applicant()
339#        self.applyData(applicant, **data)
340#        # XXX: temporarily disabled.
341#        #self.context[applicant.reg_no] = applicant
342#        try:
343#            self.context[applicant.access_code] = applicant
344#        except KeyError:
345#            self.flash('The given access code is already in use!')
346#            return
347#        self.redirect(self.url(self.context))
348
349class DisplayApplicant(WAeUPDisplayFormPage):
350    grok.context(IApplicant)
351    grok.name('index')
352    grok.require('waeup.viewApplication')
353    form_fields = grok.AutoFields(IApplicant).omit('locked')
354    form_fields['fst_sit_results'].custom_widget = list_results_display_widget
355    #form_fields['passport'].custom_widget = PassportDisplayWidget
356    form_fields['passport'].custom_widget = ThumbnailWidget
357    #form_fields['passport'].custom_widget = EncodingImageFileWidget
358    label = 'Applicant'
359    title = 'Applicant'
360    pnav = 3
361
362class EditApplicant(WAeUPEditFormPage):
363    grok.context(IApplicant)
364    grok.name('edit')
365    grok.require('waeup.editApplication')
366    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
367    #form_fields['passport'].custom_widget = FileWidget
368    #form_fields['passport'].custom_widget = PassportWidget
369    form_fields['passport'].custom_widget = EncodingImageFileWidget
370    grok.template('form_edit_pde')
371
372    def emitLockMessage(self):
373        self.flash('The requested form is locked (read-only)')
374        self.redirect(self.url(self.context))
375        return
376   
377    def update(self):
378        if self.context.locked:
379            self.emitLockMessage()
380            return
381        super(EditApplicant, self).update()
382        return
383
384    def filteredWidgets(self):
385        for widget in self.widgets:
386            if widget.name == 'form.confirm_passport':
387                continue
388            yield widget
389
390    @property
391    def label(self):
392        # XXX: Use current/upcoming session
393        return 'Apply for Post UDE Screening Test (2009/2010)'
394    title = 'Edit Application'
395    pnav = 3
396
397    @grok.action('Save')
398    def save(self, **data):
399        if self.context.locked:
400            self.emitLockMessage()
401            return
402        self.applyData(self.context, **data)
403        self.context._p_changed = True
404        return
405
406    @grok.action('Final Submit')
407    def finalsubmit(self, **data):
408        if self.context.locked:
409            self.emitLockMessage()
410            return
411        self.applyData(self.context, **data)
412        self.context._p_changed = True
413        if not self.dataComplete():
414            self.flash('Data yet not complete.')
415            return
416        self.context.locked = True
417        return
418
419    def dataComplete(self):
420        if context.confirm_passport is not True:
421            return False
422        if len(self.errors) > 0:
423            return False
424        return True
Note: See TracBrowser for help on using the repository browser.