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

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

Don't use access code if no bed is found. This is in contrast to the Uniben practice.

  • Property svn:keywords set to Id
File size: 55.8 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
1111        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1112        entries = cat.searchResults(
1113            bed_type=(acc_details['bt'],acc_details['bt']))
1114        available_beds = [
1115            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1116        if not available_beds:
1117            self.flash('There is no free bed in your category %s.'
1118                        % acc_details['bt'])
1119            return
1120        # Mark pin as used (this also fires a pin related transition)
1121        if code.state == USED:
1122            self.flash('Activation code has already been used.')
1123            return
1124        else:
1125            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1126            # Here we know that the ac is in state initialized so we do not
1127            # expect an exception, but the owner might be different
1128            if not invalidate_accesscode(
1129                pin,comment,self.context.getStudent().student_id):
1130                self.flash('You are not the owner of this access code.')
1131                return
1132        # Create bed ticket and book bed
1133        bedticket = createObject(u'waeup.BedTicket')
1134        bedticket.booking_code = pin
1135        bedticket.booking_session = acc_details['booking_session']
1136        bedticket.bed_type = acc_details['bt']
1137        bed = available_beds[0] # first bed found
1138        bed.bookBed(student.student_id)
1139        bedticket.bed = bed # maybe wo don't need the bed object itself
1140        hall_title = bed.__parent__.hostel_name
1141        coordinates = bed.getBedCoordinates()[1:]
1142        block, room_nr, bed_nr = coordinates
1143        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1144            hall_title, block, room_nr, bed_nr)
1145        key = str(acc_details['booking_session'])
1146        self.context[key] = bedticket
1147        self.flash('Bed ticket created and bed booked: %s'
1148            % bedticket.bed_coordinates)
1149        self.redirect(self.url(self.context))
1150        return
1151
1152class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1153    """ Page to display bed tickets
1154    """
1155    grok.context(IBedTicket)
1156    grok.name('index')
1157    grok.require('waeup.viewStudent')
1158    form_fields = grok.AutoFields(IBedTicket)
1159    form_fields[
1160        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1161    pnav = 4
1162
1163    @property
1164    def label(self):
1165        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1166
1167    @property
1168    def title(self):
1169        return 'Bed Ticket %s' % self.context.getSessionString()
1170
1171class StudentHistoryPage(WAeUPPage):
1172    """ Page to display student clearance data
1173    """
1174    grok.context(IStudent)
1175    grok.name('history')
1176    grok.require('waeup.viewStudent')
1177    grok.template('studenthistory')
1178    title = 'History'
1179    pnav = 4
1180
1181    @property
1182    def label(self):
1183        return '%s: History' % self.context.fullname
1184
1185# Pages for students only
1186
1187class StudentBaseEditActionButton(ManageActionButton):
1188    grok.order(1)
1189    grok.context(IStudent)
1190    grok.view(StudentBaseDisplayFormPage)
1191    grok.require('waeup.handleStudent')
1192    text = 'Change password'
1193    target = 'bedit'
1194
1195class StudentPasswordSetting(grok.Adapter):
1196    """Adapt IStudent to data needed for password settings.
1197
1198    We provide password getters/setters for the attached context (an
1199    IStudent object) that cooperate seamless with the usual
1200    formlib/form techniques.
1201    """
1202    grok.context(IStudent)
1203    grok.provides(IStudentPasswordSetting)
1204
1205    def __init__(self, context):
1206        self.name = context.fullname
1207        self.password_repeat = context.password
1208        self.context = context
1209        return
1210
1211    def getPassword(self):
1212        return self.context.password
1213
1214    def setPassword(self, password):
1215        IUserAccount(self.context).setPassword(password)
1216        return
1217
1218    password = property(getPassword, setPassword)
1219
1220class StudentBaseEditFormPage(WAeUPEditFormPage):
1221    """ View to edit student base data by student
1222    """
1223    grok.context(IStudent)
1224    grok.name('bedit')
1225    grok.require('waeup.handleStudent')
1226    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1227    #    'student_id', 'reg_number', 'matric_number')
1228    form_fields = grok.AutoFields(IStudentPasswordSetting)
1229    grok.template('baseeditpage')
1230    label = 'Change password'
1231    title = 'Base Data'
1232    pnav = 4
1233
1234    def update(self):
1235        super(StudentBaseEditFormPage, self).update()
1236        self.wf_info = IWorkflowInfo(self.context)
1237        return
1238
1239    def onFailure(self, action, data, errors):
1240        new_status = []
1241        other_errors = False
1242        for error in errors:
1243            msg = getattr(error, 'message', '')
1244            if isinstance(msg, basestring) and msg != '':
1245                new_status.append(msg)
1246            else:
1247                other_errors = True
1248        if other_errors:
1249            if new_status:
1250                new_status.append('see below for further errors')
1251            else:
1252                new_status.append('See below for details.')
1253        if new_status:
1254            self.status = u'There were errors: %s' % ', '.join(new_status)
1255        return
1256
1257    @grok.action('Save', failure=onFailure)
1258    def save(self, **data):
1259        self.applyData(self.context, **data)
1260        self.flash('Form has been saved.')
1261        return
1262
1263class StudentClearanceStartActionButton(ManageActionButton):
1264    grok.order(1)
1265    grok.context(IStudent)
1266    grok.view(StudentClearanceDisplayFormPage)
1267    grok.require('waeup.handleStudent')
1268    icon = 'actionicon_start.png'
1269    text = 'Start clearance'
1270    target = 'start_clearance'
1271
1272    @property
1273    def target_url(self):
1274        if self.context.state != 'admitted':
1275            return ''
1276        return self.view.url(self.view.context, self.target)
1277
1278class StartClearancePage(WAeUPPage):
1279    grok.context(IStudent)
1280    grok.name('start_clearance')
1281    grok.require('waeup.handleStudent')
1282    grok.template('enterpin')
1283    title = 'Start clearance'
1284    label = 'Start clearance'
1285    ac_prefix = 'CLR'
1286    notice = ''
1287    pnav = 4
1288    buttonname = 'Start clearance now'
1289
1290    def update(self, SUBMIT=None):
1291        if not self.context.state == 'admitted':
1292            self.flash("Wrong state.")
1293            self.redirect(self.url(self.context))
1294            return
1295        self.ac_series = self.request.form.get('ac_series', None)
1296        self.ac_number = self.request.form.get('ac_number', None)
1297
1298        if SUBMIT is None:
1299            return
1300        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1301        code = get_access_code(pin)
1302        if not code:
1303            self.flash('Activation code is invalid.')
1304            return
1305        # Mark pin as used (this also fires a pin related transition)
1306        # and fire transition start_clearance
1307        if code.state == USED:
1308            self.flash('Activation code has already been used.')
1309            return
1310        else:
1311            comment = u"AC invalidated for %s" % self.context.student_id
1312            # Here we know that the ac is in state initialized so we do not
1313            # expect an exception, but the owner might be different
1314            if not invalidate_accesscode(pin,comment,self.context.student_id):
1315                self.flash('You are not the owner of this access code.')
1316                return
1317            self.context.clr_code = pin
1318        IWorkflowInfo(self.context).fireTransition('start_clearance')
1319        self.flash('Clearance process has been started.')
1320        self.redirect(self.url(self.context,'cedit'))
1321        return
1322
1323class StudentClearanceEditActionButton(ManageActionButton):
1324    grok.order(1)
1325    grok.context(IStudent)
1326    grok.view(StudentClearanceDisplayFormPage)
1327    grok.require('waeup.handleStudent')
1328    text = 'Edit'
1329    target = 'cedit'
1330
1331    @property
1332    def target_url(self):
1333        if self.context.clearance_locked:
1334            return ''
1335        return self.view.url(self.view.context, self.target)
1336
1337class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1338    """ View to edit student clearance data by student
1339    """
1340    grok.context(IStudent)
1341    grok.name('cedit')
1342    grok.require('waeup.handleStudent')
1343    form_fields = grok.AutoFields(
1344        IStudentClearanceEdit).omit('clearance_locked')
1345    label = 'Edit clearance data'
1346    title = 'Clearance Data'
1347    pnav = 4
1348    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1349
1350    def emitLockMessage(self):
1351        self.flash('The requested form is locked (read-only).')
1352        self.redirect(self.url(self.context))
1353        return
1354
1355    def update(self):
1356        if self.context.clearance_locked:
1357            self.emitLockMessage()
1358            return
1359        datepicker.need()
1360        return super(StudentClearanceEditFormPage, self).update()
1361
1362    @grok.action('Save')
1363    def save(self, **data):
1364        self.applyData(self.context, **data)
1365        self.flash('Clearance form has been saved.')
1366        return
1367
1368    @grok.action('Save and request clearance')
1369    def requestclearance(self, **data):
1370        self.applyData(self.context, **data)
1371        self.context._p_changed = True
1372        #if self.dataNotComplete():
1373        #    self.flash(self.dataNotComplete())
1374        #    return
1375        self.flash('Clearance form has been saved.')
1376        self.redirect(self.url(self.context,'request_clearance'))
1377        return
1378
1379class RequestClearancePage(WAeUPPage):
1380    grok.context(IStudent)
1381    grok.name('request_clearance')
1382    grok.require('waeup.handleStudent')
1383    grok.template('enterpin')
1384    title = 'Request clearance'
1385    label = 'Request clearance'
1386    notice = 'Enter the CLR access code used for starting clearance.'
1387    ac_prefix = 'CLR'
1388    pnav = 4
1389    buttonname = 'Request clearance now'
1390
1391    def update(self, SUBMIT=None):
1392        self.ac_series = self.request.form.get('ac_series', None)
1393        self.ac_number = self.request.form.get('ac_number', None)
1394        if SUBMIT is None:
1395            return
1396        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1397        if self.context.clr_code != pin:
1398            self.flash("This isn't your CLR access code.")
1399            return
1400        state = IWorkflowState(self.context).getState()
1401        # This shouldn't happen, but the application officer
1402        # might have forgotten to lock the form after changing the state
1403        if state != CLEARANCE:
1404            self.flash('This form cannot be submitted. Wrong state!')
1405            return
1406        IWorkflowInfo(self.context).fireTransition('request_clearance')
1407        self.flash('Clearance has been requested.')
1408        self.redirect(self.url(self.context))
1409        return
1410
1411class CourseRegistrationStartActionButton(ManageActionButton):
1412    grok.order(1)
1413    grok.context(IStudentStudyCourse)
1414    grok.view(StudyCourseDisplayFormPage)
1415    grok.require('waeup.handleStudent')
1416    icon = 'actionicon_start.png'
1417    text = 'Start course registration'
1418    target = 'start_course_registration'
1419
1420    @property
1421    def target_url(self):
1422        if not self.context.getStudent().state in (CLEARED,RETURNING):
1423            return ''
1424        return self.view.url(self.view.context, self.target)
1425
1426class StartCourseRegistrationPage(WAeUPPage):
1427    grok.context(IStudentStudyCourse)
1428    grok.name('start_course_registration')
1429    grok.require('waeup.handleStudent')
1430    grok.template('enterpin')
1431    title = 'Start course registration'
1432    label = 'Start course registration'
1433    ac_prefix = 'SFE'
1434    notice = ''
1435    pnav = 4
1436    buttonname = 'Start course registration now'
1437
1438    def update(self, SUBMIT=None):
1439        if not self.context.getStudent().state in (CLEARED,RETURNING):
1440            self.flash("Wrong state.")
1441            self.redirect(self.url(self.context))
1442            return
1443        self.ac_series = self.request.form.get('ac_series', None)
1444        self.ac_number = self.request.form.get('ac_number', None)
1445
1446        if SUBMIT is None:
1447            return
1448        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1449        code = get_access_code(pin)
1450        if not code:
1451            self.flash('Activation code is invalid.')
1452            return
1453        # Mark pin as used (this also fires a pin related transition)
1454        # and fire transition start_clearance
1455        if code.state == USED:
1456            self.flash('Activation code has already been used.')
1457            return
1458        else:
1459            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1460            # Here we know that the ac is in state initialized so we do not
1461            # expect an exception, but the owner might be different
1462            if not invalidate_accesscode(
1463                pin,comment,self.context.getStudent().student_id):
1464                self.flash('You are not the owner of this access code.')
1465                return
1466        if self.context.getStudent().state == CLEARED:
1467            IWorkflowInfo(self.context.getStudent()).fireTransition(
1468                'pay_first_school_fee')
1469        elif self.context.getStudent().state == RETURNING:
1470            IWorkflowInfo(self.context.getStudent()).fireTransition(
1471                'pay_school_fee')
1472        self.flash('Course registration has been started.')
1473        self.redirect(self.url(self.context))
1474        return
1475
1476
1477class AddStudyLevelActionButton(AddActionButton):
1478    grok.order(1)
1479    grok.context(IStudentStudyCourse)
1480    grok.view(StudyCourseDisplayFormPage)
1481    grok.require('waeup.handleStudent')
1482    text = 'Add course list'
1483    target = 'add'
1484
1485    @property
1486    def target_url(self):
1487        student = self.view.context.getStudent()
1488        condition1 = student.state != 'school fee paid'
1489        condition2 = str(student['studycourse'].current_level) in \
1490            self.view.context.keys()
1491        if condition1 or condition2:
1492            return ''
1493        return self.view.url(self.view.context, self.target)
1494
1495class AddStudyLevelFormPage(WAeUPEditFormPage):
1496    """ Page for students to add current study levels
1497    """
1498    grok.context(IStudentStudyCourse)
1499    grok.name('add')
1500    grok.require('waeup.handleStudent')
1501    grok.template('studyleveladdpage')
1502    form_fields = grok.AutoFields(IStudentStudyCourse)
1503    title = 'Study Course'
1504    pnav = 4
1505
1506    @property
1507    def label(self):
1508        studylevelsource = StudyLevelSource().factory
1509        code = self.context.current_level
1510        title = studylevelsource.getTitle(self.context, code)
1511        return 'Add current level %s' % title
1512
1513    def emitLockMessage(self):
1514        self.flash('The requested form is locked (read-only).')
1515        self.redirect(self.url(self.context))
1516        return
1517
1518    def update(self):
1519        if self.context.getStudent().state != 'school fee paid':
1520            self.emitLockMessage()
1521            return
1522        super(AddStudyLevelFormPage, self).update()
1523        return
1524
1525    @grok.action('Create course list now')
1526    def addStudyLevel(self, **data):
1527        studylevel = StudentStudyLevel()
1528        studylevel.level = self.context.current_level
1529        studylevel.level_session = self.context.current_session
1530        try:
1531            self.context.addStudentStudyLevel(
1532                self.context.certificate,studylevel)
1533        except KeyError:
1534            self.flash('This level exists.')
1535        self.redirect(self.url(self.context))
1536        return
1537
1538class StudyLevelEditActionButton(ManageActionButton):
1539    grok.order(1)
1540    grok.context(IStudentStudyLevel)
1541    grok.view(StudyLevelDisplayFormPage)
1542    grok.require('waeup.handleStudent')
1543    text = 'Add and remove courses'
1544    target = 'edit'
1545
1546    @property
1547    def target_url(self):
1548        student = self.view.context.getStudent()
1549        condition1 = student.state != 'school fee paid'
1550        condition2 = student[
1551            'studycourse'].current_level != self.view.context.level
1552        if condition1 or condition2:
1553            return ''
1554        return self.view.url(self.view.context, self.target)
1555
1556class StudyLevelEditFormPage(WAeUPEditFormPage):
1557    """ Page to edit the student study level data by students
1558    """
1559    grok.context(IStudentStudyLevel)
1560    grok.name('edit')
1561    grok.require('waeup.handleStudent')
1562    grok.template('studyleveleditpage')
1563    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1564        'level_session', 'level_verdict')
1565    pnav = 4
1566
1567    def update(self):
1568        super(StudyLevelEditFormPage, self).update()
1569    #    tabs.need()
1570        datatable.need()
1571        return
1572
1573    @property
1574    def title(self):
1575        return 'Study Level %s' % self.context.level_title
1576
1577    @property
1578    def label(self):
1579        return 'Add and remove course tickets of study level %s' % self.context.level_title
1580
1581    @property
1582    def total_credits(self):
1583        total_credits = 0
1584        for key, val in self.context.items():
1585            total_credits += val.credits
1586        return total_credits
1587
1588    @grok.action('Add course ticket')
1589    def addCourseTicket(self, **data):
1590        self.redirect(self.url(self.context, 'ctadd'))
1591
1592    @grok.action('Remove selected tickets')
1593    def delCourseTicket(self, **data):
1594        form = self.request.form
1595        if form.has_key('val_id'):
1596            child_id = form['val_id']
1597        else:
1598            self.flash('No ticket selected.')
1599            self.redirect(self.url(self.context, '@@edit'))
1600            return
1601        if not isinstance(child_id, list):
1602            child_id = [child_id]
1603        deleted = []
1604        for id in child_id:
1605            # Students are not allowed to remove core tickets
1606            if not self.context[id].core_or_elective:
1607                try:
1608                    del self.context[id]
1609                    deleted.append(id)
1610                except:
1611                    self.flash('Could not delete %s: %s: %s' % (
1612                            id, sys.exc_info()[0], sys.exc_info()[1]))
1613        if len(deleted):
1614            self.flash('Successfully removed: %s' % ', '.join(deleted))
1615        self.redirect(self.url(self.context, u'@@edit'))
1616        return
1617
1618    @grok.action('Register course list')
1619    def register_courses(self, **data):
1620        state = IWorkflowState(self.context.getStudent()).getState()
1621        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1622        self.flash('Course list has been registered.')
1623        self.redirect(self.url(self.context))
1624        return
1625
1626class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1627    """Add a course ticket by student.
1628    """
1629    grok.name('ctadd')
1630    grok.require('waeup.handleStudent')
1631    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1632        'grade', 'score', 'core_or_elective', 'automatic')
1633
1634    @grok.action('Add course ticket')
1635    def addCourseTicket(self, **data):
1636        ticket = CourseTicket()
1637        course = data['course']
1638        ticket.automatic = False
1639        ticket.code = course.code
1640        ticket.title = course.title
1641        ticket.faculty = course.__parent__.__parent__.__parent__.title
1642        ticket.department = course.__parent__.__parent__.title
1643        ticket.credits = course.credits
1644        ticket.passmark = course.passmark
1645        ticket.semester = course.semester
1646        try:
1647            self.context.addCourseTicket(ticket)
1648        except KeyError:
1649            self.flash('The ticket exists.')
1650            return
1651        self.flash('Successfully added %s.' % ticket.code)
1652        self.redirect(self.url(self.context, u'@@edit'))
1653        return
Note: See TracBrowser for help on using the repository browser.