source: main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py @ 11031

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

Some minor changes on. The po files are now released for editing.

  • Property svn:keywords set to Id
File size: 69.0 KB
RevLine 
[7191]1## $Id: browser.py 7745 2012-03-02 06:53:59Z henrik $
2##
[6621]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for students and related components.
19"""
[7006]20import sys
[6621]21import grok
[7275]22from urllib import urlencode
[6869]23from time import time
[7256]24from datetime import datetime
[7015]25from zope.event import notify
[7723]26from zope.i18n import translate
[6996]27from zope.catalog.interfaces import ICatalog
[7386]28from zope.component import queryUtility, getUtility, createObject
29from zope.formlib.textwidgets import BytesDisplayWidget
[6621]30from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6936]31from waeup.sirp.accesscodes import (
[6937]32    invalidate_accesscode, get_access_code, create_accesscode)
[6621]33from waeup.sirp.accesscodes.workflow import USED
34from waeup.sirp.browser import (
[7321]35    SIRPPage, SIRPEditFormPage, SIRPAddFormPage, SIRPDisplayFormPage,
[7369]36    ContactAdminForm, SIRPForm)
[7591]37from waeup.sirp.browser.interfaces import ICaptchaManager
[6621]38from waeup.sirp.browser.breadcrumbs import Breadcrumb
[7329]39from waeup.sirp.browser.resources import datepicker, datatable, tabs, warning
[7459]40from waeup.sirp.browser.layout import jsaction, action, UtilityView
[7147]41from waeup.sirp.interfaces import (
[7358]42    ISIRPObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm,
[7369]43    ISIRPUtils, IUniversity)
[7687]44from waeup.sirp.interfaces import MessageFactory as _
[6621]45from waeup.sirp.widgets.datewidget import (
[6869]46    FriendlyDateWidget, FriendlyDateDisplayWidget,
47    FriendlyDatetimeDisplayWidget)
[6621]48from waeup.sirp.students.interfaces import (
[7144]49    IStudentsContainer, IStudent, IStudentClearance,
[6859]50    IStudentPersonal, IStudentBase, IStudentStudyCourse,
[7538]51    IStudentAccommodation, IStudentStudyLevel,
[6877]52    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
[7369]53    IStudentOnlinePayment, IBedTicket, IStudentsUtils, IStudentChangePassword
[6621]54    )
[6626]55from waeup.sirp.students.catalog import search
[7539]56from waeup.sirp.students.workflow import (ADMITTED, PAID,
[7158]57    CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED)
[6795]58from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
[6775]59from waeup.sirp.students.vocabularies import StudyLevelSource
[6820]60from waeup.sirp.browser.resources import toggleall
[6997]61from waeup.sirp.hostels.hostel import NOT_OCCUPIED
[7459]62from waeup.sirp.utils.helpers import get_current_principal
[6621]63
[6943]64def write_log_message(view, message):
65    ob_class = view.__implemented__.__name__.replace('waeup.sirp.','')
66    view.context.getStudent().loggerInfo(ob_class, message)
67    return
68
[7133]69# Save function used for save methods in pages
[6762]70def msave(view, **data):
71    changed_fields = view.applyData(view.context, **data)
[6771]72    # Turn list of lists into single list
73    if changed_fields:
74        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7205]75    # Inform catalog if certificate has changed
76    # (applyData does this only for the context)
77    if 'certificate' in changed_fields:
78        notify(grok.ObjectModifiedEvent(view.context.getStudent()))
[6771]79    fields_string = ' + '.join(changed_fields)
[7723]80    view.flash(_('Form has been saved.'))
[6762]81    if fields_string:
[7108]82        write_log_message(view, 'saved: %s' % fields_string)
[6762]83    return
84
[7145]85def emit_lock_message(view):
[7642]86    """Flash a lock message.
87    """
[7723]88    view.flash(_('The requested form is locked (read-only).'))
[7133]89    view.redirect(view.url(view.context))
90    return
91
[6629]92class StudentsBreadcrumb(Breadcrumb):
93    """A breadcrumb for the students container.
94    """
95    grok.context(IStudentsContainer)
[7723]96    title = _('Students')
[6629]97
[7459]98    @property
99    def target(self):
100        user = get_current_principal()
101        if getattr(user, 'user_type', None) == 'student':
102            return None
103        return self.viewname
104
[6818]105class StudentBreadcrumb(Breadcrumb):
106    """A breadcrumb for the student container.
107    """
108    grok.context(IStudent)
109
110    def title(self):
[7364]111        return self.context.display_fullname
[6818]112
[6635]113class SudyCourseBreadcrumb(Breadcrumb):
114    """A breadcrumb for the student study course.
115    """
116    grok.context(IStudentStudyCourse)
[7723]117    title = _('Study Course')
[6635]118
119class PaymentsBreadcrumb(Breadcrumb):
120    """A breadcrumb for the student payments folder.
121    """
[6859]122    grok.context(IStudentPaymentsContainer)
[7723]123    title = _('Payments')
[6635]124
[6870]125class OnlinePaymentBreadcrumb(Breadcrumb):
[7251]126    """A breadcrumb for payments.
[6870]127    """
[6877]128    grok.context(IStudentOnlinePayment)
[6870]129
130    @property
131    def title(self):
132        return self.context.p_id
133
[6635]134class AccommodationBreadcrumb(Breadcrumb):
135    """A breadcrumb for the student accommodation folder.
136    """
137    grok.context(IStudentAccommodation)
[7723]138    title = _('Accommodation')
[6635]139
[6994]140class BedTicketBreadcrumb(Breadcrumb):
141    """A breadcrumb for bed tickets.
142    """
143    grok.context(IBedTicket)
[7009]144
[6994]145    @property
146    def title(self):
[7723]147        return _('Bed Ticket ${a}',
148            mapping = {'a':self.context.getSessionString()})
[6994]149
[6776]150class StudyLevelBreadcrumb(Breadcrumb):
151    """A breadcrumb for course lists.
152    """
153    grok.context(IStudentStudyLevel)
154
155    @property
156    def title(self):
[7723]157        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
158        # There is no request attribute, thus we can only translate
159        # to the default (portal) language
160        return translate(self.context.level_title, 'waeup.sirp',
161            target_language=portal_language)
[6776]162
[7321]163class StudentsContainerPage(SIRPPage):
[6626]164    """The standard view for student containers.
[6621]165    """
166    grok.context(IStudentsContainer)
167    grok.name('index')
[7240]168    grok.require('waeup.viewStudentsContainer')
[6695]169    grok.template('containerpage')
[7723]170    label = _('Student Section')
[7735]171    search_button = _('Search')
[6642]172    pnav = 4
[6621]173
[6626]174    def update(self, *args, **kw):
175        datatable.need()
176        form = self.request.form
177        self.hitlist = []
178        if 'searchterm' in form and form['searchterm']:
179            self.searchterm = form['searchterm']
180            self.searchtype = form['searchtype']
181        elif 'old_searchterm' in form:
182            self.searchterm = form['old_searchterm']
183            self.searchtype = form['old_searchtype']
184        else:
185            if 'search' in form:
[7745]186                self.flash(_('Empty search string'))
[6626]187            return
[7068]188        if self.searchtype == 'current_session':
189            self.searchterm = int(self.searchterm)
[6626]190        self.hitlist = search(query=self.searchterm,
191            searchtype=self.searchtype, view=self)
192        if not self.hitlist:
193            self.flash('No student found.')
194        return
195
[7321]196class StudentsContainerManagePage(SIRPPage):
[6626]197    """The manage page for student containers.
[6622]198    """
199    grok.context(IStudentsContainer)
200    grok.name('manage')
[7136]201    grok.require('waeup.manageStudent')
[6695]202    grok.template('containermanagepage')
[6642]203    pnav = 4
[7723]204    label = _('Manage student section')
[7735]205    search_button = _('Search')
206    remove_button = _('Remove selected')
[6622]207
[6626]208    def update(self, *args, **kw):
209        datatable.need()
[6820]210        toggleall.need()
[7329]211        warning.need()
[6626]212        form = self.request.form
213        self.hitlist = []
214        if 'searchterm' in form and form['searchterm']:
215            self.searchterm = form['searchterm']
216            self.searchtype = form['searchtype']
217        elif 'old_searchterm' in form:
218            self.searchterm = form['old_searchterm']
219            self.searchtype = form['old_searchtype']
220        else:
221            if 'search' in form:
[7745]222                self.flash(_('Empty search string'))
[6626]223            return
224        if not 'entries' in form:
225            self.hitlist = search(query=self.searchterm,
226                searchtype=self.searchtype, view=self)
227            if not self.hitlist:
[7723]228                self.flash(_('No student found.'))
[7459]229            if 'remove' in form:
[7723]230                self.flash(_('No item selected.'))
[6626]231            return
232        entries = form['entries']
233        if isinstance(entries, basestring):
234            entries = [entries]
235        deleted = []
236        for entry in entries:
237            if 'remove' in form:
238                del self.context[entry]
239                deleted.append(entry)
240        self.hitlist = search(query=self.searchterm,
241            searchtype=self.searchtype, view=self)
242        if len(deleted):
[7723]243            self.flash(_('Successfully removed: ${a}',
244                mapping = {'a':', '.join(deleted)}))
[6622]245        return
246
[7321]247class StudentAddFormPage(SIRPAddFormPage):
[6622]248    """Add-form to add a student.
249    """
250    grok.context(IStudentsContainer)
[7136]251    grok.require('waeup.manageStudent')
[6622]252    grok.name('addstudent')
[7357]253    form_fields = grok.AutoFields(IStudent).select(
[7520]254        'firstname', 'middlename', 'lastname', 'reg_number')
[7723]255    label = _('Add student')
[6642]256    pnav = 4
[6622]257
[7723]258    @action(_('Create student record'), style='primary')
[6622]259    def addStudent(self, **data):
260        student = createObject(u'waeup.Student')
261        self.applyData(student, **data)
[6652]262        self.context.addStudent(student)
[7723]263        self.flash(_('Student record created.'))
[6651]264        self.redirect(self.url(self.context[student.student_id], 'index'))
[6622]265        return
266
[7321]267class StudentBaseDisplayFormPage(SIRPDisplayFormPage):
[6631]268    """ Page to display student base data
269    """
[6622]270    grok.context(IStudent)
271    grok.name('index')
[6660]272    grok.require('waeup.viewStudent')
[6695]273    grok.template('basepage')
[6756]274    form_fields = grok.AutoFields(IStudentBase).omit('password')
[6642]275    pnav = 4
[6622]276
277    @property
278    def label(self):
[7723]279        return _('${a}: Base Data',
280            mapping = {'a':self.context.display_fullname})
[6631]281
[6699]282    @property
283    def hasPassword(self):
284        if self.context.password:
[7723]285            return _('set')
286        return _('unset')
[6699]287
[7229]288class ContactStudentForm(ContactAdminForm):
289    grok.context(IStudent)
[7230]290    grok.name('contactstudent')
[7275]291    grok.require('waeup.viewStudent')
[7229]292    pnav = 4
293    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
294
[7275]295    def update(self, subject=u''):
296        self.form_fields.get('subject').field.default = subject
297        self.subject = subject
298        return
299
[7229]300    def label(self):
[7723]301        return _(u'Send message to ${a}',
302            mapping = {'a':self.context.display_fullname})
[7229]303
[7459]304    @action('Send message now', style='primary')
[7229]305    def send(self, *args, **data):
[7234]306        try:
[7403]307            email = self.request.principal.email
[7234]308        except AttributeError:
[7403]309            email = self.config.email_admin
310        usertype = getattr(self.request.principal,
311                           'user_type', 'system').title()
[7358]312        sirp_utils = getUtility(ISIRPUtils)
[7404]313        success = sirp_utils.sendContactForm(
[7403]314                self.request.principal.title,email,
315                self.context.display_fullname,self.context.email,
316                self.request.principal.id,usertype,
317                self.config.name,
318                data['body'],data['subject'])
[7229]319        if success:
[7723]320            self.flash(_('Your message has been sent.'))
[7229]321        else:
[7723]322            self.flash(_('An smtp server error occurred.'))
[7229]323        return
324
[7321]325class StudentBaseManageFormPage(SIRPEditFormPage):
[7133]326    """ View to manage student base data
[6631]327    """
328    grok.context(IStudent)
[7133]329    grok.name('manage_base')
[7136]330    grok.require('waeup.manageStudent')
[6631]331    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
[6695]332    grok.template('basemanagepage')
[7723]333    label = _('Manage base data')
[6642]334    pnav = 4
[6631]335
[6638]336    def update(self):
337        datepicker.need() # Enable jQuery datepicker in date fields.
[7134]338        tabs.need()
[7490]339        self.tab1 = self.tab2 = ''
340        qs = self.request.get('QUERY_STRING', '')
341        if not qs:
342            qs = 'tab1'
343        setattr(self, qs, 'active')
[6638]344        super(StudentBaseManageFormPage, self).update()
345        self.wf_info = IWorkflowInfo(self.context)
346        return
347
348    def getTransitions(self):
349        """Return a list of dicts of allowed transition ids and titles.
350
351        Each list entry provides keys ``name`` and ``title`` for
352        internal name and (human readable) title of a single
353        transition.
354        """
355        allowed_transitions = self.wf_info.getManualTransitions()
[7687]356        return [dict(name='', title=_('No transition'))] +[
[6638]357            dict(name=x, title=y) for x, y in allowed_transitions]
358
[7723]359    @action(_('Save'), style='primary')
[6638]360    def save(self, **data):
[6701]361        form = self.request.form
[6790]362        password = form.get('password', None)
363        password_ctl = form.get('control_password', None)
364        if password:
[7147]365            validator = getUtility(IPasswordValidator)
366            errors = validator.validate_password(password, password_ctl)
367            if errors:
368                self.flash( ' '.join(errors))
369                return
370        changed_fields = self.applyData(self.context, **data)
[6771]371        # Turn list of lists into single list
372        if changed_fields:
373            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7147]374        else:
375            changed_fields = []
376        if password:
377            # Now we know that the form has no errors and can set password ...
378            IUserAccount(self.context).setPassword(password)
379            changed_fields.append('password')
380        # ... and execute transition
[6638]381        if form.has_key('transition') and form['transition']:
382            transition_id = form['transition']
383            self.wf_info.fireTransition(transition_id)
[7147]384        fields_string = ' + '.join(changed_fields)
[7723]385        self.flash(_('Form has been saved.'))
[6644]386        if fields_string:
[6943]387            write_log_message(self, 'saved: % s' % fields_string)
[6638]388        return
389
[7321]390class StudentClearanceDisplayFormPage(SIRPDisplayFormPage):
[6631]391    """ Page to display student clearance data
392    """
393    grok.context(IStudent)
394    grok.name('view_clearance')
[6660]395    grok.require('waeup.viewStudent')
[6695]396    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
[6650]397    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[6642]398    pnav = 4
[6631]399
400    @property
401    def label(self):
[7723]402        return _('${a}: Clearance Data',
403            mapping = {'a':self.context.display_fullname})
[6631]404
[7277]405class ExportPDFClearanceSlipPage(grok.View):
406    """Deliver a PDF slip of the context.
407    """
408    grok.context(IStudent)
409    grok.name('clearance.pdf')
410    grok.require('waeup.viewStudent')
411    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
[7318]412    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[7277]413    prefix = 'form'
414
415    @property
[7723]416    def title(self):
417        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
418        return translate(_('Clearance Data'), 'waeup.sirp',
419            target_language=portal_language)
420
421    @property
[7277]422    def label(self):
[7723]423        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
424        return translate(_('Clearance Slip of '),
425            'waeup.sirp', target_language=portal_language) \
426            + ' %s' % self.context.display_fullname
[7277]427
428    def render(self):
429        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
430            self.request)
431        students_utils = getUtility(IStudentsUtils)
432        return students_utils.renderPDF(
[7318]433            self, 'clearance.pdf',
[7310]434            self.context.getStudent(), studentview)
[7277]435
[7321]436class StudentClearanceManageFormPage(SIRPEditFormPage):
[6631]437    """ Page to edit student clearance data
438    """
439    grok.context(IStudent)
440    grok.name('edit_clearance')
[7136]441    grok.require('waeup.manageStudent')
[7134]442    grok.template('clearanceeditpage')
[6631]443    form_fields = grok.AutoFields(IStudentClearance)
[7723]444    label = _('Manage clearance data')
[6642]445    pnav = 4
[6650]446    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
447
448    def update(self):
449        datepicker.need() # Enable jQuery datepicker in date fields.
[7134]450        tabs.need()
[7490]451        self.tab1 = self.tab2 = ''
452        qs = self.request.get('QUERY_STRING', '')
453        if not qs:
454            qs = 'tab1'
455        setattr(self, qs, 'active')
[6650]456        return super(StudentClearanceManageFormPage, self).update()
457
[7723]458    @action(_('Save'), style='primary')
[6695]459    def save(self, **data):
[6762]460        msave(self, **data)
[6695]461        return
462
[7459]463class StudentClearPage(UtilityView, grok.View):
[7158]464    """ Clear student by clearance officer
465    """
466    grok.context(IStudent)
467    grok.name('clear')
468    grok.require('waeup.clearStudent')
469
470    def update(self):
471        if self.context.state == REQUESTED:
472            IWorkflowInfo(self.context).fireTransition('clear')
[7723]473            self.flash(_('Student has been cleared.'))
[7158]474        else:
[7723]475            self.flash(_('Student is in wrong state.'))
[7158]476        self.redirect(self.url(self.context,'view_clearance'))
477        return
478
479    def render(self):
480        return
481
[7459]482class StudentRejectClearancePage(UtilityView, grok.View):
[7158]483    """ Reject clearance by clearance officers
484    """
485    grok.context(IStudent)
486    grok.name('reject_clearance')
487    grok.require('waeup.clearStudent')
488
489    def update(self):
490        if self.context.state == CLEARED:
491            IWorkflowInfo(self.context).fireTransition('reset4')
[7723]492            message = _('Clearance has been annulled.')
[7275]493            self.flash(message)
[7158]494        elif self.context.state == REQUESTED:
495            IWorkflowInfo(self.context).fireTransition('reset3')
[7723]496            message = _('Clearance request has been rejected.')
[7275]497            self.flash(message)
[7158]498        else:
[7723]499            self.flash(_('Student is in wrong state.'))
[7334]500            self.redirect(self.url(self.context,'view_clearance'))
[7275]501            return
502        args = {'subject':message}
503        self.redirect(self.url(self.context) +
504            '/contactstudent?%s' % urlencode(args))
[7158]505        return
506
507    def render(self):
508        return
509
[7321]510class StudentPersonalDisplayFormPage(SIRPDisplayFormPage):
[6631]511    """ Page to display student personal data
512    """
513    grok.context(IStudent)
514    grok.name('view_personal')
[6660]515    grok.require('waeup.viewStudent')
[6631]516    form_fields = grok.AutoFields(IStudentPersonal)
[7386]517    form_fields['perm_address'].custom_widget = BytesDisplayWidget
[6642]518    pnav = 4
[6631]519
520    @property
521    def label(self):
[7723]522        return _('${a}: Personal Data',
523            mapping = {'a':self.context.display_fullname})
[6631]524
[7321]525class StudentPersonalManageFormPage(SIRPEditFormPage):
[6631]526    """ Page to edit student clearance data
527    """
528    grok.context(IStudent)
529    grok.name('edit_personal')
[6660]530    grok.require('waeup.viewStudent')
[6631]531    form_fields = grok.AutoFields(IStudentPersonal)
[7723]532    label = _('Manage personal data')
[6642]533    pnav = 4
[6631]534
[7723]535    @action(_('Save'), style='primary')
[6762]536    def save(self, **data):
537        msave(self, **data)
538        return
539
[7321]540class StudyCourseDisplayFormPage(SIRPDisplayFormPage):
[6635]541    """ Page to display the student study course data
542    """
543    grok.context(IStudentStudyCourse)
544    grok.name('index')
[6660]545    grok.require('waeup.viewStudent')
[6635]546    form_fields = grok.AutoFields(IStudentStudyCourse)
[6775]547    grok.template('studycoursepage')
[6642]548    pnav = 4
[6635]549
550    @property
551    def label(self):
[7723]552        return _('${a}: Study Course',
553            mapping = {'a':self.context.__parent__.display_fullname})
[6635]554
[6912]555    @property
556    def current_mode(self):
[7641]557        if self.context.certificate is not None:
[7681]558            studymodes_dict = getUtility(ISIRPUtils).getStudyModesDict()
559            return studymodes_dict[self.context.certificate.study_mode]
[7171]560        return
[7642]561
[7171]562    @property
563    def department(self):
[7205]564        if self.context.certificate is not None:
[7171]565            return self.context.certificate.__parent__.__parent__
566        return
[6912]567
[7171]568    @property
569    def faculty(self):
[7205]570        if self.context.certificate is not None:
[7171]571            return self.context.certificate.__parent__.__parent__.__parent__
572        return
573
[7321]574class StudyCourseManageFormPage(SIRPEditFormPage):
[6649]575    """ Page to edit the student study course data
576    """
577    grok.context(IStudentStudyCourse)
[6775]578    grok.name('manage')
[7136]579    grok.require('waeup.manageStudent')
[6775]580    grok.template('studycoursemanagepage')
[6649]581    form_fields = grok.AutoFields(IStudentStudyCourse)
[7723]582    label = _('Manage study course')
[6649]583    pnav = 4
[7723]584    taboneactions = [_('Save'),_('Cancel')]
585    tabtwoactions = [_('Remove selected levels'),_('Cancel')]
586    tabthreeactions = [_('Add study level')]
[6649]587
[6775]588    def update(self):
589        super(StudyCourseManageFormPage, self).update()
590        tabs.need()
[7484]591        self.tab1 = self.tab2 = ''
592        qs = self.request.get('QUERY_STRING', '')
593        if not qs:
594            qs = 'tab1'
595        setattr(self, qs, 'active')
[7490]596        warning.need()
597        datatable.need()
598        return
[6775]599
[7723]600    @action(_('Save'), style='primary')
[6761]601    def save(self, **data):
[6762]602        msave(self, **data)
[6761]603        return
604
[6775]605    @property
606    def level_dict(self):
607        studylevelsource = StudyLevelSource().factory
608        for code in studylevelsource.getValues(self.context):
609            title = studylevelsource.getTitle(self.context, code)
610            yield(dict(code=code, title=title))
611
[7723]612    @action(_('Add study level'))
[6774]613    def addStudyLevel(self, **data):
[6775]614        level_code = self.request.form.get('addlevel', None)
[6774]615        studylevel = StudentStudyLevel()
[6775]616        studylevel.level = int(level_code)
617        try:
[6782]618            self.context.addStudentStudyLevel(
619                self.context.certificate,studylevel)
[7723]620            self.flash(_('Study level has been added.'))
[6775]621        except KeyError:
[7723]622            self.flash(_('This level exists.'))
[7484]623        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
[6774]624        return
625
[7723]626    @jsaction(_('Remove selected levels'))
[6775]627    def delStudyLevels(self, **data):
628        form = self.request.form
629        if form.has_key('val_id'):
630            child_id = form['val_id']
631        else:
[7723]632            self.flash(_('No study level selected.'))
[7484]633            self.redirect(self.url(self.context, '@@manage')+'?tab2')
[6775]634            return
635        if not isinstance(child_id, list):
636            child_id = [child_id]
637        deleted = []
638        for id in child_id:
[7723]639            del self.context[id]
640            deleted.append(id)
[6775]641        if len(deleted):
[7723]642            self.flash(_('Successfully removed: ${a}',
643                mapping = {'a':', '.join(deleted)}))
[7484]644        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
[6775]645        return
[6774]646
[7321]647class StudyLevelDisplayFormPage(SIRPDisplayFormPage):
[6774]648    """ Page to display student study levels
649    """
650    grok.context(IStudentStudyLevel)
651    grok.name('index')
652    grok.require('waeup.viewStudent')
[6775]653    form_fields = grok.AutoFields(IStudentStudyLevel)
[6783]654    grok.template('studylevelpage')
[6774]655    pnav = 4
656
[7310]657    def update(self):
658        super(StudyLevelDisplayFormPage, self).update()
659        datatable.need()
660        return
661
[6774]662    @property
663    def label(self):
[7723]664        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
665        lang = self.request.cookies.get('sirp.language', portal_language)
666        level_title = translate(self.context.level_title, 'waeup.sirp',
667            target_language=lang)
668        return _('${a}: Study Level ${b}', mapping = {
669            'a':self.context.getStudent().display_fullname,
670            'b':level_title})
[6774]671
[6803]672    @property
673    def total_credits(self):
674        total_credits = 0
675        for key, val in self.context.items():
676            total_credits += val.credits
677        return total_credits
678
[7459]679class ExportPDFCourseRegistrationSlipPage(UtilityView, grok.View):
[7028]680    """Deliver a PDF slip of the context.
681    """
682    grok.context(IStudentStudyLevel)
683    grok.name('course_registration.pdf')
684    grok.require('waeup.viewStudent')
685    form_fields = grok.AutoFields(IStudentStudyLevel)
686    prefix = 'form'
687
688    @property
[7723]689    def title(self):
690        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
691        return translate(_('Level Data'), 'waeup.sirp',
692            target_language=portal_language)
693
694    @property
695    def content_title(self):
696        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
697        return translate(_('Course List'), 'waeup.sirp',
698            target_language=portal_language)
699
700    @property
[7028]701    def label(self):
[7723]702        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
703        lang = self.request.cookies.get('sirp.language', portal_language)
704        level_title = translate(self.context.level_title, 'waeup.sirp',
705            target_language=lang)
706        return translate(_('Course Registration Slip  '),
707            'waeup.sirp', target_language=portal_language) \
708            + ' %s' % level_title
[7028]709
710    def render(self):
[7723]711        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
712        Sem = translate(_('Sem.'), 'waeup.sirp', target_language=portal_language)
713        Code = translate(_('Code'), 'waeup.sirp', target_language=portal_language)
714        Title = translate(_('Title'), 'waeup.sirp', target_language=portal_language)
715        Dept = translate(_('Dept.'), 'waeup.sirp', target_language=portal_language)
716        Faculty = translate(_('Faculty'), 'waeup.sirp', target_language=portal_language)
717        Cred = translate(_('Cred.'), 'waeup.sirp', target_language=portal_language)
718        Mand = translate(_('Mand.'), 'waeup.sirp', target_language=portal_language)
719        Score = translate(_('Score'), 'waeup.sirp', target_language=portal_language)
[7028]720        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
721            self.request)
[7150]722        students_utils = getUtility(IStudentsUtils)
[7318]723        tabledata = sorted(self.context.values(),
724            key=lambda value: str(value.semester) + value.code)
[7186]725        return students_utils.renderPDF(
[7318]726            self, 'course_registration.pdf',
[7310]727            self.context.getStudent(), studentview,
[7723]728            tableheader=[(Sem,'semester', 1.5),(Code,'code', 2.5),
729                         (Title,'title', 5),
730                         (Dept,'dcode', 1.5), (Faculty,'fcode', 1.5),
731                         (Cred, 'credits', 1.5),
732                         (Mand, 'mandatory', 1.5),
733                         (Score, 'score', 1.5),('Auto', 'automatic', 1.5)
[7304]734                         ],
[7318]735            tabledata=tabledata)
[7028]736
[7321]737class StudyLevelManageFormPage(SIRPEditFormPage):
[6792]738    """ Page to edit the student study level data
739    """
740    grok.context(IStudentStudyLevel)
741    grok.name('manage')
[7136]742    grok.require('waeup.manageStudent')
[6792]743    grok.template('studylevelmanagepage')
744    form_fields = grok.AutoFields(IStudentStudyLevel)
745    pnav = 4
[7723]746    taboneactions = [_('Save'),_('Cancel')]
747    tabtwoactions = [_('Add course ticket'),
748        _('Remove selected tickets'),_('Cancel')]
[6792]749
750    def update(self):
751        super(StudyLevelManageFormPage, self).update()
752        tabs.need()
[7484]753        self.tab1 = self.tab2 = ''
754        qs = self.request.get('QUERY_STRING', '')
755        if not qs:
756            qs = 'tab1'
757        setattr(self, qs, 'active')
[7490]758        warning.need()
759        datatable.need()
[6792]760        return
761
762    @property
763    def label(self):
[7723]764        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
765        lang = self.request.cookies.get('sirp.language', portal_language)
766        level_title = translate(self.context.level_title, 'waeup.sirp',
767            target_language=lang)
768        return _('Manage study level ${a}',
769            mapping = {'a':level_title})
[6792]770
[7723]771    @action(_('Save'), style='primary')
[6792]772    def save(self, **data):
773        msave(self, **data)
774        return
775
[7723]776    @action(_('Add course ticket'))
[6795]777    def addCourseTicket(self, **data):
778        self.redirect(self.url(self.context, '@@add'))
[6792]779
[7723]780    @jsaction(_('Remove selected tickets'))
[6792]781    def delCourseTicket(self, **data):
782        form = self.request.form
783        if form.has_key('val_id'):
784            child_id = form['val_id']
785        else:
[7723]786            self.flash(_('No ticket selected.'))
[7484]787            self.redirect(self.url(self.context, '@@manage')+'?tab2')
[6792]788            return
789        if not isinstance(child_id, list):
790            child_id = [child_id]
791        deleted = []
792        for id in child_id:
[7723]793            del self.context[id]
794            deleted.append(id)
[6792]795        if len(deleted):
[7723]796            self.flash(_('Successfully removed: ${a}',
797                mapping = {'a':', '.join(deleted)}))
[7484]798        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
[6792]799        return
800
[7459]801class ValidateCoursesPage(UtilityView, grok.View):
[7334]802    """ Validate course list by course adviser
803    """
804    grok.context(IStudentStudyLevel)
805    grok.name('validate_courses')
806    grok.require('waeup.validateStudent')
807
808    def update(self):
809        if str(self.context.__parent__.current_level) != self.context.__name__:
[7723]810            self.flash(_('This level does not correspond current level.'))
[7334]811        elif self.context.getStudent().state == REGISTERED:
[7642]812            IWorkflowInfo(self.context.getStudent()).fireTransition(
813                'validate_courses')
[7723]814            self.flash(_('Course list has been validated.'))
[7334]815        else:
[7723]816            self.flash(_('Student is in the wrong state.'))
[7334]817        self.redirect(self.url(self.context))
818        return
819
820    def render(self):
821        return
822
[7459]823class RejectCoursesPage(UtilityView, grok.View):
[7334]824    """ Reject course list by course adviser
825    """
826    grok.context(IStudentStudyLevel)
827    grok.name('reject_courses')
828    grok.require('waeup.validateStudent')
829
830    def update(self):
831        if str(self.context.__parent__.current_level) != self.context.__name__:
[7723]832            self.flash(_('This level does not correspond current level.'))
[7334]833            self.redirect(self.url(self.context))
834            return
835        elif self.context.getStudent().state == VALIDATED:
836            IWorkflowInfo(self.context.getStudent()).fireTransition('reset8')
[7723]837            message = _('Course list request has been annulled.')
[7334]838            self.flash(message)
839        elif self.context.getStudent().state == REGISTERED:
840            IWorkflowInfo(self.context.getStudent()).fireTransition('reset7')
[7723]841            message = _('Course list request has been rejected:')
[7334]842            self.flash(message)
843        else:
[7723]844            self.flash(_('Student is in the wrong state.'))
[7334]845            self.redirect(self.url(self.context))
846            return
847        args = {'subject':message}
848        self.redirect(self.url(self.context.getStudent()) +
849            '/contactstudent?%s' % urlencode(args))
850        return
851
852    def render(self):
853        return
854
[7321]855class CourseTicketAddFormPage(SIRPAddFormPage):
[6808]856    """Add a course ticket.
[6795]857    """
858    grok.context(IStudentStudyLevel)
859    grok.name('add')
[7136]860    grok.require('waeup.manageStudent')
[7723]861    label = _('Add course ticket')
[6808]862    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
[7663]863        'grade', 'score', 'automatic', 'carry_over')
[6795]864    pnav = 4
865
[7723]866    @action(_('Add course ticket'))
[6795]867    def addCourseTicket(self, **data):
868        ticket = CourseTicket()
869        course = data['course']
[6802]870        ticket.automatic = False
[6795]871        ticket.code = course.code
872        ticket.title = course.title
[7304]873        ticket.fcode = course.__parent__.__parent__.__parent__.code
874        ticket.dcode = course.__parent__.__parent__.code
[6795]875        ticket.credits = course.credits
876        ticket.passmark = course.passmark
877        ticket.semester = course.semester
878        try:
879            self.context.addCourseTicket(ticket)
880        except KeyError:
[7723]881            self.flash(_('The ticket exists.'))
[6795]882            return
[7723]883        self.flash(_('Successfully added ${a}.',
884            mapping = {'a':ticket.code}))
[7484]885        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
[6795]886        return
887
[7723]888    @action(_('Cancel'))
[6795]889    def cancel(self, **data):
890        self.redirect(self.url(self.context))
891
[7321]892class CourseTicketDisplayFormPage(SIRPDisplayFormPage):
[6796]893    """ Page to display course tickets
894    """
895    grok.context(ICourseTicket)
896    grok.name('index')
897    grok.require('waeup.viewStudent')
898    form_fields = grok.AutoFields(ICourseTicket)
899    grok.template('courseticketpage')
900    pnav = 4
901
902    @property
903    def label(self):
[7723]904        return _('${a}: Course Ticket ${b}', mapping = {
905            'a':self.context.getStudent().display_fullname,
906            'b':self.context.code})
[6796]907
[7321]908class CourseTicketManageFormPage(SIRPEditFormPage):
[6796]909    """ Page to manage course tickets
910    """
911    grok.context(ICourseTicket)
912    grok.name('manage')
[7136]913    grok.require('waeup.manageStudent')
[6796]914    form_fields = grok.AutoFields(ICourseTicket)
915    grok.template('courseticketmanagepage')
916    pnav = 4
917
918    @property
919    def label(self):
[7723]920        return _('Manage course ticket ${a}', mapping = {'a':self.context.code})
[6796]921
[7459]922    @action('Save', style='primary')
[6796]923    def save(self, **data):
924        msave(self, **data)
925        return
926
[7321]927class PaymentsManageFormPage(SIRPEditFormPage):
[6869]928    """ Page to manage the student payments
[7642]929
930    This manage form page is for both students and students officers.
[6869]931    """
932    grok.context(IStudentPaymentsContainer)
[6940]933    grok.name('index')
[7181]934    grok.require('waeup.payStudent')
[6869]935    form_fields = grok.AutoFields(IStudentPaymentsContainer)
936    grok.template('paymentsmanagepage')
937    pnav = 4
938
[6940]939    def unremovable(self, ticket):
[7251]940        usertype = getattr(self.request.principal, 'user_type', None)
941        if not usertype:
942            return False
943        return (self.request.principal.user_type == 'student' and ticket.r_code)
[6940]944
[6869]945    @property
946    def label(self):
[7723]947        return _('${a}: Payments',
948            mapping = {'a':self.context.__parent__.display_fullname})
[6869]949
950    def update(self):
951        super(PaymentsManageFormPage, self).update()
952        datatable.need()
[7329]953        warning.need()
[6869]954        return
955
[7723]956    @jsaction(_('Remove selected tickets'))
[6869]957    def delPaymentTicket(self, **data):
958        form = self.request.form
959        if form.has_key('val_id'):
960            child_id = form['val_id']
961        else:
[7723]962            self.flash(_('No payment selected.'))
[6940]963            self.redirect(self.url(self.context))
[6869]964            return
965        if not isinstance(child_id, list):
966            child_id = [child_id]
967        deleted = []
968        for id in child_id:
[6992]969            # Students are not allowed to remove used payment tickets
[6940]970            if not self.unremovable(self.context[id]):
[7723]971                del self.context[id]
972                deleted.append(id)
[6869]973        if len(deleted):
[7723]974            self.flash(_('Successfully removed: ${a}',
975                mapping = {'a': ', '.join(deleted)}))
[6943]976            write_log_message(self,'removed: % s' % ', '.join(deleted))
[6940]977        self.redirect(self.url(self.context))
[6869]978        return
979
[7723]980    @action(_('Add online payment ticket'))
[6869]981    def addPaymentTicket(self, **data):
982        self.redirect(self.url(self.context, '@@addop'))
983
[7321]984class OnlinePaymentAddFormPage(SIRPAddFormPage):
[6869]985    """ Page to add an online payment ticket
986    """
987    grok.context(IStudentPaymentsContainer)
988    grok.name('addop')
[7181]989    grok.require('waeup.payStudent')
[6877]990    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
[6869]991        'p_category')
[7723]992    label = _('Add online payment')
[6869]993    pnav = 4
[7642]994
[7723]995    @action(_('Create ticket'), style='primary')
[6869]996    def createTicket(self, **data):
[7024]997        p_category = data['p_category']
998        student = self.context.__parent__
999        if p_category == 'bed_allocation' and student[
1000            'studycourse'].current_session != grok.getSite()[
1001            'configuration'].accommodation_session:
1002                self.flash(
[7723]1003                    _('Your current session does not match ' + \
1004                    'accommodation session.'))
[7024]1005                self.redirect(self.url(self.context))
1006                return
[7150]1007        students_utils = getUtility(IStudentsUtils)
[7186]1008        pay_details  = students_utils.getPaymentDetails(
[7024]1009            p_category,student)
[6994]1010        if pay_details['error']:
1011            self.flash(pay_details['error'])
[6940]1012            self.redirect(self.url(self.context))
[6920]1013            return
[7025]1014        p_item = pay_details['p_item']
1015        p_session = pay_details['p_session']
1016        for key in self.context.keys():
1017            ticket = self.context[key]
[7248]1018            if ticket.p_state == 'paid' and\
1019               ticket.p_category == p_category and \
[7025]1020               ticket.p_item == p_item and \
1021               ticket.p_session == p_session:
1022                  self.flash(
[7723]1023                      _('This type of payment has already been made.'))
[7025]1024                  self.redirect(self.url(self.context))
1025                  return
1026        payment = createObject(u'waeup.StudentOnlinePayment')
1027        self.applyData(payment, **data)
1028        timestamp = "%d" % int(time()*1000)
1029        payment.p_id = "p%s" % timestamp
1030        payment.p_item = p_item
1031        payment.p_session = p_session
[6994]1032        payment.amount_auth = pay_details['amount']
1033        payment.surcharge_1 = pay_details['surcharge_1']
1034        payment.surcharge_2 = pay_details['surcharge_2']
1035        payment.surcharge_3 = pay_details['surcharge_3']
[6869]1036        self.context[payment.p_id] = payment
[7723]1037        self.flash(_('Payment ticket created.'))
[6940]1038        self.redirect(self.url(self.context))
[6869]1039        return
1040
[7321]1041class OnlinePaymentDisplayFormPage(SIRPDisplayFormPage):
[6869]1042    """ Page to view an online payment ticket
1043    """
[6877]1044    grok.context(IStudentOnlinePayment)
[6869]1045    grok.name('index')
1046    grok.require('waeup.viewStudent')
[6877]1047    form_fields = grok.AutoFields(IStudentOnlinePayment)
[7642]1048    form_fields[
1049        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1050    form_fields[
1051        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[6869]1052    pnav = 4
1053
1054    @property
1055    def label(self):
[7723]1056        return _('${a}: Online Payment Ticket ${b}', mapping = {
1057            'a':self.context.getStudent().display_fullname,
1058            'b':self.context.p_id})
[6869]1059
[7459]1060class OnlinePaymentCallbackPage(UtilityView, grok.View):
[6930]1061    """ Callback view
1062    """
1063    grok.context(IStudentOnlinePayment)
1064    grok.name('callback')
1065    grok.require('waeup.payStudent')
1066
1067    # This update method simulates a valid callback und must be
1068    # specified in the customization package. The parameters must be taken
1069    # from the incoming request.
1070    def update(self):
[7026]1071        if self.context.p_state == 'paid':
[7723]1072            self.flash(_('This ticket has already been paid.'))
[7026]1073            return
[6936]1074        student = self.context.getStudent()
[6943]1075        write_log_message(self,'valid callback: %s' % self.context.p_id)
[6930]1076        self.context.r_amount_approved = self.context.amount_auth
1077        self.context.r_card_num = u'0000'
1078        self.context.r_code = u'00'
1079        self.context.p_state = 'paid'
1080        self.context.payment_date = datetime.now()
1081        if self.context.p_category == 'clearance':
[6936]1082            # Create CLR access code
[6937]1083            pin, error = create_accesscode('CLR',0,student.student_id)
[6936]1084            if error:
[7723]1085                self.flash(_('Valid callback received. ${a}',
1086                    mapping = {'a':error}))
[6936]1087                return
1088            self.context.ac = pin
[6930]1089        elif self.context.p_category == 'schoolfee':
[6936]1090            # Create SFE access code
[6940]1091            pin, error = create_accesscode('SFE',0,student.student_id)
[6936]1092            if error:
[7723]1093                self.flash(_('Valid callback received. ${a}',
1094                    mapping = {'a':error}))
[6936]1095                return
1096            self.context.ac = pin
[6994]1097        elif self.context.p_category == 'bed_allocation':
1098            # Create HOS access code
1099            pin, error = create_accesscode('HOS',0,student.student_id)
1100            if error:
[7723]1101                self.flash(_('Valid callback received. ${a}',
1102                    mapping = {'a':error}))
[6994]1103                return
1104            self.context.ac = pin
[7723]1105        self.flash(_('Valid callback received.'))
[6940]1106        return
[6930]1107
1108    def render(self):
[6940]1109        self.redirect(self.url(self.context, '@@index'))
[6930]1110        return
1111
[7459]1112class ExportPDFPaymentSlipPage(UtilityView, grok.View):
[7019]1113    """Deliver a PDF slip of the context.
1114    """
1115    grok.context(IStudentOnlinePayment)
1116    grok.name('payment_receipt.pdf')
1117    grok.require('waeup.viewStudent')
1118    form_fields = grok.AutoFields(IStudentOnlinePayment)
1119    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1120    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1121    prefix = 'form'
[7318]1122    title = 'Payment Data'
[7019]1123
1124    @property
1125    def label(self):
1126        return 'Online Payment Receipt %s' % self.context.p_id
1127
1128    def render(self):
[7028]1129        if self.context.p_state != 'paid':
1130            self.flash('Ticket not yet paid.')
1131            self.redirect(self.url(self.context))
1132            return
[7019]1133        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1134            self.request)
[7150]1135        students_utils = getUtility(IStudentsUtils)
[7318]1136        return students_utils.renderPDF(self, 'payment_receipt.pdf',
[7310]1137            self.context.getStudent(), studentview)
[7019]1138
[6992]1139
[7321]1140class AccommodationManageFormPage(SIRPEditFormPage):
[7009]1141    """ Page to manage bed tickets.
[7642]1142
1143    This manage form page is for both students and students officers.
[6635]1144    """
1145    grok.context(IStudentAccommodation)
1146    grok.name('index')
[7181]1147    grok.require('waeup.handleAccommodation')
[6635]1148    form_fields = grok.AutoFields(IStudentAccommodation)
[6992]1149    grok.template('accommodationmanagepage')
[6642]1150    pnav = 4
[7723]1151    officers_only_actions = [_('Remove selected')]
[6635]1152
1153    @property
1154    def label(self):
[7723]1155        return _('${a}: Accommodation',
1156            mapping = {'a':self.context.__parent__.display_fullname})
[6637]1157
[6992]1158    def update(self):
1159        super(AccommodationManageFormPage, self).update()
1160        datatable.need()
[7329]1161        warning.need()
[6992]1162        return
1163
[7723]1164    @jsaction(_('Remove selected'))
[7009]1165    def delBedTickets(self, **data):
[7240]1166        if getattr(self.request.principal, 'user_type', None) == 'student':
[7723]1167            self.flash(_('You are not allowed to remove bed tickets.'))
[7017]1168            self.redirect(self.url(self.context))
1169            return
[6992]1170        form = self.request.form
1171        if form.has_key('val_id'):
1172            child_id = form['val_id']
1173        else:
[7723]1174            self.flash(_('No bed ticket selected.'))
[6992]1175            self.redirect(self.url(self.context))
1176            return
1177        if not isinstance(child_id, list):
1178            child_id = [child_id]
1179        deleted = []
1180        for id in child_id:
[7068]1181            del self.context[id]
1182            deleted.append(id)
[6992]1183        if len(deleted):
[7723]1184            self.flash(_('Successfully removed: ${a}',
1185                mapping = {'a':', '.join(deleted)}))
[6992]1186            write_log_message(self,'removed: % s' % ', '.join(deleted))
1187        self.redirect(self.url(self.context))
1188        return
1189
[7009]1190    @property
1191    def selected_actions(self):
[7240]1192        if getattr(self.request.principal, 'user_type', None) == 'student':
[7642]1193            return [action for action in self.actions
1194                    if not action.label in self.officers_only_actions]
1195        return self.actions
[7009]1196
[7321]1197class BedTicketAddPage(SIRPPage):
[6992]1198    """ Page to add an online payment ticket
1199    """
1200    grok.context(IStudentAccommodation)
1201    grok.name('add')
[7181]1202    grok.require('waeup.handleAccommodation')
[6992]1203    grok.template('enterpin')
[6993]1204    ac_prefix = 'HOS'
[7723]1205    label = _('Add bed ticket')
[6992]1206    pnav = 4
[7723]1207    buttonname = _('Create bed ticket')
[6993]1208    notice = ''
[6992]1209
1210    def update(self, SUBMIT=None):
[6996]1211        student = self.context.getStudent()
[7150]1212        students_utils = getUtility(IStudentsUtils)
[7186]1213        acc_details  = students_utils.getAccommodationDetails(student)
[7369]1214        if not acc_details:
[7723]1215            self.flash(_("Your data are incomplete."))
[7369]1216            self.redirect(self.url(self.context))
1217            return
[6996]1218        if not student.state in acc_details['allowed_states']:
[7723]1219            self.flash(_("You are in the wrong registration state."))
[6992]1220            self.redirect(self.url(self.context))
1221            return
[7642]1222        if student['studycourse'].current_session != acc_details[
1223            'booking_session']:
[7061]1224            self.flash(
[7723]1225                _('Your current session does not match accommodation session.'))
[7061]1226            self.redirect(self.url(self.context))
1227            return
1228        if str(acc_details['booking_session']) in self.context.keys():
[7642]1229            self.flash(
[7723]1230                _('You already booked a bed space in current ' \
1231                    + 'accommodation session.'))
[7004]1232            self.redirect(self.url(self.context))
1233            return
[6992]1234        self.ac_series = self.request.form.get('ac_series', None)
1235        self.ac_number = self.request.form.get('ac_number', None)
1236        if SUBMIT is None:
1237            return
1238        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1239        code = get_access_code(pin)
1240        if not code:
[7723]1241            self.flash(_('Activation code is invalid.'))
[6992]1242            return
[7060]1243        # Search and book bed
[6997]1244        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1245        entries = cat.searchResults(
[7003]1246            owner=(student.student_id,student.student_id))
1247        if len(entries):
[7060]1248            # If bed space has bee manually allocated use this bed
[7003]1249            bed = [entry for entry in entries][0]
[7060]1250        else:
1251            # else search for other available beds
1252            entries = cat.searchResults(
1253                bed_type=(acc_details['bt'],acc_details['bt']))
1254            available_beds = [
1255                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1256            if available_beds:
[7150]1257                students_utils = getUtility(IStudentsUtils)
[7186]1258                bed = students_utils.selectBed(available_beds)
[7060]1259                bed.bookBed(student.student_id)
1260            else:
[7723]1261                self.flash(_('There is no free bed in your category ${a}.',
1262                    mapping = {'a':acc_details['bt']}))
[7060]1263                return
[6992]1264        # Mark pin as used (this also fires a pin related transition)
1265        if code.state == USED:
[7723]1266            self.flash(_('Activation code has already been used.'))
[6992]1267            return
1268        else:
[7723]1269            comment = _(u'invalidated')
[6992]1270            # Here we know that the ac is in state initialized so we do not
1271            # expect an exception, but the owner might be different
1272            if not invalidate_accesscode(
1273                pin,comment,self.context.getStudent().student_id):
[7723]1274                self.flash(_('You are not the owner of this access code.'))
[6992]1275                return
[7060]1276        # Create bed ticket
[6992]1277        bedticket = createObject(u'waeup.BedTicket')
1278        bedticket.booking_code = pin
[6994]1279        bedticket.booking_session = acc_details['booking_session']
[6996]1280        bedticket.bed_type = acc_details['bt']
[7006]1281        bedticket.bed = bed
[6996]1282        hall_title = bed.__parent__.hostel_name
1283        coordinates = bed.getBedCoordinates()[1:]
1284        block, room_nr, bed_nr = coordinates
[7723]1285        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
1286            'a':hall_title, 'b':block,
1287            'c':room_nr, 'd':bed_nr,
1288            'e':bed.bed_type})
1289        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
1290        bedticket.bed_coordinates = translate(
1291            bc, 'waeup.sirp',target_language=portal_language)
[6996]1292        key = str(acc_details['booking_session'])
1293        self.context[key] = bedticket
[7723]1294        self.flash(_('Bed ticket created and bed booked: ${a}',
1295            mapping = {'a':bedticket.bed_coordinates}))
[6992]1296        self.redirect(self.url(self.context))
1297        return
1298
[7321]1299class BedTicketDisplayFormPage(SIRPDisplayFormPage):
[6994]1300    """ Page to display bed tickets
1301    """
1302    grok.context(IBedTicket)
1303    grok.name('index')
[7181]1304    grok.require('waeup.handleAccommodation')
[6994]1305    form_fields = grok.AutoFields(IBedTicket)
1306    form_fields[
1307        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1308    pnav = 4
1309
1310    @property
1311    def label(self):
[7723]1312        return _('Bed Ticket for Session ${a}',
1313            mapping = {'a':self.context.getSessionString()})
[6994]1314
[7459]1315class ExportPDFBedTicketSlipPage(UtilityView, grok.View):
[7027]1316    """Deliver a PDF slip of the context.
1317    """
1318    grok.context(IBedTicket)
1319    grok.name('bed_allocation.pdf')
[7181]1320    grok.require('waeup.handleAccommodation')
[7027]1321    form_fields = grok.AutoFields(IBedTicket)
1322    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1323    prefix = 'form'
1324
1325    @property
[7723]1326    def title(self):
1327        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
1328        return translate(_('Bed Allocation Data'), 'waeup.sirp',
1329            target_language=portal_language)
1330
1331    @property
[7027]1332    def label(self):
[7723]1333        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
1334        return translate(_('Bed Allocation: '),
1335            'waeup.sirp', target_language=portal_language) \
1336            + ' %s' % self.context.bed_coordinates
[7027]1337
1338    def render(self):
1339        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1340            self.request)
[7150]1341        students_utils = getUtility(IStudentsUtils)
[7186]1342        return students_utils.renderPDF(
[7318]1343            self, 'bed_allocation.pdf',
[7310]1344            self.context.getStudent(), studentview)
[7027]1345
[7459]1346class BedTicketRelocationPage(UtilityView, grok.View):
[7015]1347    """ Callback view
1348    """
1349    grok.context(IBedTicket)
1350    grok.name('relocate')
1351    grok.require('waeup.manageHostels')
1352
[7059]1353    # Relocate student if student parameters have changed or the bed_type
1354    # of the bed has changed
[7015]1355    def update(self):
1356        student = self.context.getStudent()
[7150]1357        students_utils = getUtility(IStudentsUtils)
[7186]1358        acc_details  = students_utils.getAccommodationDetails(student)
[7068]1359        if self.context.bed != None and \
1360              'reserved' in self.context.bed.bed_type:
[7723]1361            self.flash(_("Students in reserved beds can't be relocated."))
[7068]1362            self.redirect(self.url(self.context))
1363            return
[7059]1364        if acc_details['bt'] == self.context.bed_type and \
[7068]1365                self.context.bed != None and \
[7059]1366                self.context.bed.bed_type == self.context.bed_type:
[7723]1367            self.flash(_("Student can't be relocated."))
[7068]1368            self.redirect(self.url(self.context))
[7015]1369            return
[7068]1370        # Search a bed
[7015]1371        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1372        entries = cat.searchResults(
[7068]1373            owner=(student.student_id,student.student_id))
1374        if len(entries) and self.context.bed == None:
1375            # If booking has been cancelled but other bed space has been
1376            # manually allocated after cancellation use this bed
1377            new_bed = [entry for entry in entries][0]
1378        else:
1379            # Search for other available beds
1380            entries = cat.searchResults(
1381                bed_type=(acc_details['bt'],acc_details['bt']))
1382            available_beds = [
1383                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1384            if available_beds:
[7150]1385                students_utils = getUtility(IStudentsUtils)
[7186]1386                new_bed = students_utils.selectBed(available_beds)
[7068]1387                new_bed.bookBed(student.student_id)
1388            else:
[7723]1389                self.flash(_('There is no free bed in your category ${a}.',
1390                    mapping = {'a':acc_details['bt']}))
[7068]1391                self.redirect(self.url(self.context))
1392                return
[7642]1393        # Release old bed if exists
[7068]1394        if self.context.bed != None:
1395            self.context.bed.owner = NOT_OCCUPIED
1396            notify(grok.ObjectModifiedEvent(self.context.bed))
[7015]1397        # Alocate new bed
1398        self.context.bed_type = acc_details['bt']
[7068]1399        self.context.bed = new_bed
1400        hall_title = new_bed.__parent__.hostel_name
1401        coordinates = new_bed.getBedCoordinates()[1:]
[7015]1402        block, room_nr, bed_nr = coordinates
[7723]1403        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
1404            'a':hall_title, 'b':block,
1405            'c':room_nr, 'd':bed_nr,
1406            'e':new_bed.bed_type})
1407        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
1408        self.context.bed_coordinates = translate(
1409            bc, 'waeup.sirp',target_language=portal_language)
1410        self.flash(_('Student relocated: ${a}',
1411            mapping = {'a':self.context.bed_coordinates}))
[7015]1412        self.redirect(self.url(self.context))
1413        return
1414
1415    def render(self):
1416        return
1417
[7321]1418class StudentHistoryPage(SIRPPage):
[6637]1419    """ Page to display student clearance data
1420    """
1421    grok.context(IStudent)
1422    grok.name('history')
[6660]1423    grok.require('waeup.viewStudent')
[6637]1424    grok.template('studenthistory')
[6642]1425    pnav = 4
[6637]1426
1427    @property
1428    def label(self):
[7723]1429        return _('${a}: History', mapping = {'a':self.context.display_fullname})
[6694]1430
1431# Pages for students only
1432
[7321]1433class StudentBaseEditFormPage(SIRPEditFormPage):
[7133]1434    """ View to edit student base data
1435    """
1436    grok.context(IStudent)
1437    grok.name('edit_base')
1438    grok.require('waeup.handleStudent')
1439    form_fields = grok.AutoFields(IStudentBase).select(
1440        'email', 'phone')
[7723]1441    label = _('Edit base data')
[7133]1442    pnav = 4
1443
[7723]1444    @action(_('Save'), style='primary')
[7133]1445    def save(self, **data):
1446        msave(self, **data)
1447        return
1448
[7321]1449class StudentChangePasswordPage(SIRPEditFormPage):
[7144]1450    """ View to manage student base data
[6756]1451    """
1452    grok.context(IStudent)
[7114]1453    grok.name('change_password')
[6694]1454    grok.require('waeup.handleStudent')
[7144]1455    grok.template('change_password')
[7723]1456    label = _('Change password')
[6694]1457    pnav = 4
1458
[7723]1459    @action(_('Save'), style='primary')
[7144]1460    def save(self, **data):
1461        form = self.request.form
1462        password = form.get('change_password', None)
1463        password_ctl = form.get('change_password_repeat', None)
1464        if password:
[7147]1465            validator = getUtility(IPasswordValidator)
1466            errors = validator.validate_password(password, password_ctl)
1467            if not errors:
1468                IUserAccount(self.context).setPassword(password)
1469                write_log_message(self, 'saved: password')
[7723]1470                self.flash(_('Password changed.'))
[6756]1471            else:
[7147]1472                self.flash( ' '.join(errors))
[6756]1473        return
1474
[7321]1475class StudentFilesUploadPage(SIRPPage):
[7114]1476    """ View to upload files by student
1477    """
1478    grok.context(IStudent)
1479    grok.name('change_portrait')
[7127]1480    grok.require('waeup.uploadStudentFile')
[7114]1481    grok.template('filesuploadpage')
[7723]1482    label = _('Upload portrait')
[7114]1483    pnav = 4
1484
[7133]1485    def update(self):
[7539]1486        if self.context.getStudent().state != ADMITTED:
[7145]1487            emit_lock_message(self)
[7133]1488            return
1489        super(StudentFilesUploadPage, self).update()
1490        return
1491
[7321]1492class StartClearancePage(SIRPPage):
[6770]1493    grok.context(IStudent)
1494    grok.name('start_clearance')
1495    grok.require('waeup.handleStudent')
1496    grok.template('enterpin')
[7723]1497    label = _('Start clearance')
[6770]1498    ac_prefix = 'CLR'
1499    notice = ''
1500    pnav = 4
[7723]1501    buttonname = _('Start clearance now')
[6770]1502
[7133]1503    @property
1504    def all_required_fields_filled(self):
1505        if self.context.email and self.context.phone:
1506            return True
1507        return False
1508
1509    @property
1510    def portrait_uploaded(self):
1511        store = getUtility(IExtFileStore)
1512        if store.getFileByContext(self.context, attr=u'passport.jpg'):
1513            return True
1514        return False
1515
[6770]1516    def update(self, SUBMIT=None):
[7671]1517        if not self.context.state == ADMITTED:
[7745]1518            self.flash(_("Wrong state"))
[6936]1519            self.redirect(self.url(self.context))
1520            return
[7133]1521        if not self.portrait_uploaded:
[7723]1522            self.flash(_("No portrait uploaded."))
[7133]1523            self.redirect(self.url(self.context, 'change_portrait'))
1524            return
1525        if not self.all_required_fields_filled:
[7723]1526            self.flash(_("Not all required fields filled."))
[7133]1527            self.redirect(self.url(self.context, 'edit_base'))
1528            return
[6770]1529        self.ac_series = self.request.form.get('ac_series', None)
1530        self.ac_number = self.request.form.get('ac_number', None)
1531
1532        if SUBMIT is None:
1533            return
1534        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1535        code = get_access_code(pin)
1536        if not code:
[7723]1537            self.flash(_('Activation code is invalid.'))
[6770]1538            return
1539        # Mark pin as used (this also fires a pin related transition)
1540        # and fire transition start_clearance
1541        if code.state == USED:
[7723]1542            self.flash(_('Activation code has already been used.'))
[6770]1543            return
1544        else:
[7723]1545            comment = _(u"invalidated")
[6770]1546            # Here we know that the ac is in state initialized so we do not
[6927]1547            # expect an exception, but the owner might be different
1548            if not invalidate_accesscode(pin,comment,self.context.student_id):
[7723]1549                self.flash(_('You are not the owner of this access code.'))
[6927]1550                return
[6770]1551            self.context.clr_code = pin
1552        IWorkflowInfo(self.context).fireTransition('start_clearance')
[7723]1553        self.flash(_('Clearance process has been started.'))
[6770]1554        self.redirect(self.url(self.context,'cedit'))
1555        return
1556
[6695]1557class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1558    """ View to edit student clearance data by student
1559    """
1560    grok.context(IStudent)
1561    grok.name('cedit')
1562    grok.require('waeup.handleStudent')
[6756]1563    form_fields = grok.AutoFields(
[7538]1564        IStudentClearance).omit('clearance_locked')
[7723]1565    label = _('Edit clearance data')
[6695]1566    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6718]1567
1568    def update(self):
1569        if self.context.clearance_locked:
[7145]1570            emit_lock_message(self)
[6718]1571            return
1572        return super(StudentClearanceEditFormPage, self).update()
[6719]1573
[7723]1574    @action(_('Save'), style='primary')
[6722]1575    def save(self, **data):
1576        self.applyData(self.context, **data)
[7723]1577        self.flash(_('Clearance form has been saved.'))
[6722]1578        return
1579
[7253]1580    def dataNotComplete(self):
[7642]1581        """To be implemented in the customization package.
1582        """
[7253]1583        return False
1584
[7723]1585    @action(_('Save and request clearance'), style='primary')
[7186]1586    def requestClearance(self, **data):
[6722]1587        self.applyData(self.context, **data)
[7253]1588        if self.dataNotComplete():
1589            self.flash(self.dataNotComplete())
1590            return
[7723]1591        self.flash(_('Clearance form has been saved.'))
[6769]1592        self.redirect(self.url(self.context,'request_clearance'))
[6722]1593        return
1594
[7321]1595class RequestClearancePage(SIRPPage):
[6769]1596    grok.context(IStudent)
1597    grok.name('request_clearance')
1598    grok.require('waeup.handleStudent')
1599    grok.template('enterpin')
[7723]1600    label = _('Request clearance')
1601    notice = _('Enter the CLR access code used for starting clearance.')
[6769]1602    ac_prefix = 'CLR'
1603    pnav = 4
[7723]1604    buttonname = _('Request clearance now')
[6769]1605
1606    def update(self, SUBMIT=None):
1607        self.ac_series = self.request.form.get('ac_series', None)
1608        self.ac_number = self.request.form.get('ac_number', None)
1609        if SUBMIT is None:
1610            return
1611        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1612        if self.context.clr_code != pin:
[7723]1613            self.flash(_("This isn't your CLR access code."))
[6769]1614            return
1615        state = IWorkflowState(self.context).getState()
1616        # This shouldn't happen, but the application officer
1617        # might have forgotten to lock the form after changing the state
1618        if state != CLEARANCE:
[7723]1619            self.flash(_('This form cannot be submitted. Wrong state!'))
[6769]1620            return
1621        IWorkflowInfo(self.context).fireTransition('request_clearance')
[7723]1622        self.flash(_('Clearance has been requested.'))
[6769]1623        self.redirect(self.url(self.context))
[6789]1624        return
[6806]1625
[7321]1626class StartCourseRegistrationPage(SIRPPage):
[6944]1627    grok.context(IStudentStudyCourse)
1628    grok.name('start_course_registration')
1629    grok.require('waeup.handleStudent')
1630    grok.template('enterpin')
[7723]1631    label = _('Start course registration')
[6944]1632    ac_prefix = 'SFE'
1633    notice = ''
1634    pnav = 4
[7723]1635    buttonname = _('Start course registration now')
[6944]1636
1637    def update(self, SUBMIT=None):
1638        if not self.context.getStudent().state in (CLEARED,RETURNING):
[7745]1639            self.flash(_("Wrong state"))
[6944]1640            self.redirect(self.url(self.context))
1641            return
1642        self.ac_series = self.request.form.get('ac_series', None)
1643        self.ac_number = self.request.form.get('ac_number', None)
1644
1645        if SUBMIT is None:
1646            return
1647        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1648        code = get_access_code(pin)
1649        if not code:
[7723]1650            self.flash(_('Activation code is invalid.'))
[6944]1651            return
1652        # Mark pin as used (this also fires a pin related transition)
1653        # and fire transition start_clearance
1654        if code.state == USED:
[7723]1655            self.flash(_('Activation code has already been used.'))
[6944]1656            return
1657        else:
[7723]1658            comment = _(u"invalidated")
[6944]1659            # Here we know that the ac is in state initialized so we do not
1660            # expect an exception, but the owner might be different
1661            if not invalidate_accesscode(
1662                pin,comment,self.context.getStudent().student_id):
[7723]1663                self.flash(_('You are not the owner of this access code.'))
[6944]1664                return
1665        if self.context.getStudent().state == CLEARED:
1666            IWorkflowInfo(self.context.getStudent()).fireTransition(
1667                'pay_first_school_fee')
1668        elif self.context.getStudent().state == RETURNING:
1669            IWorkflowInfo(self.context.getStudent()).fireTransition(
1670                'pay_school_fee')
[7723]1671        self.flash(_('Course registration has been started.'))
[6944]1672        self.redirect(self.url(self.context))
1673        return
1674
[7321]1675class AddStudyLevelFormPage(SIRPEditFormPage):
[6806]1676    """ Page for students to add current study levels
1677    """
1678    grok.context(IStudentStudyCourse)
1679    grok.name('add')
1680    grok.require('waeup.handleStudent')
1681    grok.template('studyleveladdpage')
1682    form_fields = grok.AutoFields(IStudentStudyCourse)
1683    pnav = 4
1684
1685    @property
1686    def label(self):
1687        studylevelsource = StudyLevelSource().factory
1688        code = self.context.current_level
1689        title = studylevelsource.getTitle(self.context, code)
[7723]1690        return _('Add current level ${a}', mapping = {'a':title})
[6806]1691
1692    def update(self):
[7539]1693        if self.context.getStudent().state != PAID:
[7145]1694            emit_lock_message(self)
[6806]1695            return
1696        super(AddStudyLevelFormPage, self).update()
1697        return
1698
[7723]1699    @action(_('Create course list now'), style='primary')
[6806]1700    def addStudyLevel(self, **data):
1701        studylevel = StudentStudyLevel()
1702        studylevel.level = self.context.current_level
1703        studylevel.level_session = self.context.current_session
1704        try:
1705            self.context.addStudentStudyLevel(
1706                self.context.certificate,studylevel)
1707        except KeyError:
[7723]1708            self.flash(_('This level exists.'))
[6806]1709        self.redirect(self.url(self.context))
1710        return
[6808]1711
[7321]1712class StudyLevelEditFormPage(SIRPEditFormPage):
[6808]1713    """ Page to edit the student study level data by students
1714    """
1715    grok.context(IStudentStudyLevel)
1716    grok.name('edit')
1717    grok.require('waeup.handleStudent')
1718    grok.template('studyleveleditpage')
1719    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1720        'level_session', 'level_verdict')
1721    pnav = 4
1722
1723    def update(self):
[7539]1724        if self.context.getStudent().state != PAID:
1725            emit_lock_message(self)
1726            return
[6808]1727        super(StudyLevelEditFormPage, self).update()
1728        datatable.need()
[7329]1729        warning.need()
[6808]1730        return
1731
1732    @property
1733    def label(self):
[7723]1734        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
1735        lang = self.request.cookies.get('sirp.language', portal_language)
1736        level_title = translate(self.context.level_title, 'waeup.sirp',
1737            target_language=lang)
1738        return _('Add and remove course tickets of study level ${a}',
1739            mapping = {'a':level_title})
[6808]1740
1741    @property
1742    def total_credits(self):
1743        total_credits = 0
1744        for key, val in self.context.items():
1745            total_credits += val.credits
1746        return total_credits
1747
[7723]1748    @action(_('Add course ticket'))
[6808]1749    def addCourseTicket(self, **data):
1750        self.redirect(self.url(self.context, 'ctadd'))
1751
[7723]1752    @jsaction(_('Remove selected tickets'))
[6808]1753    def delCourseTicket(self, **data):
1754        form = self.request.form
1755        if form.has_key('val_id'):
1756            child_id = form['val_id']
1757        else:
[7723]1758            self.flash(_('No ticket selected.'))
[6808]1759            self.redirect(self.url(self.context, '@@edit'))
1760            return
1761        if not isinstance(child_id, list):
1762            child_id = [child_id]
1763        deleted = []
1764        for id in child_id:
[6940]1765            # Students are not allowed to remove core tickets
[7665]1766            if not self.context[id].mandatory:
[7723]1767                del self.context[id]
1768                deleted.append(id)
[6808]1769        if len(deleted):
[7723]1770            self.flash(_('Successfully removed: ${a}',
1771                mapping = {'a':', '.join(deleted)}))
[6808]1772        self.redirect(self.url(self.context, u'@@edit'))
1773        return
1774
[7723]1775    @action(_('Register course list'), style='primary')
[7186]1776    def RegisterCourses(self, **data):
[7642]1777        IWorkflowInfo(self.context.getStudent()).fireTransition(
1778            'register_courses')
[7723]1779        self.flash(_('Course list has been registered.'))
[6810]1780        self.redirect(self.url(self.context))
1781        return
1782
[6808]1783class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1784    """Add a course ticket by student.
1785    """
1786    grok.name('ctadd')
1787    grok.require('waeup.handleStudent')
1788    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
[7665]1789        'grade', 'score', 'mandatory', 'automatic', 'carry_over')
[6808]1790
[7539]1791    def update(self):
1792        if self.context.getStudent().state != PAID:
1793            emit_lock_message(self)
1794            return
1795        super(CourseTicketAddFormPage2, self).update()
1796        return
1797
[7723]1798    @action(_('Add course ticket'))
[6808]1799    def addCourseTicket(self, **data):
[7642]1800        # Safety belt
[7539]1801        if self.context.getStudent().state != PAID:
1802            return
[6808]1803        ticket = CourseTicket()
1804        course = data['course']
[7642]1805        for name in ['code', 'title', 'credits', 'passmark', 'semester']:
1806            setattr(ticket, name, getattr(course, name))
[6808]1807        ticket.automatic = False
1808        try:
1809            self.context.addCourseTicket(ticket)
1810        except KeyError:
[7723]1811            self.flash(_('The ticket exists.'))
[6808]1812            return
[7723]1813        self.flash(_('Successfully added ${a}.',
1814            mapping = {'a':ticket.code}))
[6808]1815        self.redirect(self.url(self.context, u'@@edit'))
1816        return
[7369]1817
1818class ChangePasswordRequestPage(SIRPForm):
1819    """Captcha'd page for students to request a password change.
1820    """
1821    grok.context(IUniversity)
1822    grok.name('changepw')
1823    grok.require('waeup.Anonymous')
1824    grok.template('changepw')
[7723]1825    label = _('Change my password')
[7369]1826    form_fields = grok.AutoFields(IStudentChangePassword)
1827
1828    def update(self):
1829        # Handle captcha
1830        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1831        self.captcha_result = self.captcha.verify(self.request)
1832        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1833        return
1834
[7723]1835    @action(_('Get new login credentials'), style='primary')
[7369]1836    def request(self, **data):
1837        if not self.captcha_result.is_valid:
1838            # Captcha will display error messages automatically.
1839            # No need to flash something.
1840            return
1841        # Search student
1842        cat = queryUtility(ICatalog, name='students_catalog')
1843        reg_number = data['reg_number']
1844        email = data['email']
1845        results = cat.searchResults(
1846            reg_number=(reg_number, reg_number),
1847            email=(email,email))
1848        if len(results) == 0:
[7723]1849            self.flash(_('No student record found.'))
[7369]1850            return
1851        student = list(results)[0]
1852        # Change password
1853        sirp_utils = getUtility(ISIRPUtils)
1854        pwd = sirp_utils.genPassword()
1855        IUserAccount(student).setPassword(pwd)
1856        # Send email with new redentials
[7734]1857        msg = _('You have successfully changed your password for the')
[7369]1858        login_url = self.url(grok.getSite(), 'login')
[7407]1859        success = sirp_utils.sendCredentials(
1860            IUserAccount(student),pwd,login_url,msg)
[7369]1861        if success:
[7723]1862            self.flash(_('An email with your user name and password ' +
1863                'has been sent to ${a}.', mapping = {'a':email}))
[7369]1864        else:
[7723]1865            self.flash(_('An smtp server error occurred.'))
[7638]1866        return
1867
[7660]1868class SetPasswordPage(SIRPPage):
1869    grok.context(ISIRPObject)
1870    grok.name('setpassword')
1871    grok.require('waeup.Anonymous')
1872    grok.template('setpassword')
[7723]1873    label = _('Set password for first-time login')
[7660]1874    ac_prefix = 'PWD'
1875    pnav = 0
[7738]1876    set_button = _('Set')
[7660]1877
1878    def update(self, SUBMIT=None):
1879        self.reg_number = self.request.form.get('reg_number', None)
1880        self.ac_series = self.request.form.get('ac_series', None)
1881        self.ac_number = self.request.form.get('ac_number', None)
1882
1883        if SUBMIT is None:
1884            return
1885        hitlist = search(query=self.reg_number,
1886            searchtype='reg_number', view=self)
1887        if not hitlist:
[7723]1888            self.flash(_('No student found.'))
[7660]1889            return
1890        if len(hitlist) != 1:   # Cannot happen but anyway
[7723]1891            self.flash(_('More than one student found.'))
[7660]1892            return
1893        student = hitlist[0].context
1894        self.student_id = student.student_id
1895        student_pw = student.password
1896        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1897        code = get_access_code(pin)
1898        if not code:
[7723]1899            self.flash(_('Access code is invalid.'))
[7660]1900            return
1901        if student_pw and pin == student.adm_code:
[7723]1902            self.flash(_(
1903                'Password has already been set. Your Student Id is ${a}',
1904                mapping = {'a':self.student_id}))
[7660]1905            return
1906        elif student_pw:
1907            self.flash(
[7723]1908                _('Password has already been set. You are using the ' +
1909                'wrong Access Code.'))
[7660]1910            return
1911        # Mark pin as used (this also fires a pin related transition)
1912        # and set student password
1913        if code.state == USED:
[7723]1914            self.flash(_('Access code has already been used.'))
[7660]1915            return
1916        else:
[7723]1917            comment = _(u"invalidated")
[7660]1918            # Here we know that the ac is in state initialized so we do not
1919            # expect an exception
1920            invalidate_accesscode(pin,comment)
1921            IUserAccount(student).setPassword(self.ac_number)
1922            student.adm_code = pin
[7723]1923        self.flash(_('Password has been set. Your Student Id is ${a}',
1924            mapping = {'a':self.student_id}))
[7660]1925        return
Note: See TracBrowser for help on using the repository browser.