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

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

Students are only allowed to create bed allocation payment tickets if their current session matches the accommodation session.

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