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

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

Use WAuPDisplayFormPage for ApplicantsContainerPage?.

Fix traceback if description field is empty.

File size: 15.2 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
[6013]37from waeup.sirp.browser.resources import datepicker, tabs, datatable
[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
[6012]88
89    def update(self):
90        super(ApplicationsPage, self).update()
91        datatable.need()
92        return
93
[5822]94    def getApplications(self):
95        """Get a list of all stored applicant containers.
96        """
97        for key, val in self.context.items():
98            url = self.url(val)
[5861]99            yield(dict(url=url, name=key, container=val))
[5273]100
[5828]101class ManageApplicantsRootActionButton(ManageActionButton):
102    grok.context(IApplicantsRoot)
103    grok.view(ApplicationsPage)
104    text = 'Manage applicant containers'
105
106class ApplicantsRootEditPage(WAeUPPage):
107    grok.context(IApplicantsRoot)
108    grok.name('manage')
109    grok.template('applicantsrooteditpage')
110    title = 'Edit applicants containers'
[5843]111    pnav = 3
[5828]112
113    def update(self, entries=None, DELETE=None, CANCEL=None):
114        if CANCEL is not None:
115            self.redirect(self.url(self.context))
116            return
117        if DELETE is None:
118            return
119        if entries is None:
120            return
121        if not isinstance(entries, list):
122            entries = [entries]
123        for name in entries:
124            del self.context[name]
125            self.flash('Deleted "%s"' % name)
126        return
127
128    def getApplications(self):
129        """Get a list of all stored applicant containers.
130        """
131        for key, val in self.context.items():
132            url = self.url(val)
133            yield(dict(url=url, name=key))
134
[5822]135class AddApplicantsContainerActionButton(AddActionButton):
136    grok.context(IApplicantsRoot)
[5828]137    grok.view(ApplicantsRootEditPage)
[5822]138    text = 'Add applicants container'
139
140class AddApplicantsContainer(WAeUPPage):
141    grok.context(IApplicantsRoot)
142    grok.name('add')
143    grok.template('addcontainer')
144    title = 'Add applicants container'
[5843]145    pnav = 3
[5822]146
[5828]147    def update(self, providername=None, name=None, title=None,
148               description=None, ADD=None, CANCEL=None):
[5822]149        if CANCEL is not None:
150            self.redirect(self.url(self.context))
151            return
152        if ADD is None:
153            return
154        if not name:
155            self.flash('Error: you must give a name')
156            return
157        if name in self.context.keys():
158            self.flash('A container of the given name already exists')
159            return
160        # Add new applicants container...
[5846]161        provider = getUtility(IApplicantsContainerProvider,
[5822]162                              name=providername)
163        container = provider.factory()
[5828]164        if title:
165            container.title = title
166        if description:
167            container.description = description
[5822]168        self.context[name] = container
169        self.flash('Added "%s".' % name)
170        self.redirect(self.url(self.context))
171        return
172       
173    def getContainerProviders(self):
[5825]174        """Get a list of applicants container providers.
[5822]175
[5825]176        Applicants container providers are named utilities that help
177        to create applicants containers of different types
178        (JAMB-based, non-JAMB-based, etc.).
179
180        The list returned contains dicts::
181
182          {'name': <utility_name>,
183           'provider': <provider instance>}
184
185        where `utility_name` is the name under which the respective
186        provider utility is registered and `provider` is the real
187        provider instance.
188
189        The `utility_name` can be used to lookup the utility anew (for
190        instance after submitting a form) and the `provider` instance
191        can be used to create new instances of the respective
192        applicants container type.
193        """
[5846]194        providers = getAllUtilitiesRegisteredFor(IApplicantsContainerProvider)
[5822]195        result = [
196            {'name': getattr(x, 'grokcore.component.directive.name'),
197             'provider': x}
198             for x in providers
199            ]
200        return result
[5828]201
[5845]202class ApplicantsRootBreadcrumb(Breadcrumb):
203    """A breadcrumb for applicantsroot.
204    """
205    grok.context(IApplicantsRoot)
206    title = u'Applicants'
207   
208class ApplicantsContainerBreadcrumb(Breadcrumb):
209    """A breadcrumb for applicantscontainers.
210    """
211    grok.context(IApplicantsContainer)
[5828]212
213class ApplicantsTab(PrimaryNavTab):
214    """Faculties-tab in primary navigation.
215    """
216    grok.context(IWAeUPObject)
217    grok.order(3)
218    grok.require('waeup.View')
219    grok.template('primarynavtab')
220
[5843]221    pnav = 3
[5828]222    tab_title = u'Applicants'
223
224    @property
225    def link_target(self):
226        return self.view.application_url('applicants')
227
[6029]228class ApplicantsContainerPage(WAeUPDisplayFormPage):
[5830]229    """The standard view for regular applicant containers.
230    """
231    grok.context(IApplicantsContainer)
232    grok.name('index')
[6029]233    grok.template('applicantscontainerpage')
[5850]234    pnav = 3
235   
[5837]236    @property
237    def title(self):
238        return "Applicants Container: %s" % getattr(
239            self.context, '__name__', 'unnamed')
240
241    @property
242    def label(self):
243        return self.title
[5830]244
[5850]245    def descriptionToHTML(self):
[6029]246        if self.context.description:
247            return ReST2HTML(self.context.description)
248        else:
249            return
[5850]250
[5832]251class ManageApplicantsContainerActionButton(ManageActionButton):
252    grok.context(IApplicantsContainer)
253    grok.view(ApplicantsContainerPage)
254    text = 'Manage'
255
256
[5837]257class ManageApplicantsContainer(WAeUPEditFormPage):
258    grok.context(IApplicantsContainer)
[5850]259    grok.name('manage')
[5982]260    grok.template('form_manage_applicants_container')
[5837]261    form_fields = grok.AutoFields(IApplicantsContainer)
[5844]262    # Use friendlier date widget...
263    form_fields['startdate'].custom_widget = FriendlyDateWidget
264    form_fields['enddate'].custom_widget = FriendlyDateWidget
[5850]265
266    @property
267    def title(self):
268        return "Manage applicants container: %s" % getattr(
269            self.context, '__name__', 'unnamed')
270   
271    @property
272    def label(self):
273        return self.title
274
[5845]275    pnav = 3
[5837]276
277    def update(self):
[5850]278        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]279        tabs.need()
[6015]280        datatable.need()  # Enable jQurey datatables for contents listing
[5837]281        return super(ManageApplicantsContainer, self).update()
282
[5850]283    @grok.action('Save')
[5837]284    def apply(self, **data):
285        self.applyData(self.context, **data)
286        self.flash('Data saved.')
287        return
288       
289    @grok.action('Back')
290    def cancel(self, **data):
291        self.redirect(self.url(self.context))
292        return
[5886]293
294class LoginApplicant(WAeUPPage):
295    grok.context(IApplicantsContainer)
296    grok.name('login')
297    grok.require('zope.Public')
298
299    title = u'Login'
[5837]300   
[5886]301    @property
302    def label(self):
303        return self.title
304
305    pnav = 3
306    prefix = u'APP'
307   
[5896]308    def update(self, SUBMIT=None):
309        self.ac_series = self.request.form.get('form.ac_series', None)
310        self.ac_number = self.request.form.get('form.ac_number', None)
[5886]311        if SUBMIT is None:
312            return
313
[5894]314        if self.request.principal.id == 'zope.anybody':
315            self.flash('Entered credentials are invalid')
[5886]316            return
[5894]317
318        if not IApplicantPrincipal.providedBy(self.request.principal):
319            # Don't care if user is already authenticated as non-applicant
320            return
321
[5905]322        pin = self.request.principal.access_code
323        if pin not in self.context.keys():
324            # Create applicant record
325            applicant = Applicant()
326            applicant.access_code = pin
327            self.context[pin] = applicant
[5937]328           
329        # Assign current principal the owner role on created applicant
330        # record
331        role_manager = IPrincipalRoleManager(self.context)
332        role_manager.assignRoleToPrincipal(
333            'waeup.ApplicationOwner', self.request.principal.id)
334        self.redirect(self.url(self.context[pin], 'edit'))
[5886]335        return
336
[5758]337#class AddApplicant(WAeUPAddFormPage):
[5846]338#    grok.context(IApplicantsContainer)
[5758]339#    grok.name('add')
340#    form_fields = grok.AutoFields(IApplicant)
341#    form_fields['fst_sit_results'].custom_widget = list_results_widget
342#    form_fields['passport'].custom_widget = EncodingImageFileWidget
343#    label = 'Add Applicant'
344#    title = 'Add Applicant'
345#    pnav = 1
346#
347#    @grok.action('Add applicant')
348#    def addApplicant(self, **data):
349#        from waeup.sirp.jambtables.applicants import Applicant
350#        applicant = Applicant()
351#        self.applyData(applicant, **data)
352#        # XXX: temporarily disabled.
353#        #self.context[applicant.reg_no] = applicant
354#        try:
355#            self.context[applicant.access_code] = applicant
356#        except KeyError:
357#            self.flash('The given access code is already in use!')
358#            return
359#        self.redirect(self.url(self.context))
[5273]360
361class DisplayApplicant(WAeUPDisplayFormPage):
362    grok.context(IApplicant)
363    grok.name('index')
[5937]364    grok.require('waeup.viewApplication')
[5941]365    form_fields = grok.AutoFields(IApplicant).omit('locked')
[5273]366    form_fields['fst_sit_results'].custom_widget = list_results_display_widget
[5686]367    #form_fields['passport'].custom_widget = PassportDisplayWidget
[5919]368    form_fields['passport'].custom_widget = ThumbnailWidget
369    #form_fields['passport'].custom_widget = EncodingImageFileWidget
[5273]370    label = 'Applicant'
371    title = 'Applicant'
[5843]372    pnav = 3
[5273]373
[5982]374class EditApplicantStudent(WAeUPEditFormPage):
375    """An applicant-centered edit view for applicant data.
376    """
[5273]377    grok.context(IApplicant)
378    grok.name('edit')
[5937]379    grok.require('waeup.editApplication')
[5941]380    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
[5686]381    #form_fields['passport'].custom_widget = FileWidget
382    #form_fields['passport'].custom_widget = PassportWidget
383    form_fields['passport'].custom_widget = EncodingImageFileWidget
[5488]384    grok.template('form_edit_pde')
[5484]385
[5941]386    def emitLockMessage(self):
387        self.flash('The requested form is locked (read-only)')
388        self.redirect(self.url(self.context))
389        return
390   
[5686]391    def update(self):
[5941]392        if self.context.locked:
393            self.emitLockMessage()
394            return
[5982]395        super(EditApplicantStudent, self).update()
[5686]396        return
[5952]397
398    def filteredWidgets(self):
399        for widget in self.widgets:
400            if widget.name == 'form.confirm_passport':
401                continue
402            yield widget
403
[5484]404    @property
405    def label(self):
406        # XXX: Use current/upcoming session
407        return 'Apply for Post UDE Screening Test (2009/2010)'
[5273]408    title = 'Edit Application'
[5845]409    pnav = 3
[5273]410
411    @grok.action('Save')
412    def save(self, **data):
[5941]413        if self.context.locked:
414            self.emitLockMessage()
415            return
[5273]416        self.applyData(self.context, **data)
417        self.context._p_changed = True
418        return
419
[5484]420    @grok.action('Final Submit')
421    def finalsubmit(self, **data):
[5941]422        if self.context.locked:
423            self.emitLockMessage()
424            return
[5273]425        self.applyData(self.context, **data)
[5484]426        self.context._p_changed = True
[5941]427        if not self.dataComplete():
428            self.flash('Data yet not complete.')
429            return
430        self.context.locked = True
[5273]431        return
[5941]432
433    def dataComplete(self):
[5952]434        if context.confirm_passport is not True:
[5941]435            return False
436        if len(self.errors) > 0:
437            return False
438        return True
[5982]439
440class EditApplicantFull(WAeUPEditFormPage):
441    """A full edit view for applicant data.
442
443    This one is meant to be used by officers only.
444    """
445    grok.context(IApplicant)
446    grok.name('edit_full')
447    grok.require('waeup.editFullApplication')
448    form_fields = grok.AutoFields(IApplicantPDEEditData).omit('locked')
449    form_fields['passport'].custom_widget = EncodingImageFileWidget
450    grok.template('form_edit_full')
451
452    def update(self):
453        if self.context.locked:
454            self.emitLockMessage()
455            return
456        super(EditApplicantFull, self).update()
457        return
458
459    def filteredWidgets(self):
460        for widget in self.widgets:
461            if widget.name == 'form.confirm_passport':
462                continue
463            yield widget
464
465    @property
466    def label(self):
467        return 'Application for %s' % self.context.access_code
468    title = 'Edit Application'
469    pnav = 3
470
471    @grok.action('Save')
472    def save(self, **data):
473        self.applyData(self.context, **data)
474        self.context._p_changed = True
475        return
Note: See TracBrowser for help on using the repository browser.