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

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

Start internationalization of students package (work in progress).

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