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

Last change on this file since 6292 was 6254, checked in by Henrik Bettermann, 13 years ago

Render Admitted Course of Study seperately and provide URL to certificate.

File size: 18.8 KB
RevLine 
[5273]1##
2## browser.py
3## Login : <uli@pu.smp.net>
[6153]4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
[5273]5## $Id$
[6078]6##
[6063]7## Copyright (C) 2010 Uli Fouquet & Henrik Bettermann
[5273]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.
[6078]12##
[5273]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.
[6078]17##
[5273]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"""
[6082]24import sys
[5273]25import grok
26
[6082]27from zope.component import getUtility
[6081]28from zope.formlib.widget import CustomWidgetFactory
[6082]29from zope.interface import Invalid
[5937]30from zope.securitypolicy.interfaces import IPrincipalRoleManager
[6153]31from zope.traversing.browser import absoluteURL
[6081]32
[5273]33from waeup.sirp.browser import (
34    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage,
35    WAeUPDisplayFormPage, NullValidator)
[6081]36from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6103]37from waeup.sirp.browser.layout import NullValidator
[6013]38from waeup.sirp.browser.resources import datepicker, tabs, datatable
[6153]39from waeup.sirp.browser.viewlets import (
40    ManageActionButton, PrimaryNavTab, LeftSidebarLink
41    )
[6081]42from waeup.sirp.image.browser.widget import (
43    ThumbnailWidget, EncodingImageFileWidget,
[5822]44    )
[6184]45from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
[6254]46from waeup.sirp.university.interfaces import ICertificate
[6054]47from waeup.sirp.widgets.datewidget import (
48    FriendlyDateWidget, FriendlyDateDisplayWidget)
[6084]49from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
[5303]50from waeup.sirp.widgets.objectwidget import (
[5301]51    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
[5303]52from waeup.sirp.widgets.multilistwidget import (
[5273]53    MultiListWidget, MultiListDisplayWidget)
[6081]54
[6153]55from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
[6081]56from waeup.sirp.applicants.interfaces import (
[6196]57    IApplicant, IApplicantPrincipal,IApplicantEdit,
[6081]58    IApplicantsRoot, IApplicantsContainer, IApplicantsContainerProvider,
[6087]59    IApplicantsContainerAdd, application_types_vocab
[5686]60    )
[6184]61from waeup.sirp.browser.pages import add_local_role, del_local_roles
62from waeup.sirp.permissions import get_users_with_local_roles, getRoles
[5320]63
[5273]64results_widget = CustomWidgetFactory(
[5301]65    WAeUPObjectWidget, ResultEntry)
[5273]66
67results_display_widget = CustomWidgetFactory(
[5301]68    WAeUPObjectDisplayWidget, ResultEntry)
[5273]69
70list_results_widget = CustomWidgetFactory(
71    MultiListWidget, subwidget=results_widget)
72
73list_results_display_widget = CustomWidgetFactory(
74    MultiListDisplayWidget, subwidget=results_display_widget)
75
[6067]76class ApplicantsRootPage(WAeUPPage):
[5822]77    grok.context(IApplicantsRoot)
78    grok.name('index')
[6153]79    grok.require('waeup.Public')
[5822]80    title = 'Applicants'
[6069]81    label = 'Application Section'
[5843]82    pnav = 3
[6012]83
84    def update(self):
[6067]85        super(ApplicantsRootPage, self).update()
[6012]86        datatable.need()
87        return
88
[5828]89class ManageApplicantsRootActionButton(ManageActionButton):
90    grok.context(IApplicantsRoot)
[6067]91    grok.view(ApplicantsRootPage)
[6198]92    grok.require('waeup.manageApplications')
[6069]93    text = 'Manage application section'
[5828]94
[6069]95class ApplicantsRootManageFormPage(WAeUPEditFormPage):
[5828]96    grok.context(IApplicantsRoot)
97    grok.name('manage')
[6107]98    grok.template('applicantsrootmanagepage')
[6069]99    title = 'Applicants'
100    label = 'Manage application section'
[5843]101    pnav = 3
[6198]102    grok.require('waeup.manageApplications')
[6069]103    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
[6184]104    tabtwoactions1 = ['Remove selected local roles']
105    tabtwoactions2 = ['Add local role']
[6069]106    subunits = 'Applicants Containers'
[6078]107
[6069]108    def update(self):
109        tabs.need()
[6108]110        datatable.need()
[6069]111        return super(ApplicantsRootManageFormPage, self).update()
[5828]112
[6184]113    def getLocalRoles(self):
114        roles = ILocalRolesAssignable(self.context)
115        return roles()
116
117    def getUsers(self):
118        """Get a list of all users.
119        """
120        for key, val in grok.getSite()['users'].items():
121            url = self.url(val)
122            yield(dict(url=url, name=key, val=val))
123
124    def getUsersWithLocalRoles(self):
125        return get_users_with_local_roles(self.context)
126
[6069]127    # ToDo: Show warning message before deletion
128    @grok.action('Remove selected')
129    def delApplicantsContainers(self, **data):
130        form = self.request.form
131        child_id = form['val_id']
132        if not isinstance(child_id, list):
133            child_id = [child_id]
134        deleted = []
135        for id in child_id:
136            try:
137                del self.context[id]
138                deleted.append(id)
139            except:
140                self.flash('Could not delete %s: %s: %s' % (
141                        id, sys.exc_info()[0], sys.exc_info()[1]))
142        if len(deleted):
143            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6078]144        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
145        return
[5828]146
[6069]147    @grok.action('Add applicants container', validator=NullValidator)
148    def addApplicantsContainer(self, **data):
149        self.redirect(self.url(self.context, '@@add'))
[6078]150        return
151
[6069]152    @grok.action('Cancel', validator=NullValidator)
153    def cancel(self, **data):
154        self.redirect(self.url(self.context))
[6078]155        return
156
[6184]157    @grok.action('Add local role', validator=NullValidator)
158    def addLocalRole(self, **data):
159        return add_local_role(self,2, **data)
160
161    @grok.action('Remove selected local roles')
162    def delLocalRoles(self, **data):
163        return del_local_roles(self,2,**data)
164
[6069]165class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
[5822]166    grok.context(IApplicantsRoot)
[6198]167    grok.require('waeup.manageApplications')
[5822]168    grok.name('add')
[6107]169    grok.template('applicantscontaineraddpage')
[6069]170    title = 'Applicants'
171    label = 'Add applicants container'
[5843]172    pnav = 3
[6078]173
[6103]174    form_fields = grok.AutoFields(
175        IApplicantsContainerAdd).omit('code').omit('title')
[6083]176    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
177    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6078]178
[6083]179    def update(self):
180        datepicker.need() # Enable jQuery datepicker in date fields.
[6110]181        #from waeup.sirp.browser.resources import jqueryui
182        #jqueryui.need()
[6083]183        return super(ApplicantsContainerAddFormPage, self).update()
184
[6069]185    @grok.action('Add applicants container')
186    def addApplicantsContainer(self, **data):
[6103]187        year = data['year']
188        code = u'%s%s' % (data['prefix'], year)
189        prefix = application_types_vocab.getTerm(data['prefix'])
190        title = u'%s %s/%s' % (prefix.title, year, year + 1)
[6087]191        if code in self.context.keys():
[6105]192            self.flash(
193                'An applicants container for the same application '
194                'type and entrance year exists already in the database.')
[5822]195            return
196        # Add new applicants container...
[6083]197        provider = data['provider'][1]
[5822]198        container = provider.factory()
[6069]199        self.applyData(container, **data)
[6087]200        container.code = code
201        container.title = title
202        self.context[code] = container
[6105]203        self.flash('Added: "%s".' % code)
[6069]204        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
[5822]205        return
[6078]206
[6103]207    @grok.action('Cancel', validator=NullValidator)
[6069]208    def cancel(self, **data):
[6103]209        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
[6078]210
[5845]211class ApplicantsRootBreadcrumb(Breadcrumb):
212    """A breadcrumb for applicantsroot.
213    """
214    grok.context(IApplicantsRoot)
[6153]215    title = u'Application Section'
[6078]216
[5845]217class ApplicantsContainerBreadcrumb(Breadcrumb):
218    """A breadcrumb for applicantscontainers.
219    """
220    grok.context(IApplicantsContainer)
[6153]221   
222class ApplicantBreadcrumb(Breadcrumb):
223    """A breadcrumb for applicants.
224    """
225    grok.context(IApplicant)
226   
227    @property
228    def title(self):
229        """Get a title for a context.
230        """
231        return self.context.access_code
[5828]232
233class ApplicantsTab(PrimaryNavTab):
[6153]234    """Applicants tab in primary navigation.
[5828]235    """
[6078]236
[5828]237    grok.context(IWAeUPObject)
238    grok.order(3)
[6198]239    grok.require('waeup.manageApplications')
[5828]240    grok.template('primarynavtab')
241
[5843]242    pnav = 3
[5828]243    tab_title = u'Applicants'
244
245    @property
246    def link_target(self):
247        return self.view.application_url('applicants')
248
[6029]249class ApplicantsContainerPage(WAeUPDisplayFormPage):
[5830]250    """The standard view for regular applicant containers.
251    """
252    grok.context(IApplicantsContainer)
253    grok.name('index')
[6153]254    grok.require('waeup.Public')
[6029]255    grok.template('applicantscontainerpage')
[5850]256    pnav = 3
[6053]257
[6105]258    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
[6054]259    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
260    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
[6084]261    form_fields['description'].custom_widget = ReSTDisplayWidget
[6053]262
[5837]263    @property
264    def title(self):
[6087]265        return "Applicants Container: %s" % self.context.title
[5837]266
267    @property
268    def label(self):
[6087]269        return self.context.title
[5830]270
[6107]271class ApplicantsContainerManageActionButton(ManageActionButton):
[5832]272    grok.context(IApplicantsContainer)
273    grok.view(ApplicantsContainerPage)
[6198]274    grok.require('waeup.manageApplications')
[6070]275    text = 'Manage applicants container'
[5832]276
277
[6107]278class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
[5837]279    grok.context(IApplicantsContainer)
[5850]280    grok.name('manage')
[6107]281    grok.template('applicantscontainermanagepage')
[6105]282    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
283    taboneactions = ['Save','Cancel']
284    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
[6184]285    tabthreeactions1 = ['Remove selected local roles']
286    tabthreeactions2 = ['Add local role']
[5844]287    # Use friendlier date widget...
[6054]288    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
289    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6198]290    grok.require('waeup.manageApplications')
[5850]291
292    @property
293    def title(self):
[6087]294        return "Applicants Container: %s" % self.context.title
[6078]295
[5850]296    @property
297    def label(self):
[6087]298        return 'Manage applicants container'
[5850]299
[5845]300    pnav = 3
[5837]301
302    def update(self):
[5850]303        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]304        tabs.need()
[6015]305        datatable.need()  # Enable jQurey datatables for contents listing
[6107]306        return super(ApplicantsContainerManageFormPage, self).update()
[5837]307
[6184]308    def getLocalRoles(self):
309        roles = ILocalRolesAssignable(self.context)
310        return roles()
311
312    def getUsers(self):
313        """Get a list of all users.
314        """
315        for key, val in grok.getSite()['users'].items():
316            url = self.url(val)
317            yield(dict(url=url, name=key, val=val))
318
319    def getUsersWithLocalRoles(self):
320        return get_users_with_local_roles(self.context)
321
[5850]322    @grok.action('Save')
[5837]323    def apply(self, **data):
324        self.applyData(self.context, **data)
325        self.flash('Data saved.')
326        return
[6078]327
[6105]328    # ToDo: Show warning message before deletion
329    @grok.action('Remove selected')
330    def delApplicant(self, **data):
[6189]331        form = self.request.form
332        if form.has_key('val_id'):
333            child_id = form['val_id']
334        else:
335            self.flash('No applicant selected!')
336            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
337            return
338        if not isinstance(child_id, list):
339            child_id = [child_id]
340        deleted = []
341        for id in child_id:
342            try:
343                del self.context[id]
344                deleted.append(id)
345            except:
346                self.flash('Could not delete %s: %s: %s' % (
347                        id, sys.exc_info()[0], sys.exc_info()[1]))
348        if len(deleted):
349            self.flash('Successfully removed: %s' % ', '.join(deleted))
350        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
351        return
[6105]352
353    @grok.action('Add applicant', validator=NullValidator)
354    def addApplicant(self, **data):
355        return self.flash('Manual addition of applicants not yet implemented!')
356
357    @grok.action('Cancel', validator=NullValidator)
[5837]358    def cancel(self, **data):
359        self.redirect(self.url(self.context))
360        return
[5886]361
[6184]362    @grok.action('Add local role', validator=NullValidator)
363    def addLocalRole(self, **data):
364        return add_local_role(self,3, **data)
[6105]365
[6184]366    @grok.action('Remove selected local roles')
367    def delLocalRoles(self, **data):
368        return del_local_roles(self,3,**data)
369
370
[5886]371class LoginApplicant(WAeUPPage):
372    grok.context(IApplicantsContainer)
373    grok.name('login')
[6153]374    grok.require('waeup.Public')
[5886]375
[6110]376    @property
377    def title(self):
378        return u"Applicant Login: %s" % self.context.title
[6078]379
[5886]380    @property
381    def label(self):
[6110]382        return u'Login for applicants only'
[5886]383
384    pnav = 3
[6110]385   
386    @property
387    def ac_prefix(self):
388        return self.context.ac_prefix
[6078]389
[5896]390    def update(self, SUBMIT=None):
391        self.ac_series = self.request.form.get('form.ac_series', None)
392        self.ac_number = self.request.form.get('form.ac_number', None)
[5886]393        if SUBMIT is None:
394            return
395
[5894]396        if self.request.principal.id == 'zope.anybody':
[6105]397            self.flash('Entered credentials are invalid.')
[5886]398            return
[5894]399
400        if not IApplicantPrincipal.providedBy(self.request.principal):
401            # Don't care if user is already authenticated as non-applicant
402            return
403
[5905]404        pin = self.request.principal.access_code
405        if pin not in self.context.keys():
406            # Create applicant record
407            applicant = Applicant()
408            applicant.access_code = pin
409            self.context[pin] = applicant
[6078]410
[5937]411        # Assign current principal the owner role on created applicant
412        # record
[6184]413        role_manager = IPrincipalRoleManager(self.context[pin])
[5937]414        role_manager.assignRoleToPrincipal(
[6043]415            'waeup.local.ApplicationOwner', self.request.principal.id)
[5937]416        self.redirect(self.url(self.context[pin], 'edit'))
[5886]417        return
[6153]418       
419class AccessCodeLink(LeftSidebarLink):
420    grok.order(1)
421    grok.require('waeup.Public')
[5886]422
[6153]423    def render(self):
424        if not IApplicantPrincipal.providedBy(self.request.principal):
425            return ''
426        access_code = getattr(self.request.principal,'access_code',None)
427        if access_code:
428            applicant_object = get_applicant_data(access_code)
429            url = absoluteURL(applicant_object, self.request)
[6198]430            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
[6153]431                url,access_code)
432        return ''
433
[5273]434class DisplayApplicant(WAeUPDisplayFormPage):
435    grok.context(IApplicant)
436    grok.name('index')
[6198]437    grok.require('waeup.handleApplication')
[6254]438    form_fields = grok.AutoFields(IApplicant).omit('locked').omit('course_admitted')
[6196]439    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
[5919]440    form_fields['passport'].custom_widget = ThumbnailWidget
[6054]441    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[5273]442    label = 'Applicant'
[6254]443    grok.template('form_display')
[5843]444    pnav = 3
[5273]445
[6196]446    @property
447    def title(self):
448        return '%s' % self.context.access_code
449
450    @property
451    def label(self):
452        container_title = self.context.__parent__.title
453        return '%s Application Record' % container_title
454
[6254]455    @property
456    def getCourseAdmitted(self):
457        course_admitted = self.context.course_admitted
458        #import pdb; pdb.set_trace()
459        if ICertificate.providedBy(course_admitted):
460            url = self.url(course_admitted)
461            title = course_admitted.title
462            code = course_admitted.code
463            return '<a href="%s">%s (%s)</a>' %(url,title,code)
464        return 'not yet admitted'
465
[6198]466class ApplicantsManageActionButton(ManageActionButton):
467    grok.context(IApplicant)
468    grok.view(DisplayApplicant)
469    grok.require('waeup.manageApplications')
470    text = 'Edit application record'
471    target = 'edit_full'
472
[6196]473class EditApplicantFull(WAeUPEditFormPage):
474    """A full edit view for applicant data.
475    """
476    grok.context(IApplicant)
477    grok.name('edit_full')
[6198]478    grok.require('waeup.manageApplications')
[6196]479    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
480    form_fields['passport'].custom_widget = EncodingImageFileWidget
481    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
482    grok.template('form_edit')
483    pnav = 3
484
485    def update(self):
486        datepicker.need() # Enable jQuery datepicker in date fields.
487        super(EditApplicantFull, self).update()
488        return
489
490    @property
491    def title(self):
492        return self.context.access_code
493
494    @property
495    def label(self):
496        container_title = self.context.__parent__.title
497        return '%s Application Form' % container_title
498
499    @grok.action('Save')
500    def save(self, **data):
501        self.applyData(self.context, **data)
502        self.context._p_changed = True
503        self.flash('Form has been saved.')
504        return
505
506class EditApplicantStudent(EditApplicantFull):
[5982]507    """An applicant-centered edit view for applicant data.
508    """
[6196]509    grok.context(IApplicantEdit)
[5273]510    grok.name('edit')
[6198]511    grok.require('waeup.handleApplication')
[6196]512    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
[5686]513    form_fields['passport'].custom_widget = EncodingImageFileWidget
[6054]514    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6196]515    grok.template('form_edit')
[5484]516
[5941]517    def emitLockMessage(self):
[6105]518        self.flash('The requested form is locked (read-only).')
[5941]519        self.redirect(self.url(self.context))
520        return
[6078]521
[5686]522    def update(self):
[5941]523        if self.context.locked:
[6198]524            self.redirect(self.url(self.context))
[5941]525            return
[6040]526        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]527        super(EditApplicantStudent, self).update()
[5686]528        return
[5952]529
[6196]530    def dataNotComplete(self):
531        if self.context.confirm_passport is not True:
532            return 'Passport confirmation box not ticked.'
533        if len(self.errors) > 0:
534            return 'Form has errors.'
535        return False
[5952]536
[5273]537    @grok.action('Save')
538    def save(self, **data):
[5941]539        if self.context.locked:
540            self.emitLockMessage()
541            return
[5273]542        self.applyData(self.context, **data)
543        self.context._p_changed = True
[6196]544        self.flash('Form has been saved.')
[5273]545        return
546
[5484]547    @grok.action('Final Submit')
548    def finalsubmit(self, **data):
[5941]549        if self.context.locked:
550            self.emitLockMessage()
551            return
[5273]552        self.applyData(self.context, **data)
[5484]553        self.context._p_changed = True
[6196]554        if self.dataNotComplete():
555            self.flash(self.dataNotComplete())
[5941]556            return
557        self.context.locked = True
[6196]558        self.flash('Form has been submitted.')
559        self.redirect(self.url(self.context))
[5273]560        return
[5941]561
Note: See TracBrowser for help on using the repository browser.