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

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

Managers do not 'pay' fees for applicants and students, they approve payments made.

Add respective transitions.

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