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

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

Add action buttons for clearance officers.

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