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

Last change on this file since 7136 was 7136, checked in by Henrik Bettermann, 14 years ago

Rename permissions and add clearStudent permission.

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