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

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

Remove breakpoint and prepare formatted_text() for unit tests.

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