source: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py @ 7857

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

We don't need methods to fetch dictionaries.

Update interfaces.

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