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

Last change on this file since 5982 was 5982, checked in by uli, 14 years ago

Add a view for full editing of applicants.

File size: 14.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
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, tabs
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    grok.template('form_manage_applicants_container')
252    form_fields = grok.AutoFields(IApplicantsContainer)
253    # Use friendlier date widget...
254    form_fields['startdate'].custom_widget = FriendlyDateWidget
255    form_fields['enddate'].custom_widget = FriendlyDateWidget
256
257    @property
258    def title(self):
259        return "Manage applicants container: %s" % getattr(
260            self.context, '__name__', 'unnamed')
261   
262    @property
263    def label(self):
264        return self.title
265
266    pnav = 3
267
268    def update(self):
269        datepicker.need() # Enable jQuery datepicker in date fields.
270        tabs.need()
271        return super(ManageApplicantsContainer, self).update()
272
273    @grok.action('Save')
274    def apply(self, **data):
275        self.applyData(self.context, **data)
276        self.flash('Data saved.')
277        return
278       
279    @grok.action('Back')
280    def cancel(self, **data):
281        self.redirect(self.url(self.context))
282        return
283
284class LoginApplicant(WAeUPPage):
285    grok.context(IApplicantsContainer)
286    grok.name('login')
287    grok.require('zope.Public')
288
289    title = u'Login'
290   
291    @property
292    def label(self):
293        return self.title
294
295    pnav = 3
296    prefix = u'APP'
297   
298    def update(self, SUBMIT=None):
299        self.ac_series = self.request.form.get('form.ac_series', None)
300        self.ac_number = self.request.form.get('form.ac_number', None)
301        if SUBMIT is None:
302            return
303
304        if self.request.principal.id == 'zope.anybody':
305            self.flash('Entered credentials are invalid')
306            return
307
308        if not IApplicantPrincipal.providedBy(self.request.principal):
309            # Don't care if user is already authenticated as non-applicant
310            return
311
312        pin = self.request.principal.access_code
313        if pin not in self.context.keys():
314            # Create applicant record
315            applicant = Applicant()
316            applicant.access_code = pin
317            self.context[pin] = applicant
318           
319        # Assign current principal the owner role on created applicant
320        # record
321        role_manager = IPrincipalRoleManager(self.context)
322        role_manager.assignRoleToPrincipal(
323            'waeup.ApplicationOwner', self.request.principal.id)
324        self.redirect(self.url(self.context[pin], 'edit'))
325        return
326
327#class AddApplicant(WAeUPAddFormPage):
328#    grok.context(IApplicantsContainer)
329#    grok.name('add')
330#    form_fields = grok.AutoFields(IApplicant)
331#    form_fields['fst_sit_results'].custom_widget = list_results_widget
332#    form_fields['passport'].custom_widget = EncodingImageFileWidget
333#    label = 'Add Applicant'
334#    title = 'Add Applicant'
335#    pnav = 1
336#
337#    @grok.action('Add applicant')
338#    def addApplicant(self, **data):
339#        from waeup.sirp.jambtables.applicants import Applicant
340#        applicant = Applicant()
341#        self.applyData(applicant, **data)
342#        # XXX: temporarily disabled.
343#        #self.context[applicant.reg_no] = applicant
344#        try:
345#            self.context[applicant.access_code] = applicant
346#        except KeyError:
347#            self.flash('The given access code is already in use!')
348#            return
349#        self.redirect(self.url(self.context))
350
351class DisplayApplicant(WAeUPDisplayFormPage):
352    grok.context(IApplicant)
353    grok.name('index')
354    grok.require('waeup.viewApplication')
355    form_fields = grok.AutoFields(IApplicant).omit('locked')
356    form_fields['fst_sit_results'].custom_widget = list_results_display_widget
357    #form_fields['passport'].custom_widget = PassportDisplayWidget
358    form_fields['passport'].custom_widget = ThumbnailWidget
359    #form_fields['passport'].custom_widget = EncodingImageFileWidget
360    label = 'Applicant'
361    title = 'Applicant'
362    pnav = 3
363
364class EditApplicantStudent(WAeUPEditFormPage):
365    """An applicant-centered edit view for applicant data.
366    """
367    grok.context(IApplicant)
368    grok.name('edit')
369    grok.require('waeup.editApplication')
370    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
371    #form_fields['passport'].custom_widget = FileWidget
372    #form_fields['passport'].custom_widget = PassportWidget
373    form_fields['passport'].custom_widget = EncodingImageFileWidget
374    grok.template('form_edit_pde')
375
376    def emitLockMessage(self):
377        self.flash('The requested form is locked (read-only)')
378        self.redirect(self.url(self.context))
379        return
380   
381    def update(self):
382        if self.context.locked:
383            self.emitLockMessage()
384            return
385        super(EditApplicantStudent, self).update()
386        return
387
388    def filteredWidgets(self):
389        for widget in self.widgets:
390            if widget.name == 'form.confirm_passport':
391                continue
392            yield widget
393
394    @property
395    def label(self):
396        # XXX: Use current/upcoming session
397        return 'Apply for Post UDE Screening Test (2009/2010)'
398    title = 'Edit Application'
399    pnav = 3
400
401    @grok.action('Save')
402    def save(self, **data):
403        if self.context.locked:
404            self.emitLockMessage()
405            return
406        self.applyData(self.context, **data)
407        self.context._p_changed = True
408        return
409
410    @grok.action('Final Submit')
411    def finalsubmit(self, **data):
412        if self.context.locked:
413            self.emitLockMessage()
414            return
415        self.applyData(self.context, **data)
416        self.context._p_changed = True
417        if not self.dataComplete():
418            self.flash('Data yet not complete.')
419            return
420        self.context.locked = True
421        return
422
423    def dataComplete(self):
424        if context.confirm_passport is not True:
425            return False
426        if len(self.errors) > 0:
427            return False
428        return True
429
430class EditApplicantFull(WAeUPEditFormPage):
431    """A full edit view for applicant data.
432
433    This one is meant to be used by officers only.
434    """
435    grok.context(IApplicant)
436    grok.name('edit_full')
437    grok.require('waeup.editFullApplication')
438    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
439    form_fields['passport'].custom_widget = EncodingImageFileWidget
440    grok.template('form_edit_full')
441
442    def update(self):
443        if self.context.locked:
444            self.emitLockMessage()
445            return
446        super(EditApplicantFull, self).update()
447        return
448
449    def filteredWidgets(self):
450        for widget in self.widgets:
451            if widget.name == 'form.confirm_passport':
452                continue
453            yield widget
454
455    @property
456    def label(self):
457        return 'Application for %s' % self.context.access_code
458    title = 'Edit Application'
459    pnav = 3
460
461    @grok.action('Save')
462    def save(self, **data):
463        self.applyData(self.context, **data)
464        self.context._p_changed = True
465        return
Note: See TracBrowser for help on using the repository browser.