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

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

Prepare selection of bed for customization.

  • Property svn:keywords set to Id
File size: 56.6 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, selectBed)
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    # To be sepezified in customization packages
1094    def selectBed(self, student):
1095        return getAccommodationDetails(student)
1096
1097    def update(self, SUBMIT=None):
1098        student = self.context.getStudent()
1099        acc_details  = self.getAccommodationDetails(student)
1100        if not student.state in acc_details['allowed_states']:
1101            self.flash("Wrong state.")
1102            self.redirect(self.url(self.context))
1103            return
1104        if str(student['studycourse'].entry_session) in self.context.keys():
1105            self.flash('Ticket for %d already created.'
1106                % student['studycourse'].entry_session)
1107            self.redirect(self.url(self.context))
1108            return
1109        self.ac_series = self.request.form.get('ac_series', None)
1110        self.ac_number = self.request.form.get('ac_number', None)
1111        if SUBMIT is None:
1112            return
1113        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1114        code = get_access_code(pin)
1115        if not code:
1116            self.flash('Activation code is invalid.')
1117            return
1118        # Search a bed and exit if no bed is found or if the student
1119        # has already booked a bed
1120        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1121        entries = cat.searchResults(
1122            bed_type=(acc_details['bt'],acc_details['bt']))
1123        available_beds = [
1124            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1125        if not available_beds:
1126            self.flash('There is no free bed in your category %s.'
1127                        % acc_details['bt'])
1128            return
1129        entries = cat.searchResults(
1130            owner=(student.student_id,student.student_id))
1131        # Cannot happen but anyway ...
1132        if len(entries):
1133            bed = [entry for entry in entries][0]
1134            self.flash('You already booked bed %s.'
1135                        % bed.bed_id)
1136            return
1137        # Mark pin as used (this also fires a pin related transition)
1138        if code.state == USED:
1139            self.flash('Activation code has already been used.')
1140            return
1141        else:
1142            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1143            # Here we know that the ac is in state initialized so we do not
1144            # expect an exception, but the owner might be different
1145            if not invalidate_accesscode(
1146                pin,comment,self.context.getStudent().student_id):
1147                self.flash('You are not the owner of this access code.')
1148                return
1149        # Create bed ticket and book bed
1150        bedticket = createObject(u'waeup.BedTicket')
1151        bedticket.booking_code = pin
1152        bedticket.booking_session = acc_details['booking_session']
1153        bedticket.bed_type = acc_details['bt']
1154        bed = selectBed(available_beds)
1155        bed.bookBed(student.student_id)
1156        bedticket.bed = bed # maybe wo don't need the bed object itself and can
1157                            # remove that later
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.