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

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

Add a view for full editing of applicants.

File size: 14.9 KB
RevLine 
[5273]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##
[5824]22"""UI components for basic applicants and related components.
[5273]23"""
24import grok
25
[5837]26from datetime import datetime
27from hurry.jquery import jquery
28from hurry.jqueryui import jqueryui
[5822]29from zope.component import getUtility, getAllUtilitiesRegisteredFor
[5844]30from zope.formlib.widgets import FileWidget, DateWidget
[5937]31from zope.securitypolicy.interfaces import IPrincipalRoleManager
[5273]32from waeup.sirp.browser import (
33    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage,
34    WAeUPDisplayFormPage, NullValidator)
[5442]35from waeup.sirp.browser.pages import LoginPage
[5320]36from waeup.sirp.interfaces import IWAeUPObject
[5982]37from waeup.sirp.browser.resources import datepicker, tabs
[5828]38from waeup.sirp.browser.viewlets import (
39    AddActionButton, ManageActionButton, PrimaryNavTab,
40    )
[5845]41from waeup.sirp.browser.breadcrumbs import Breadcrumb
[5910]42from waeup.sirp.applicants import get_applicant_data, ResultEntry, Applicant
[5758]43from waeup.sirp.applicants.interfaces import (
[5822]44    IApplicant, IApplicantPrincipal, IApplicantPDEEditData,
[5846]45    IApplicantsRoot, IApplicantsContainer, IApplicantsContainerProvider,
[5822]46    )
[5686]47from waeup.sirp.widgets.passportwidget import (
48    PassportWidget, PassportDisplayWidget
49    )
[5273]50#from zope.formlib.objectwidget import ObjectWidget
51from zope.formlib.sequencewidget import ListSequenceWidget, SequenceDisplayWidget
52from zope.formlib.widget import CustomWidgetFactory
[5850]53from waeup.sirp.utils.helpers import ReST2HTML
[5303]54from waeup.sirp.widgets.objectwidget import (
[5301]55    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
[5303]56from waeup.sirp.widgets.multilistwidget import (
[5273]57    MultiListWidget, MultiListDisplayWidget)
[5686]58from waeup.sirp.image.browser.widget import (
59    ThumbnailWidget, EncodingImageFileWidget,
60    )
[5320]61
[5273]62results_widget = CustomWidgetFactory(
[5301]63    WAeUPObjectWidget, ResultEntry)
[5273]64
65results_display_widget = CustomWidgetFactory(
[5301]66    WAeUPObjectDisplayWidget, ResultEntry)
[5273]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
[5844]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
[5822]83class ApplicationsPage(WAeUPPage):
84    grok.context(IApplicantsRoot)
85    grok.name('index')
86    title = 'Applicants'
[5843]87    pnav = 3
[5822]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)
[5861]94            yield(dict(url=url, name=key, container=val))
[5273]95
[5828]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'
[5843]106    pnav = 3
[5828]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
[5822]130class AddApplicantsContainerActionButton(AddActionButton):
131    grok.context(IApplicantsRoot)
[5828]132    grok.view(ApplicantsRootEditPage)
[5822]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'
[5843]140    pnav = 3
[5822]141
[5828]142    def update(self, providername=None, name=None, title=None,
143               description=None, ADD=None, CANCEL=None):
[5822]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...
[5846]156        provider = getUtility(IApplicantsContainerProvider,
[5822]157                              name=providername)
158        container = provider.factory()
[5828]159        if title:
160            container.title = title
161        if description:
162            container.description = description
[5822]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):
[5825]169        """Get a list of applicants container providers.
[5822]170
[5825]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        """
[5846]189        providers = getAllUtilitiesRegisteredFor(IApplicantsContainerProvider)
[5822]190        result = [
191            {'name': getattr(x, 'grokcore.component.directive.name'),
192             'provider': x}
193             for x in providers
194            ]
195        return result
[5828]196
[5845]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)
[5828]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
[5843]216    pnav = 3
[5828]217    tab_title = u'Applicants'
218
219    @property
220    def link_target(self):
221        return self.view.application_url('applicants')
222
[5830]223class ApplicantsContainerPage(WAeUPPage):
224    """The standard view for regular applicant containers.
225    """
226    grok.context(IApplicantsContainer)
227    grok.name('index')
[5850]228    pnav = 3
229   
[5837]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
[5830]238
[5850]239    def descriptionToHTML(self):
240        return ReST2HTML(self.context.description)
241
[5832]242class ManageApplicantsContainerActionButton(ManageActionButton):
243    grok.context(IApplicantsContainer)
244    grok.view(ApplicantsContainerPage)
245    text = 'Manage'
246
247
[5837]248class ManageApplicantsContainer(WAeUPEditFormPage):
249    grok.context(IApplicantsContainer)
[5850]250    grok.name('manage')
[5982]251    grok.template('form_manage_applicants_container')
[5837]252    form_fields = grok.AutoFields(IApplicantsContainer)
[5844]253    # Use friendlier date widget...
254    form_fields['startdate'].custom_widget = FriendlyDateWidget
255    form_fields['enddate'].custom_widget = FriendlyDateWidget
[5850]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
[5845]266    pnav = 3
[5837]267
268    def update(self):
[5850]269        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]270        tabs.need()
[5837]271        return super(ManageApplicantsContainer, self).update()
272
[5850]273    @grok.action('Save')
[5837]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
[5886]283
284class LoginApplicant(WAeUPPage):
285    grok.context(IApplicantsContainer)
286    grok.name('login')
287    grok.require('zope.Public')
288
289    title = u'Login'
[5837]290   
[5886]291    @property
292    def label(self):
293        return self.title
294
295    pnav = 3
296    prefix = u'APP'
297   
[5896]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)
[5886]301        if SUBMIT is None:
302            return
303
[5894]304        if self.request.principal.id == 'zope.anybody':
305            self.flash('Entered credentials are invalid')
[5886]306            return
[5894]307
308        if not IApplicantPrincipal.providedBy(self.request.principal):
309            # Don't care if user is already authenticated as non-applicant
310            return
311
[5905]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
[5937]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'))
[5886]325        return
326
[5758]327#class AddApplicant(WAeUPAddFormPage):
[5846]328#    grok.context(IApplicantsContainer)
[5758]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))
[5273]350
351class DisplayApplicant(WAeUPDisplayFormPage):
352    grok.context(IApplicant)
353    grok.name('index')
[5937]354    grok.require('waeup.viewApplication')
[5941]355    form_fields = grok.AutoFields(IApplicant).omit('locked')
[5273]356    form_fields['fst_sit_results'].custom_widget = list_results_display_widget
[5686]357    #form_fields['passport'].custom_widget = PassportDisplayWidget
[5919]358    form_fields['passport'].custom_widget = ThumbnailWidget
359    #form_fields['passport'].custom_widget = EncodingImageFileWidget
[5273]360    label = 'Applicant'
361    title = 'Applicant'
[5843]362    pnav = 3
[5273]363
[5982]364class EditApplicantStudent(WAeUPEditFormPage):
365    """An applicant-centered edit view for applicant data.
366    """
[5273]367    grok.context(IApplicant)
368    grok.name('edit')
[5937]369    grok.require('waeup.editApplication')
[5941]370    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
[5686]371    #form_fields['passport'].custom_widget = FileWidget
372    #form_fields['passport'].custom_widget = PassportWidget
373    form_fields['passport'].custom_widget = EncodingImageFileWidget
[5488]374    grok.template('form_edit_pde')
[5484]375
[5941]376    def emitLockMessage(self):
377        self.flash('The requested form is locked (read-only)')
378        self.redirect(self.url(self.context))
379        return
380   
[5686]381    def update(self):
[5941]382        if self.context.locked:
383            self.emitLockMessage()
384            return
[5982]385        super(EditApplicantStudent, self).update()
[5686]386        return
[5952]387
388    def filteredWidgets(self):
389        for widget in self.widgets:
390            if widget.name == 'form.confirm_passport':
391                continue
392            yield widget
393
[5484]394    @property
395    def label(self):
396        # XXX: Use current/upcoming session
397        return 'Apply for Post UDE Screening Test (2009/2010)'
[5273]398    title = 'Edit Application'
[5845]399    pnav = 3
[5273]400
401    @grok.action('Save')
402    def save(self, **data):
[5941]403        if self.context.locked:
404            self.emitLockMessage()
405            return
[5273]406        self.applyData(self.context, **data)
407        self.context._p_changed = True
408        return
409
[5484]410    @grok.action('Final Submit')
411    def finalsubmit(self, **data):
[5941]412        if self.context.locked:
413            self.emitLockMessage()
414            return
[5273]415        self.applyData(self.context, **data)
[5484]416        self.context._p_changed = True
[5941]417        if not self.dataComplete():
418            self.flash('Data yet not complete.')
419            return
420        self.context.locked = True
[5273]421        return
[5941]422
423    def dataComplete(self):
[5952]424        if context.confirm_passport is not True:
[5941]425            return False
426        if len(self.errors) > 0:
427            return False
428        return True
[5982]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.