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

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

Filter actions if students are using the AccommodationManageFormPage?.

I first tried to use two different pages, one for officers and one for students, and use different targets in breadcrumbs. But this was much more complicated.

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