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

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

Add missing handler.

  • Property svn:keywords set to Id
File size: 56.5 KB
Line 
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"""
18import sys
19import grok
20from time import time
21from datetime import date, datetime
22from zope.catalog.interfaces import ICatalog
23from zope.component import queryUtility
24from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
25from zope.component import createObject
26from waeup.sirp.accesscodes import (
27    invalidate_accesscode, get_access_code, create_accesscode)
28from waeup.sirp.accesscodes.workflow import USED
29from waeup.sirp.browser import (
30    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
31from waeup.sirp.browser.breadcrumbs import Breadcrumb
32from waeup.sirp.browser.resources import datepicker, datatable, tabs
33from waeup.sirp.browser.viewlets import (
34    ManageActionButton, PrimaryNavTab, AddActionButton)
35from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
36from waeup.sirp.widgets.datewidget import (
37    FriendlyDateWidget, FriendlyDateDisplayWidget,
38    FriendlyDatetimeDisplayWidget)
39from waeup.sirp.university.vocabularies import study_modes
40from waeup.sirp.students.interfaces import (
41    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
42    IStudentPersonal, IStudentBase, IStudentStudyCourse,
43    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
44    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
45    IStudentOnlinePayment, IBedTicket
46    )
47from waeup.sirp.students.catalog import search
48from waeup.sirp.students.workflow import (
49    CLEARANCE, RETURNING, CLEARED, REGISTERED, VALIDATED)
50from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
51from waeup.sirp.students.vocabularies import StudyLevelSource
52from waeup.sirp.students.utils import (
53    getPaymentDetails, getAccommodationDetails, selectBed)
54from waeup.sirp.browser.resources import toggleall
55from waeup.sirp.authentication import get_principal_role_manager
56from waeup.sirp.hostels.hostel import NOT_OCCUPIED
57
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
63# Save function used for save methods in manager pages
64def msave(view, **data):
65    form = view.request.form
66    changed_fields = view.applyData(view.context, **data)
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)
71    view.context._p_changed = True
72    view.flash('Form has been saved.')
73    if fields_string:
74        write_log_message(view, 'saved: % s' % fields_string)
75    return
76
77class StudentsTab(PrimaryNavTab):
78    """Students tab in primary navigation.
79    """
80
81    grok.context(IWAeUPObject)
82    grok.order(4)
83    grok.require('waeup.viewStudent')
84    grok.template('primarynavtab')
85
86    pnav = 4
87    tab_title = u'Students'
88
89    @property
90    def link_target(self):
91        return self.view.application_url('students')
92
93class StudentsBreadcrumb(Breadcrumb):
94    """A breadcrumb for the students container.
95    """
96    grok.context(IStudentsContainer)
97    title = u'Students'
98
99class StudentBreadcrumb(Breadcrumb):
100    """A breadcrumb for the student container.
101    """
102    grok.context(IStudent)
103
104    def title(self):
105        return self.context.fullname
106
107class SudyCourseBreadcrumb(Breadcrumb):
108    """A breadcrumb for the student study course.
109    """
110    grok.context(IStudentStudyCourse)
111    title = u'Study Course'
112
113class PaymentsBreadcrumb(Breadcrumb):
114    """A breadcrumb for the student payments folder.
115    """
116    grok.context(IStudentPaymentsContainer)
117    title = u'Payments'
118
119class OnlinePaymentBreadcrumb(Breadcrumb):
120    """A breadcrumb for course lists.
121    """
122    grok.context(IStudentOnlinePayment)
123
124    @property
125    def title(self):
126        return self.context.p_id
127
128class AccommodationBreadcrumb(Breadcrumb):
129    """A breadcrumb for the student accommodation folder.
130    """
131    grok.context(IStudentAccommodation)
132    title = u'Accommodation'
133
134class BedTicketBreadcrumb(Breadcrumb):
135    """A breadcrumb for bed tickets.
136    """
137    grok.context(IBedTicket)
138    @property
139
140    def title(self):
141        return 'Bed Ticket %s' % self.context.getSessionString()
142
143class StudyLevelBreadcrumb(Breadcrumb):
144    """A breadcrumb for course lists.
145    """
146    grok.context(IStudentStudyLevel)
147
148    @property
149    def title(self):
150        return self.context.level_title
151
152class StudentsContainerPage(WAeUPPage):
153    """The standard view for student containers.
154    """
155    grok.context(IStudentsContainer)
156    grok.name('index')
157    grok.require('waeup.viewStudent')
158    grok.template('containerpage')
159    label = 'Student Section'
160    title = 'Students'
161    pnav = 4
162
163    def update(self, *args, **kw):
164        datatable.need()
165        form = self.request.form
166        self.hitlist = []
167        if 'searchterm' in form and form['searchterm']:
168            self.searchterm = form['searchterm']
169            self.searchtype = form['searchtype']
170        elif 'old_searchterm' in form:
171            self.searchterm = form['old_searchterm']
172            self.searchtype = form['old_searchtype']
173        else:
174            if 'search' in form:
175                self.flash('Empty search string.')
176            return
177        self.hitlist = search(query=self.searchterm,
178            searchtype=self.searchtype, view=self)
179        if not self.hitlist:
180            self.flash('No student found.')
181        return
182
183class SetPasswordPage(WAeUPPage):
184    grok.context(IWAeUPObject)
185    grok.name('setpassword')
186    grok.require('waeup.Public')
187    grok.template('setpassword')
188    title = ''
189    label = 'Set password for first-time login'
190    ac_prefix = 'PWD'
191    pnav = 0
192
193    def update(self, SUBMIT=None):
194        self.reg_number = self.request.form.get('reg_number', None)
195        self.ac_series = self.request.form.get('ac_series', None)
196        self.ac_number = self.request.form.get('ac_number', None)
197
198        if SUBMIT is None:
199            return
200        hitlist = search(query=self.reg_number,
201            searchtype='reg_number', view=self)
202        if not hitlist:
203            self.flash('No student found.')
204            return
205        if len(hitlist) != 1:   # Cannot happen but anyway
206            self.flash('More than one student found.')
207            return
208        student = hitlist[0].context
209        self.student_id = student.student_id
210        student_pw = student.password
211        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
212        code = get_access_code(pin)
213        if not code:
214            self.flash('Access code is invalid.')
215            return
216        if student_pw and pin == student.adm_code:
217            self.flash('Password has already been set. Your Student Id is %s'
218                % self.student_id)
219            return
220        elif student_pw:
221            self.flash('Password has already been set. You are using the wrong Access Code.')
222            return
223        # Mark pin as used (this also fires a pin related transition)
224        # and set student password
225        if code.state == USED:
226            self.flash('Access code has already been used.')
227            return
228        else:
229            comment = u"AC invalidated for %s" % self.student_id
230            # Here we know that the ac is in state initialized so we do not
231            # expect an exception
232            #import pdb; pdb.set_trace()
233            invalidate_accesscode(pin,comment)
234            IUserAccount(student).setPassword(self.ac_number)
235            student.adm_code = pin
236        self.flash('Password has been set. Your Student Id is %s'
237            % self.student_id)
238        return
239
240class StudentsContainerManageActionButton(ManageActionButton):
241    grok.order(1)
242    grok.context(IStudentsContainer)
243    grok.view(StudentsContainerPage)
244    grok.require('waeup.manageStudents')
245    text = 'Manage student section'
246
247
248class StudentsContainerManagePage(WAeUPPage):
249    """The manage page for student containers.
250    """
251    grok.context(IStudentsContainer)
252    grok.name('manage')
253    grok.require('waeup.manageStudents')
254    grok.template('containermanagepage')
255    pnav = 4
256    title = 'Manage student section'
257
258    @property
259    def label(self):
260        return self.title
261
262    def update(self, *args, **kw):
263        datatable.need()
264        toggleall.need()
265        form = self.request.form
266        self.hitlist = []
267        if 'searchterm' in form and form['searchterm']:
268            self.searchterm = form['searchterm']
269            self.searchtype = form['searchtype']
270        elif 'old_searchterm' in form:
271            self.searchterm = form['old_searchterm']
272            self.searchtype = form['old_searchtype']
273        else:
274            if 'search' in form:
275                self.flash('Empty search string.')
276            return
277        if not 'entries' in form:
278            self.hitlist = search(query=self.searchterm,
279                searchtype=self.searchtype, view=self)
280            if not self.hitlist:
281                self.flash('No student found.')
282            return
283        entries = form['entries']
284        if isinstance(entries, basestring):
285            entries = [entries]
286        deleted = []
287        for entry in entries:
288            if 'remove' in form:
289                del self.context[entry]
290                deleted.append(entry)
291        self.hitlist = search(query=self.searchterm,
292            searchtype=self.searchtype, view=self)
293        if len(deleted):
294            self.flash('Successfully removed: %s' % ', '.join(deleted))
295        return
296
297class StudentsContainerAddActionButton(AddActionButton):
298    grok.order(1)
299    grok.context(IStudentsContainer)
300    grok.view(StudentsContainerManagePage)
301    grok.require('waeup.manageStudents')
302    text = 'Add student'
303    target = 'addstudent'
304
305class StudentAddFormPage(WAeUPAddFormPage):
306    """Add-form to add a student.
307    """
308    grok.context(IStudentsContainer)
309    grok.require('waeup.manageStudents')
310    grok.name('addstudent')
311    grok.template('studentaddpage')
312    form_fields = grok.AutoFields(IStudent)
313    title = 'Students'
314    label = 'Add student'
315    pnav = 4
316
317    @grok.action('Create student record')
318    def addStudent(self, **data):
319        student = createObject(u'waeup.Student')
320        self.applyData(student, **data)
321        self.context.addStudent(student)
322        self.flash('Student record created.')
323        self.redirect(self.url(self.context[student.student_id], 'index'))
324        return
325
326class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
327    """ Page to display student base data
328    """
329    grok.context(IStudent)
330    grok.name('index')
331    grok.require('waeup.viewStudent')
332    grok.template('basepage')
333    form_fields = grok.AutoFields(IStudentBase).omit('password')
334    pnav = 4
335    title = 'Base Data'
336
337    @property
338    def label(self):
339        return '%s: Base Data' % self.context.fullname
340
341    @property
342    def hasPassword(self):
343        if self.context.password:
344            return 'set'
345        return 'unset'
346
347class StudentBaseManageActionButton(ManageActionButton):
348    grok.order(1)
349    grok.context(IStudent)
350    grok.view(StudentBaseDisplayFormPage)
351    grok.require('waeup.manageStudents')
352    text = 'Manage'
353    target = 'edit_base'
354
355class StudentBaseManageFormPage(WAeUPEditFormPage):
356    """ View to edit student base data
357    """
358    grok.context(IStudent)
359    grok.name('edit_base')
360    grok.require('waeup.manageStudents')
361    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
362    grok.template('basemanagepage')
363    label = 'Manage base data'
364    title = 'Base Data'
365    pnav = 4
366
367    def update(self):
368        datepicker.need() # Enable jQuery datepicker in date fields.
369        super(StudentBaseManageFormPage, self).update()
370        self.wf_info = IWorkflowInfo(self.context)
371        return
372
373    def getTransitions(self):
374        """Return a list of dicts of allowed transition ids and titles.
375
376        Each list entry provides keys ``name`` and ``title`` for
377        internal name and (human readable) title of a single
378        transition.
379        """
380        allowed_transitions = self.wf_info.getManualTransitions()
381        return [dict(name='', title='No transition')] +[
382            dict(name=x, title=y) for x, y in allowed_transitions]
383
384    @grok.action('Save')
385    def save(self, **data):
386        form = self.request.form
387        password = form.get('password', None)
388        password_ctl = form.get('control_password', None)
389        if password:
390            if (password != password_ctl):
391                self.flash('Passwords do not match.')
392            else:
393                # XXX: This is too early. PW should only be saved if there
394                #      are no (other) errors left in form.
395                IUserAccount(self.context).setPassword(password)
396                write_log_message(self, 'password changed')
397
398        #self.reg_number = form.get('form.reg_number', None)
399        #if self.reg_number:
400        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
401        #    if hitlist and hitlist[0].student_id != self.context.student_id:
402        #        self.flash('Registration number exists.')
403        #        return
404        #self.matric_number = form.get('form.matric_number', None)
405        #if self.matric_number:
406        #    hitlist = search(query=self.matric_number,
407        #        searchtype='matric_number', view=self)
408        #    if hitlist and hitlist[0].student_id != self.context.student_id:
409        #        self.flash('Matriculation number exists.')
410        #        return
411
412        # Turn list of lists into single list
413        changed_fields = self.applyData(self.context, **data)
414        if changed_fields:
415            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
416        fields_string = ' + '.join(changed_fields)
417        self.context._p_changed = True
418        if form.has_key('transition') and form['transition']:
419            transition_id = form['transition']
420            self.wf_info.fireTransition(transition_id)
421        self.flash('Form has been saved.')
422        if fields_string:
423            write_log_message(self, 'saved: % s' % fields_string)
424        return
425
426class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
427    """ Page to display student clearance data
428    """
429    grok.context(IStudent)
430    grok.name('view_clearance')
431    grok.require('waeup.viewStudent')
432    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
433    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
434    title = 'Clearance Data'
435    pnav = 4
436
437    @property
438    def label(self):
439        return '%s: Clearance Data' % self.context.fullname
440
441class StudentClearanceManageActionButton(ManageActionButton):
442    grok.order(1)
443    grok.context(IStudent)
444    grok.view(StudentClearanceDisplayFormPage)
445    grok.require('waeup.manageStudents')
446    text = 'Manage'
447    target = 'edit_clearance'
448
449class StudentClearanceManageFormPage(WAeUPEditFormPage):
450    """ Page to edit student clearance data
451    """
452    grok.context(IStudent)
453    grok.name('edit_clearance')
454    grok.require('waeup.manageStudents')
455    form_fields = grok.AutoFields(IStudentClearance)
456    label = 'Manage clearance data'
457    title = 'Clearance Data'
458    pnav = 4
459
460    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
461
462    def update(self):
463        datepicker.need() # Enable jQuery datepicker in date fields.
464        return super(StudentClearanceManageFormPage, self).update()
465
466    @grok.action('Save')
467    def save(self, **data):
468        msave(self, **data)
469        return
470
471class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
472    """ Page to display student personal data
473    """
474    grok.context(IStudent)
475    grok.name('view_personal')
476    grok.require('waeup.viewStudent')
477    form_fields = grok.AutoFields(IStudentPersonal)
478    title = 'Personal Data'
479    pnav = 4
480
481    @property
482    def label(self):
483        return '%s: Personal Data' % self.context.fullname
484
485class StudentPersonalManageActionButton(ManageActionButton):
486    grok.order(1)
487    grok.context(IStudent)
488    grok.view(StudentPersonalDisplayFormPage)
489    grok.require('waeup.manageStudents')
490    text = 'Manage'
491    target = 'edit_personal'
492
493class StudentPersonalManageFormPage(WAeUPEditFormPage):
494    """ Page to edit student clearance data
495    """
496    grok.context(IStudent)
497    grok.name('edit_personal')
498    grok.require('waeup.viewStudent')
499    form_fields = grok.AutoFields(IStudentPersonal)
500    label = 'Manage personal data'
501    title = 'Personal Data'
502    pnav = 4
503
504    @grok.action('Save')
505    def save(self, **data):
506        msave(self, **data)
507        return
508
509class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
510    """ Page to display the student study course data
511    """
512    grok.context(IStudentStudyCourse)
513    grok.name('index')
514    grok.require('waeup.viewStudent')
515    form_fields = grok.AutoFields(IStudentStudyCourse)
516    grok.template('studycoursepage')
517    title = 'Study Course'
518    pnav = 4
519
520    @property
521    def label(self):
522        return '%s: Study Course' % self.context.__parent__.fullname
523
524    @property
525    def current_mode(self):
526        return study_modes.getTermByToken(
527            self.context.certificate.study_mode).title
528
529class StudyCourseManageActionButton(ManageActionButton):
530    grok.order(1)
531    grok.context(IStudentStudyCourse)
532    grok.view(StudyCourseDisplayFormPage)
533    grok.require('waeup.manageStudents')
534    text = 'Manage'
535    target = 'manage'
536
537class StudyCourseManageFormPage(WAeUPEditFormPage):
538    """ Page to edit the student study course data
539    """
540    grok.context(IStudentStudyCourse)
541    grok.name('manage')
542    grok.require('waeup.manageStudents')
543    grok.template('studycoursemanagepage')
544    form_fields = grok.AutoFields(IStudentStudyCourse)
545    title = 'Study Course'
546    label = 'Manage study course'
547    pnav = 4
548    taboneactions = ['Save','Cancel']
549    tabtwoactions = ['Remove selected levels','Cancel']
550    tabthreeactions = ['Add study level']
551
552    def update(self):
553        super(StudyCourseManageFormPage, self).update()
554        tabs.need()
555        datatable.need()
556        return
557
558    @grok.action('Save')
559    def save(self, **data):
560        msave(self, **data)
561        return
562
563    @property
564    def level_dict(self):
565        studylevelsource = StudyLevelSource().factory
566        for code in studylevelsource.getValues(self.context):
567            title = studylevelsource.getTitle(self.context, code)
568            yield(dict(code=code, title=title))
569
570    @grok.action('Add study level')
571    def addStudyLevel(self, **data):
572        level_code = self.request.form.get('addlevel', None)
573        studylevel = StudentStudyLevel()
574        studylevel.level = int(level_code)
575        try:
576            self.context.addStudentStudyLevel(
577                self.context.certificate,studylevel)
578        except KeyError:
579            self.flash('This level exists.')
580        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
581        return
582
583    @grok.action('Remove selected levels')
584    def delStudyLevels(self, **data):
585        form = self.request.form
586        if form.has_key('val_id'):
587            child_id = form['val_id']
588        else:
589            self.flash('No study level selected.')
590            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
591            return
592        if not isinstance(child_id, list):
593            child_id = [child_id]
594        deleted = []
595        for id in child_id:
596            try:
597                del self.context[id]
598                deleted.append(id)
599            except:
600                self.flash('Could not delete %s: %s: %s' % (
601                        id, sys.exc_info()[0], sys.exc_info()[1]))
602        if len(deleted):
603            self.flash('Successfully removed: %s' % ', '.join(deleted))
604        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
605        return
606
607class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
608    """ Page to display student study levels
609    """
610    grok.context(IStudentStudyLevel)
611    grok.name('index')
612    grok.require('waeup.viewStudent')
613    form_fields = grok.AutoFields(IStudentStudyLevel)
614    grok.template('studylevelpage')
615    pnav = 4
616
617    @property
618    def title(self):
619        return 'Study Level %s' % self.context.level_title
620
621    @property
622    def label(self):
623        return '%s: Study Level %s' % (
624            self.context.getStudent().fullname,self.context.level_title)
625
626    @property
627    def total_credits(self):
628        total_credits = 0
629        for key, val in self.context.items():
630            total_credits += val.credits
631        return total_credits
632
633class StudyLevelManageActionButton(ManageActionButton):
634    grok.order(1)
635    grok.context(IStudentStudyLevel)
636    grok.view(StudyLevelDisplayFormPage)
637    grok.require('waeup.manageStudents')
638    text = 'Manage'
639    target = 'manage'
640
641class StudyLevelManageFormPage(WAeUPEditFormPage):
642    """ Page to edit the student study level data
643    """
644    grok.context(IStudentStudyLevel)
645    grok.name('manage')
646    grok.require('waeup.manageStudents')
647    grok.template('studylevelmanagepage')
648    form_fields = grok.AutoFields(IStudentStudyLevel)
649    pnav = 4
650    taboneactions = ['Save','Cancel']
651    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
652
653    def update(self):
654        super(StudyLevelManageFormPage, self).update()
655        tabs.need()
656        datatable.need()
657        return
658
659    @property
660    def title(self):
661        return 'Study Level %s' % self.context.level_title
662
663    @property
664    def label(self):
665        return 'Manage study level %s' % self.context.level_title
666
667    @grok.action('Save')
668    def save(self, **data):
669        msave(self, **data)
670        return
671
672    @grok.action('Add course ticket')
673    def addCourseTicket(self, **data):
674        self.redirect(self.url(self.context, '@@add'))
675
676    @grok.action('Remove selected tickets')
677    def delCourseTicket(self, **data):
678        form = self.request.form
679        if form.has_key('val_id'):
680            child_id = form['val_id']
681        else:
682            self.flash('No ticket selected.')
683            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
684            return
685        if not isinstance(child_id, list):
686            child_id = [child_id]
687        deleted = []
688        for id in child_id:
689            try:
690                del self.context[id]
691                deleted.append(id)
692            except:
693                self.flash('Could not delete %s: %s: %s' % (
694                        id, sys.exc_info()[0], sys.exc_info()[1]))
695        if len(deleted):
696            self.flash('Successfully removed: %s' % ', '.join(deleted))
697        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
698        return
699
700class CourseTicketAddFormPage(WAeUPAddFormPage):
701    """Add a course ticket.
702    """
703    grok.context(IStudentStudyLevel)
704    grok.name('add')
705    grok.require('waeup.manageStudents')
706    label = 'Add course ticket'
707    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
708        'grade', 'score', 'automatic')
709    pnav = 4
710
711    @property
712    def title(self):
713        return 'Study Level %s' % self.context.level_title
714
715    @grok.action('Add course ticket')
716    def addCourseTicket(self, **data):
717        ticket = CourseTicket()
718        course = data['course']
719        ticket.core_or_elective = data['core_or_elective']
720        ticket.automatic = False
721        ticket.code = course.code
722        ticket.title = course.title
723        ticket.faculty = course.__parent__.__parent__.__parent__.title
724        ticket.department = course.__parent__.__parent__.title
725        ticket.credits = course.credits
726        ticket.passmark = course.passmark
727        ticket.semester = course.semester
728        try:
729            self.context.addCourseTicket(ticket)
730        except KeyError:
731            self.flash('The ticket exists.')
732            return
733        self.flash('Successfully added %s.' % ticket.code)
734        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
735        return
736
737    @grok.action('Cancel')
738    def cancel(self, **data):
739        self.redirect(self.url(self.context))
740
741class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
742    """ Page to display course tickets
743    """
744    grok.context(ICourseTicket)
745    grok.name('index')
746    grok.require('waeup.viewStudent')
747    form_fields = grok.AutoFields(ICourseTicket)
748    grok.template('courseticketpage')
749    pnav = 4
750
751    @property
752    def title(self):
753        return 'Course Ticket %s' % self.context.code
754
755    @property
756    def label(self):
757        return '%s: Course Ticket %s' % (
758            self.context.getStudent().fullname,self.context.code)
759
760class CourseTicketManageActionButton(ManageActionButton):
761    grok.order(1)
762    grok.context(ICourseTicket)
763    grok.view(CourseTicketDisplayFormPage)
764    grok.require('waeup.manageStudents')
765    text = 'Manage'
766    target = 'manage'
767
768class CourseTicketManageFormPage(WAeUPEditFormPage):
769    """ Page to manage course tickets
770    """
771    grok.context(ICourseTicket)
772    grok.name('manage')
773    grok.require('waeup.manageStudents')
774    form_fields = grok.AutoFields(ICourseTicket)
775    grok.template('courseticketmanagepage')
776    pnav = 4
777
778    @property
779    def title(self):
780        return 'Course Ticket %s' % self.context.code
781
782    @property
783    def label(self):
784        return 'Manage course ticket %s' % self.context.code
785
786    @grok.action('Save')
787    def save(self, **data):
788        msave(self, **data)
789        return
790
791# We don't need the display form page yet
792#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
793#    """ Page to display the student payments
794#    """
795#    grok.context(IStudentPaymentsContainer)
796#    grok.name('view')
797#    grok.require('waeup.viewStudent')
798#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
799#    grok.template('paymentspage')
800#    title = 'Payments'
801#    pnav = 4
802
803#    def formatDatetime(self,datetimeobj):
804#        if isinstance(datetimeobj, datetime):
805#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
806#        else:
807#            return None
808
809#    @property
810#    def label(self):
811#        return '%s: Payments' % self.context.__parent__.fullname
812
813#    def update(self):
814#        super(PaymentsDisplayFormPage, self).update()
815#        datatable.need()
816#        return
817
818# This manage form page is for both students and students officers.
819class PaymentsManageFormPage(WAeUPEditFormPage):
820    """ Page to manage the student payments
821    """
822    grok.context(IStudentPaymentsContainer)
823    grok.name('index')
824    grok.require('waeup.handleStudent')
825    form_fields = grok.AutoFields(IStudentPaymentsContainer)
826    grok.template('paymentsmanagepage')
827    title = 'Payments'
828    pnav = 4
829
830    def unremovable(self, ticket):
831        prm = get_principal_role_manager()
832        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
833        return ('waeup.Student' in roles and ticket.r_code)
834
835    def formatDatetime(self,datetimeobj):
836        if isinstance(datetimeobj, datetime):
837            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
838        else:
839            return None
840
841    @property
842    def label(self):
843        return '%s: Payments' % self.context.__parent__.fullname
844
845    def update(self):
846        super(PaymentsManageFormPage, self).update()
847        datatable.need()
848        return
849
850    @grok.action('Remove selected tickets')
851    def delPaymentTicket(self, **data):
852        form = self.request.form
853        if form.has_key('val_id'):
854            child_id = form['val_id']
855        else:
856            self.flash('No payment selected.')
857            self.redirect(self.url(self.context))
858            return
859        if not isinstance(child_id, list):
860            child_id = [child_id]
861        deleted = []
862        for id in child_id:
863            # Students are not allowed to remove used payment tickets
864            if not self.unremovable(self.context[id]):
865                try:
866                    del self.context[id]
867                    deleted.append(id)
868                except:
869                    self.flash('Could not delete %s: %s: %s' % (
870                            id, sys.exc_info()[0], sys.exc_info()[1]))
871        if len(deleted):
872            self.flash('Successfully removed: %s' % ', '.join(deleted))
873            write_log_message(self,'removed: % s' % ', '.join(deleted))
874        self.redirect(self.url(self.context))
875        return
876
877    @grok.action('Add online payment ticket')
878    def addPaymentTicket(self, **data):
879        self.redirect(self.url(self.context, '@@addop'))
880
881#class OnlinePaymentManageActionButton(ManageActionButton):
882#    grok.order(1)
883#    grok.context(IStudentPaymentsContainer)
884#    grok.view(PaymentsDisplayFormPage)
885#    grok.require('waeup.manageStudents')
886#    text = 'Manage payments'
887#    target = 'manage'
888
889class OnlinePaymentAddFormPage(WAeUPAddFormPage):
890    """ Page to add an online payment ticket
891    """
892    grok.context(IStudentPaymentsContainer)
893    grok.name('addop')
894    grok.require('waeup.handleStudent')
895    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
896        'p_category')
897    #zzgrok.template('addpaymentpage')
898    label = 'Add online payment'
899    title = 'Payments'
900    pnav = 4
901   
902    # To be sepezified in customization packages
903    def getPaymentDetails(self, category, student):
904        return getPaymentDetails(category, student)
905
906    @grok.action('Create ticket')
907    def createTicket(self, **data):
908        payment = createObject(u'waeup.StudentOnlinePayment')
909        self.applyData(payment, **data)
910        timestamp = "%d" % int(time()*1000)
911        #order_id = "%s%s" % (student_id[1:],timestamp)
912        payment.p_id = "p%s" % timestamp
913        pay_details  = self.getPaymentDetails(
914            data['p_category'],self.context.__parent__)
915        if pay_details['error']:
916            self.flash(pay_details['error'])
917            self.redirect(self.url(self.context))
918            return
919        payment.amount_auth = pay_details['amount']
920        payment.p_item = pay_details['p_item']
921        payment.p_session = pay_details['p_session']
922        payment.surcharge_1 = pay_details['surcharge_1']
923        payment.surcharge_2 = pay_details['surcharge_2']
924        payment.surcharge_3 = pay_details['surcharge_3']
925        self.context[payment.p_id] = payment
926        self.flash('Payment ticket created.')
927        self.redirect(self.url(self.context))
928        return
929
930class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
931    """ Page to view an online payment ticket
932    """
933    grok.context(IStudentOnlinePayment)
934    grok.name('index')
935    grok.require('waeup.viewStudent')
936    form_fields = grok.AutoFields(IStudentOnlinePayment)
937    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
938    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
939    pnav = 4
940
941    @property
942    def title(self):
943        return 'Online Payment Ticket %s' % self.context.p_id
944
945    @property
946    def label(self):
947        return '%s: Online Payment Ticket %s' % (
948            self.context.__parent__.__parent__.fullname,self.context.p_id)
949
950class OnlinePaymentCallbackPage(grok.View):
951    """ Callback view
952    """
953    grok.context(IStudentOnlinePayment)
954    grok.name('callback')
955    grok.require('waeup.payStudent')
956
957    # This update method simulates a valid callback und must be
958    # specified in the customization package. The parameters must be taken
959    # from the incoming request.
960    def update(self):
961        student = self.context.getStudent()
962        write_log_message(self,'valid callback: %s' % self.context.p_id)
963        self.context.r_amount_approved = self.context.amount_auth
964        self.context.r_card_num = u'0000'
965        self.context.r_code = u'00'
966        self.context.p_state = 'paid'
967        self.context.payment_date = datetime.now()
968        if self.context.p_category == 'clearance':
969            # Create CLR access code
970            pin, error = create_accesscode('CLR',0,student.student_id)
971            if error:
972                self.flash('Valid callback received. ' + error)
973                return
974            self.context.ac = pin
975        elif self.context.p_category == 'schoolfee':
976            # Create SFE access code
977            pin, error = create_accesscode('SFE',0,student.student_id)
978            if error:
979                self.flash('Valid callback received. ' + error)
980                return
981            self.context.ac = pin
982        elif self.context.p_category == 'bed_allocation':
983            # Create HOS access code
984            pin, error = create_accesscode('HOS',0,student.student_id)
985            if error:
986                self.flash('Valid callback received. ' + error)
987                return
988            self.context.ac = pin
989        self.flash('Valid callback received.')
990        return
991
992    def render(self):
993        self.redirect(self.url(self.context, '@@index'))
994        return
995
996# We don't need the display form page yet
997#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
998#    """ Page to display the student accommodation data
999#    """
1000#    grok.context(IStudentAccommodation)
1001#    grok.name('xxx')
1002#    grok.require('waeup.viewStudent')
1003#    form_fields = grok.AutoFields(IStudentAccommodation)
1004#    #grok.template('accommodationpage')
1005#    title = 'Accommodation'
1006#    pnav = 4
1007
1008#    @property
1009#    def label(self):
1010#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1011
1012# This manage form page is for both students and students officers.
1013class AccommodationManageFormPage(WAeUPEditFormPage):
1014    """ Page to manage the bed tickets
1015    """
1016    grok.context(IStudentAccommodation)
1017    grok.name('index')
1018    grok.require('waeup.handleStudent')
1019    form_fields = grok.AutoFields(IStudentAccommodation)
1020    grok.template('accommodationmanagepage')
1021    title = 'Accommodation'
1022    pnav = 4
1023
1024    def unremovable(self):
1025        prm = get_principal_role_manager()
1026        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1027        return ('waeup.Student' in roles)
1028
1029    def formatDatetime(self,datetimeobj):
1030        if isinstance(datetimeobj, datetime):
1031            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1032        else:
1033            return None
1034
1035    @property
1036    def label(self):
1037        return '%s: Accommodation' % self.context.__parent__.fullname
1038
1039    def update(self):
1040        super(AccommodationManageFormPage, self).update()
1041        datatable.need()
1042        return
1043
1044    # We need an event handler which releases the bed space too.
1045    @grok.action('Remove selected tickets')
1046    def delBedTicket(self, **data):
1047        form = self.request.form
1048        if form.has_key('val_id'):
1049            child_id = form['val_id']
1050        else:
1051            self.flash('No bed ticket selected.')
1052            self.redirect(self.url(self.context))
1053            return
1054        if not isinstance(child_id, list):
1055            child_id = [child_id]
1056        deleted = []
1057        for id in child_id:
1058            # Students are not allowed to remove bed tickets
1059            if not self.unremovable():
1060                try:
1061                    del self.context[id]
1062                    deleted.append(id)
1063                except:
1064                    self.flash('Could not delete %s: %s: %s' % (
1065                            id, sys.exc_info()[0], sys.exc_info()[1]))
1066        if len(deleted):
1067            self.flash('Successfully removed: %s' % ', '.join(deleted))
1068            write_log_message(self,'removed: % s' % ', '.join(deleted))
1069        self.redirect(self.url(self.context))
1070        return
1071
1072    @grok.action('Add bed ticket')
1073    def addBedTicket(self, **data):
1074        self.redirect(self.url(self.context, '@@add'))
1075
1076class BedTicketAddPage(WAeUPPage):
1077    """ Page to add an online payment ticket
1078    """
1079    grok.context(IStudentAccommodation)
1080    grok.name('add')
1081    grok.require('waeup.handleStudent')
1082    grok.template('enterpin')
1083    ac_prefix = 'HOS'
1084    label = 'Add bed ticket'
1085    title = 'Add bed ticket'
1086    pnav = 4
1087    buttonname = 'Create bed ticket'
1088    notice = ''
1089
1090    # To be sepezified in customization packages
1091    def getAccommodationDetails(self, student):
1092        return getAccommodationDetails(student)
1093
1094    # To be sepezified in customization packages
1095    def selectBed(self, student):
1096        return getAccommodationDetails(student)
1097
1098    def update(self, SUBMIT=None):
1099        student = self.context.getStudent()
1100        acc_details  = self.getAccommodationDetails(student)
1101        if not student.state in acc_details['allowed_states']:
1102            self.flash("Wrong state.")
1103            self.redirect(self.url(self.context))
1104            return
1105        if str(student['studycourse'].entry_session) in self.context.keys():
1106            self.flash('Ticket for %d already created.'
1107                % student['studycourse'].entry_session)
1108            self.redirect(self.url(self.context))
1109            return
1110        self.ac_series = self.request.form.get('ac_series', None)
1111        self.ac_number = self.request.form.get('ac_number', None)
1112        if SUBMIT is None:
1113            return
1114        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1115        code = get_access_code(pin)
1116        if not code:
1117            self.flash('Activation code is invalid.')
1118            return
1119        # Search a bed and exit if no bed is found or if the student
1120        # has already booked a bed
1121        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1122        entries = cat.searchResults(
1123            bed_type=(acc_details['bt'],acc_details['bt']))
1124        available_beds = [
1125            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1126        if not available_beds:
1127            self.flash('There is no free bed in your category %s.'
1128                        % acc_details['bt'])
1129            return
1130        entries = cat.searchResults(
1131            owner=(student.student_id,student.student_id))
1132        # Cannot happen but anyway ...
1133        if len(entries):
1134            bed = [entry for entry in entries][0]
1135            self.flash('You already booked bed %s.'
1136                        % bed.bed_id)
1137            return
1138        # Mark pin as used (this also fires a pin related transition)
1139        if code.state == USED:
1140            self.flash('Activation code has already been used.')
1141            return
1142        else:
1143            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1144            # Here we know that the ac is in state initialized so we do not
1145            # expect an exception, but the owner might be different
1146            if not invalidate_accesscode(
1147                pin,comment,self.context.getStudent().student_id):
1148                self.flash('You are not the owner of this access code.')
1149                return
1150        # Create bed ticket and book bed
1151        bedticket = createObject(u'waeup.BedTicket')
1152        bedticket.booking_code = pin
1153        bedticket.booking_session = acc_details['booking_session']
1154        bedticket.bed_type = acc_details['bt']
1155        bed = selectBed(available_beds)
1156        bed.bookBed(student.student_id)
1157        bedticket.bed = bed
1158        hall_title = bed.__parent__.hostel_name
1159        coordinates = bed.getBedCoordinates()[1:]
1160        block, room_nr, bed_nr = coordinates
1161        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1162            hall_title, block, room_nr, bed_nr)
1163        key = str(acc_details['booking_session'])
1164        self.context[key] = bedticket
1165        self.flash('Bed ticket created and bed booked: %s'
1166            % bedticket.bed_coordinates)
1167        self.redirect(self.url(self.context))
1168        return
1169
1170class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1171    """ Page to display bed tickets
1172    """
1173    grok.context(IBedTicket)
1174    grok.name('index')
1175    grok.require('waeup.viewStudent')
1176    form_fields = grok.AutoFields(IBedTicket)
1177    form_fields[
1178        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1179    pnav = 4
1180
1181    @property
1182    def label(self):
1183        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1184
1185    @property
1186    def title(self):
1187        return 'Bed Ticket %s' % self.context.getSessionString()
1188
1189class StudentHistoryPage(WAeUPPage):
1190    """ Page to display student clearance data
1191    """
1192    grok.context(IStudent)
1193    grok.name('history')
1194    grok.require('waeup.viewStudent')
1195    grok.template('studenthistory')
1196    title = 'History'
1197    pnav = 4
1198
1199    @property
1200    def label(self):
1201        return '%s: History' % self.context.fullname
1202
1203# Pages for students only
1204
1205class StudentBaseEditActionButton(ManageActionButton):
1206    grok.order(1)
1207    grok.context(IStudent)
1208    grok.view(StudentBaseDisplayFormPage)
1209    grok.require('waeup.handleStudent')
1210    text = 'Change password'
1211    target = 'bedit'
1212
1213class StudentPasswordSetting(grok.Adapter):
1214    """Adapt IStudent to data needed for password settings.
1215
1216    We provide password getters/setters for the attached context (an
1217    IStudent object) that cooperate seamless with the usual
1218    formlib/form techniques.
1219    """
1220    grok.context(IStudent)
1221    grok.provides(IStudentPasswordSetting)
1222
1223    def __init__(self, context):
1224        self.name = context.fullname
1225        self.password_repeat = context.password
1226        self.context = context
1227        return
1228
1229    def getPassword(self):
1230        return self.context.password
1231
1232    def setPassword(self, password):
1233        IUserAccount(self.context).setPassword(password)
1234        return
1235
1236    password = property(getPassword, setPassword)
1237
1238class StudentBaseEditFormPage(WAeUPEditFormPage):
1239    """ View to edit student base data by student
1240    """
1241    grok.context(IStudent)
1242    grok.name('bedit')
1243    grok.require('waeup.handleStudent')
1244    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1245    #    'student_id', 'reg_number', 'matric_number')
1246    form_fields = grok.AutoFields(IStudentPasswordSetting)
1247    grok.template('baseeditpage')
1248    label = 'Change password'
1249    title = 'Base Data'
1250    pnav = 4
1251
1252    def update(self):
1253        super(StudentBaseEditFormPage, self).update()
1254        self.wf_info = IWorkflowInfo(self.context)
1255        return
1256
1257    def onFailure(self, action, data, errors):
1258        new_status = []
1259        other_errors = False
1260        for error in errors:
1261            msg = getattr(error, 'message', '')
1262            if isinstance(msg, basestring) and msg != '':
1263                new_status.append(msg)
1264            else:
1265                other_errors = True
1266        if other_errors:
1267            if new_status:
1268                new_status.append('see below for further errors')
1269            else:
1270                new_status.append('See below for details.')
1271        if new_status:
1272            self.status = u'There were errors: %s' % ', '.join(new_status)
1273        return
1274
1275    @grok.action('Save', failure=onFailure)
1276    def save(self, **data):
1277        self.applyData(self.context, **data)
1278        self.flash('Form has been saved.')
1279        return
1280
1281class StudentClearanceStartActionButton(ManageActionButton):
1282    grok.order(1)
1283    grok.context(IStudent)
1284    grok.view(StudentClearanceDisplayFormPage)
1285    grok.require('waeup.handleStudent')
1286    icon = 'actionicon_start.png'
1287    text = 'Start clearance'
1288    target = 'start_clearance'
1289
1290    @property
1291    def target_url(self):
1292        if self.context.state != 'admitted':
1293            return ''
1294        return self.view.url(self.view.context, self.target)
1295
1296class StartClearancePage(WAeUPPage):
1297    grok.context(IStudent)
1298    grok.name('start_clearance')
1299    grok.require('waeup.handleStudent')
1300    grok.template('enterpin')
1301    title = 'Start clearance'
1302    label = 'Start clearance'
1303    ac_prefix = 'CLR'
1304    notice = ''
1305    pnav = 4
1306    buttonname = 'Start clearance now'
1307
1308    def update(self, SUBMIT=None):
1309        if not self.context.state == 'admitted':
1310            self.flash("Wrong state.")
1311            self.redirect(self.url(self.context))
1312            return
1313        self.ac_series = self.request.form.get('ac_series', None)
1314        self.ac_number = self.request.form.get('ac_number', None)
1315
1316        if SUBMIT is None:
1317            return
1318        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1319        code = get_access_code(pin)
1320        if not code:
1321            self.flash('Activation code is invalid.')
1322            return
1323        # Mark pin as used (this also fires a pin related transition)
1324        # and fire transition start_clearance
1325        if code.state == USED:
1326            self.flash('Activation code has already been used.')
1327            return
1328        else:
1329            comment = u"AC invalidated for %s" % self.context.student_id
1330            # Here we know that the ac is in state initialized so we do not
1331            # expect an exception, but the owner might be different
1332            if not invalidate_accesscode(pin,comment,self.context.student_id):
1333                self.flash('You are not the owner of this access code.')
1334                return
1335            self.context.clr_code = pin
1336        IWorkflowInfo(self.context).fireTransition('start_clearance')
1337        self.flash('Clearance process has been started.')
1338        self.redirect(self.url(self.context,'cedit'))
1339        return
1340
1341class StudentClearanceEditActionButton(ManageActionButton):
1342    grok.order(1)
1343    grok.context(IStudent)
1344    grok.view(StudentClearanceDisplayFormPage)
1345    grok.require('waeup.handleStudent')
1346    text = 'Edit'
1347    target = 'cedit'
1348
1349    @property
1350    def target_url(self):
1351        if self.context.clearance_locked:
1352            return ''
1353        return self.view.url(self.view.context, self.target)
1354
1355class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1356    """ View to edit student clearance data by student
1357    """
1358    grok.context(IStudent)
1359    grok.name('cedit')
1360    grok.require('waeup.handleStudent')
1361    form_fields = grok.AutoFields(
1362        IStudentClearanceEdit).omit('clearance_locked')
1363    label = 'Edit clearance data'
1364    title = 'Clearance Data'
1365    pnav = 4
1366    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1367
1368    def emitLockMessage(self):
1369        self.flash('The requested form is locked (read-only).')
1370        self.redirect(self.url(self.context))
1371        return
1372
1373    def update(self):
1374        if self.context.clearance_locked:
1375            self.emitLockMessage()
1376            return
1377        datepicker.need()
1378        return super(StudentClearanceEditFormPage, self).update()
1379
1380    @grok.action('Save')
1381    def save(self, **data):
1382        self.applyData(self.context, **data)
1383        self.flash('Clearance form has been saved.')
1384        return
1385
1386    @grok.action('Save and request clearance')
1387    def requestclearance(self, **data):
1388        self.applyData(self.context, **data)
1389        self.context._p_changed = True
1390        #if self.dataNotComplete():
1391        #    self.flash(self.dataNotComplete())
1392        #    return
1393        self.flash('Clearance form has been saved.')
1394        self.redirect(self.url(self.context,'request_clearance'))
1395        return
1396
1397class RequestClearancePage(WAeUPPage):
1398    grok.context(IStudent)
1399    grok.name('request_clearance')
1400    grok.require('waeup.handleStudent')
1401    grok.template('enterpin')
1402    title = 'Request clearance'
1403    label = 'Request clearance'
1404    notice = 'Enter the CLR access code used for starting clearance.'
1405    ac_prefix = 'CLR'
1406    pnav = 4
1407    buttonname = 'Request clearance now'
1408
1409    def update(self, SUBMIT=None):
1410        self.ac_series = self.request.form.get('ac_series', None)
1411        self.ac_number = self.request.form.get('ac_number', None)
1412        if SUBMIT is None:
1413            return
1414        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1415        if self.context.clr_code != pin:
1416            self.flash("This isn't your CLR access code.")
1417            return
1418        state = IWorkflowState(self.context).getState()
1419        # This shouldn't happen, but the application officer
1420        # might have forgotten to lock the form after changing the state
1421        if state != CLEARANCE:
1422            self.flash('This form cannot be submitted. Wrong state!')
1423            return
1424        IWorkflowInfo(self.context).fireTransition('request_clearance')
1425        self.flash('Clearance has been requested.')
1426        self.redirect(self.url(self.context))
1427        return
1428
1429class CourseRegistrationStartActionButton(ManageActionButton):
1430    grok.order(1)
1431    grok.context(IStudentStudyCourse)
1432    grok.view(StudyCourseDisplayFormPage)
1433    grok.require('waeup.handleStudent')
1434    icon = 'actionicon_start.png'
1435    text = 'Start course registration'
1436    target = 'start_course_registration'
1437
1438    @property
1439    def target_url(self):
1440        if not self.context.getStudent().state in (CLEARED,RETURNING):
1441            return ''
1442        return self.view.url(self.view.context, self.target)
1443
1444class StartCourseRegistrationPage(WAeUPPage):
1445    grok.context(IStudentStudyCourse)
1446    grok.name('start_course_registration')
1447    grok.require('waeup.handleStudent')
1448    grok.template('enterpin')
1449    title = 'Start course registration'
1450    label = 'Start course registration'
1451    ac_prefix = 'SFE'
1452    notice = ''
1453    pnav = 4
1454    buttonname = 'Start course registration now'
1455
1456    def update(self, SUBMIT=None):
1457        if not self.context.getStudent().state in (CLEARED,RETURNING):
1458            self.flash("Wrong state.")
1459            self.redirect(self.url(self.context))
1460            return
1461        self.ac_series = self.request.form.get('ac_series', None)
1462        self.ac_number = self.request.form.get('ac_number', None)
1463
1464        if SUBMIT is None:
1465            return
1466        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1467        code = get_access_code(pin)
1468        if not code:
1469            self.flash('Activation code is invalid.')
1470            return
1471        # Mark pin as used (this also fires a pin related transition)
1472        # and fire transition start_clearance
1473        if code.state == USED:
1474            self.flash('Activation code has already been used.')
1475            return
1476        else:
1477            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1478            # Here we know that the ac is in state initialized so we do not
1479            # expect an exception, but the owner might be different
1480            if not invalidate_accesscode(
1481                pin,comment,self.context.getStudent().student_id):
1482                self.flash('You are not the owner of this access code.')
1483                return
1484        if self.context.getStudent().state == CLEARED:
1485            IWorkflowInfo(self.context.getStudent()).fireTransition(
1486                'pay_first_school_fee')
1487        elif self.context.getStudent().state == RETURNING:
1488            IWorkflowInfo(self.context.getStudent()).fireTransition(
1489                'pay_school_fee')
1490        self.flash('Course registration has been started.')
1491        self.redirect(self.url(self.context))
1492        return
1493
1494
1495class AddStudyLevelActionButton(AddActionButton):
1496    grok.order(1)
1497    grok.context(IStudentStudyCourse)
1498    grok.view(StudyCourseDisplayFormPage)
1499    grok.require('waeup.handleStudent')
1500    text = 'Add course list'
1501    target = 'add'
1502
1503    @property
1504    def target_url(self):
1505        student = self.view.context.getStudent()
1506        condition1 = student.state != 'school fee paid'
1507        condition2 = str(student['studycourse'].current_level) in \
1508            self.view.context.keys()
1509        if condition1 or condition2:
1510            return ''
1511        return self.view.url(self.view.context, self.target)
1512
1513class AddStudyLevelFormPage(WAeUPEditFormPage):
1514    """ Page for students to add current study levels
1515    """
1516    grok.context(IStudentStudyCourse)
1517    grok.name('add')
1518    grok.require('waeup.handleStudent')
1519    grok.template('studyleveladdpage')
1520    form_fields = grok.AutoFields(IStudentStudyCourse)
1521    title = 'Study Course'
1522    pnav = 4
1523
1524    @property
1525    def label(self):
1526        studylevelsource = StudyLevelSource().factory
1527        code = self.context.current_level
1528        title = studylevelsource.getTitle(self.context, code)
1529        return 'Add current level %s' % title
1530
1531    def emitLockMessage(self):
1532        self.flash('The requested form is locked (read-only).')
1533        self.redirect(self.url(self.context))
1534        return
1535
1536    def update(self):
1537        if self.context.getStudent().state != 'school fee paid':
1538            self.emitLockMessage()
1539            return
1540        super(AddStudyLevelFormPage, self).update()
1541        return
1542
1543    @grok.action('Create course list now')
1544    def addStudyLevel(self, **data):
1545        studylevel = StudentStudyLevel()
1546        studylevel.level = self.context.current_level
1547        studylevel.level_session = self.context.current_session
1548        try:
1549            self.context.addStudentStudyLevel(
1550                self.context.certificate,studylevel)
1551        except KeyError:
1552            self.flash('This level exists.')
1553        self.redirect(self.url(self.context))
1554        return
1555
1556class StudyLevelEditActionButton(ManageActionButton):
1557    grok.order(1)
1558    grok.context(IStudentStudyLevel)
1559    grok.view(StudyLevelDisplayFormPage)
1560    grok.require('waeup.handleStudent')
1561    text = 'Add and remove courses'
1562    target = 'edit'
1563
1564    @property
1565    def target_url(self):
1566        student = self.view.context.getStudent()
1567        condition1 = student.state != 'school fee paid'
1568        condition2 = student[
1569            'studycourse'].current_level != self.view.context.level
1570        if condition1 or condition2:
1571            return ''
1572        return self.view.url(self.view.context, self.target)
1573
1574class StudyLevelEditFormPage(WAeUPEditFormPage):
1575    """ Page to edit the student study level data by students
1576    """
1577    grok.context(IStudentStudyLevel)
1578    grok.name('edit')
1579    grok.require('waeup.handleStudent')
1580    grok.template('studyleveleditpage')
1581    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1582        'level_session', 'level_verdict')
1583    pnav = 4
1584
1585    def update(self):
1586        super(StudyLevelEditFormPage, self).update()
1587    #    tabs.need()
1588        datatable.need()
1589        return
1590
1591    @property
1592    def title(self):
1593        return 'Study Level %s' % self.context.level_title
1594
1595    @property
1596    def label(self):
1597        return 'Add and remove course tickets of study level %s' % self.context.level_title
1598
1599    @property
1600    def total_credits(self):
1601        total_credits = 0
1602        for key, val in self.context.items():
1603            total_credits += val.credits
1604        return total_credits
1605
1606    @grok.action('Add course ticket')
1607    def addCourseTicket(self, **data):
1608        self.redirect(self.url(self.context, 'ctadd'))
1609
1610    @grok.action('Remove selected tickets')
1611    def delCourseTicket(self, **data):
1612        form = self.request.form
1613        if form.has_key('val_id'):
1614            child_id = form['val_id']
1615        else:
1616            self.flash('No ticket selected.')
1617            self.redirect(self.url(self.context, '@@edit'))
1618            return
1619        if not isinstance(child_id, list):
1620            child_id = [child_id]
1621        deleted = []
1622        for id in child_id:
1623            # Students are not allowed to remove core tickets
1624            if not self.context[id].core_or_elective:
1625                try:
1626                    del self.context[id]
1627                    deleted.append(id)
1628                except:
1629                    self.flash('Could not delete %s: %s: %s' % (
1630                            id, sys.exc_info()[0], sys.exc_info()[1]))
1631        if len(deleted):
1632            self.flash('Successfully removed: %s' % ', '.join(deleted))
1633        self.redirect(self.url(self.context, u'@@edit'))
1634        return
1635
1636    @grok.action('Register course list')
1637    def register_courses(self, **data):
1638        state = IWorkflowState(self.context.getStudent()).getState()
1639        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1640        self.flash('Course list has been registered.')
1641        self.redirect(self.url(self.context))
1642        return
1643
1644class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1645    """Add a course ticket by student.
1646    """
1647    grok.name('ctadd')
1648    grok.require('waeup.handleStudent')
1649    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1650        'grade', 'score', 'core_or_elective', 'automatic')
1651
1652    @grok.action('Add course ticket')
1653    def addCourseTicket(self, **data):
1654        ticket = CourseTicket()
1655        course = data['course']
1656        ticket.automatic = False
1657        ticket.code = course.code
1658        ticket.title = course.title
1659        ticket.faculty = course.__parent__.__parent__.__parent__.title
1660        ticket.department = course.__parent__.__parent__.title
1661        ticket.credits = course.credits
1662        ticket.passmark = course.passmark
1663        ticket.semester = course.semester
1664        try:
1665            self.context.addCourseTicket(ticket)
1666        except KeyError:
1667            self.flash('The ticket exists.')
1668            return
1669        self.flash('Successfully added %s.' % ticket.code)
1670        self.redirect(self.url(self.context, u'@@edit'))
1671        return
Note: See TracBrowser for help on using the repository browser.