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

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

We have to call notify(grok.ObjectModifiedEvent?(self)) to update the catalog when only an attribute is changed.

Only one bed per student is allowed.

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