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

Last change on this file since 7026 was 7026, checked in by Henrik Bettermann, 14 years ago

Callback can't be applied twice.

  • Property svn:keywords set to Id
File size: 61.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.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        pay_details  = self.getPaymentDetails(
929            p_category,student)
930        if pay_details['error']:
931            self.flash(pay_details['error'])
932            self.redirect(self.url(self.context))
933            return
934        p_item = pay_details['p_item']
935        p_session = pay_details['p_session']
936        for key in self.context.keys():
937            ticket = self.context[key]
938            if ticket.p_category == p_category and \
939               ticket.p_item == p_item and \
940               ticket.p_session == p_session:
941                  self.flash(
942                      'This payment ticket already exists.')
943                  self.redirect(self.url(self.context))
944                  return
945        payment = createObject(u'waeup.StudentOnlinePayment')
946        self.applyData(payment, **data)
947        timestamp = "%d" % int(time()*1000)
948        #order_id = "%s%s" % (student_id[1:],timestamp)
949        payment.p_id = "p%s" % timestamp
950        payment.p_item = p_item
951        payment.p_session = p_session
952        payment.amount_auth = pay_details['amount']
953        payment.surcharge_1 = pay_details['surcharge_1']
954        payment.surcharge_2 = pay_details['surcharge_2']
955        payment.surcharge_3 = pay_details['surcharge_3']
956        self.context[payment.p_id] = payment
957        self.flash('Payment ticket created.')
958        self.redirect(self.url(self.context))
959        return
960
961class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
962    """ Page to view an online payment ticket
963    """
964    grok.context(IStudentOnlinePayment)
965    grok.name('index')
966    grok.require('waeup.viewStudent')
967    form_fields = grok.AutoFields(IStudentOnlinePayment)
968    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
969    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
970    pnav = 4
971
972    @property
973    def title(self):
974        return 'Online Payment Ticket %s' % self.context.p_id
975
976    @property
977    def label(self):
978        return '%s: Online Payment Ticket %s' % (
979            self.context.getStudent().fullname,self.context.p_id)
980
981class PaymentReceiptActionButton(ManageActionButton):
982    grok.context(IStudentOnlinePayment)
983    grok.require('waeup.viewStudent')
984    icon = 'actionicon_pdf.png'
985    text = 'Download payment receipt'
986    target = 'payment_receipt.pdf'
987
988class OnlinePaymentCallbackPage(grok.View):
989    """ Callback view
990    """
991    grok.context(IStudentOnlinePayment)
992    grok.name('callback')
993    grok.require('waeup.payStudent')
994
995    # This update method simulates a valid callback und must be
996    # specified in the customization package. The parameters must be taken
997    # from the incoming request.
998    def update(self):
999        if self.context.p_state == 'paid':
1000            self.flash('This ticket has already been paid.')
1001            return
1002        student = self.context.getStudent()
1003        write_log_message(self,'valid callback: %s' % self.context.p_id)
1004        self.context.r_amount_approved = self.context.amount_auth
1005        self.context.r_card_num = u'0000'
1006        self.context.r_code = u'00'
1007        self.context.p_state = 'paid'
1008        self.context.payment_date = datetime.now()
1009        if self.context.p_category == 'clearance':
1010            # Create CLR access code
1011            pin, error = create_accesscode('CLR',0,student.student_id)
1012            if error:
1013                self.flash('Valid callback received. ' + error)
1014                return
1015            self.context.ac = pin
1016        elif self.context.p_category == 'schoolfee':
1017            # Create SFE access code
1018            pin, error = create_accesscode('SFE',0,student.student_id)
1019            if error:
1020                self.flash('Valid callback received. ' + error)
1021                return
1022            self.context.ac = pin
1023        elif self.context.p_category == 'bed_allocation':
1024            # Create HOS access code
1025            pin, error = create_accesscode('HOS',0,student.student_id)
1026            if error:
1027                self.flash('Valid callback received. ' + error)
1028                return
1029            self.context.ac = pin
1030        self.flash('Valid callback received.')
1031        return
1032
1033    def render(self):
1034        self.redirect(self.url(self.context, '@@index'))
1035        return
1036
1037class ExportPDFPaymentSlipPage(grok.View):
1038    """Deliver a PDF slip of the context.
1039    """
1040    grok.context(IStudentOnlinePayment)
1041    grok.name('payment_receipt.pdf')
1042    grok.require('waeup.viewStudent')
1043    form_fields = grok.AutoFields(IStudentOnlinePayment)
1044    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1045    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1046    prefix = 'form'
1047
1048    @property
1049    def label(self):
1050        return 'Online Payment Receipt %s' % self.context.p_id
1051
1052    def render(self):
1053        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1054            self.request)
1055        return renderPDF(self,'Payment', 'payment_receipt.pdf',
1056            self.context.getStudent, studentview)
1057
1058# We don't need the display form page yet
1059#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1060#    """ Page to display the student accommodation data
1061#    """
1062#    grok.context(IStudentAccommodation)
1063#    grok.name('xxx')
1064#    grok.require('waeup.viewStudent')
1065#    form_fields = grok.AutoFields(IStudentAccommodation)
1066#    #grok.template('accommodationpage')
1067#    title = 'Accommodation'
1068#    pnav = 4
1069
1070#    @property
1071#    def label(self):
1072#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1073
1074# This manage form page is for both students and students officers.
1075class AccommodationManageFormPage(WAeUPEditFormPage):
1076    """ Page to manage bed tickets.
1077    """
1078    grok.context(IStudentAccommodation)
1079    grok.name('index')
1080    grok.require('waeup.handleStudent')
1081    form_fields = grok.AutoFields(IStudentAccommodation)
1082    grok.template('accommodationmanagepage')
1083    title = 'Accommodation'
1084    pnav = 4
1085    officers_only_actions = ['Remove selected']
1086
1087    def formatDatetime(self,datetimeobj):
1088        if isinstance(datetimeobj, datetime):
1089            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1090        else:
1091            return None
1092
1093    @property
1094    def label(self):
1095        return '%s: Accommodation' % self.context.__parent__.fullname
1096
1097    def update(self):
1098        super(AccommodationManageFormPage, self).update()
1099        datatable.need()
1100        return
1101
1102    @property
1103    def is_student(self):
1104        prm = get_principal_role_manager()
1105        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1106        return 'waeup.Student' in roles
1107
1108    @grok.action('Remove selected')
1109    def delBedTickets(self, **data):
1110        if self.is_student:
1111            self.flash('You are not allowed to remove bed tickets.')
1112            self.redirect(self.url(self.context))
1113            return
1114        form = self.request.form
1115        if form.has_key('val_id'):
1116            child_id = form['val_id']
1117        else:
1118            self.flash('No bed ticket selected.')
1119            self.redirect(self.url(self.context))
1120            return
1121        if not isinstance(child_id, list):
1122            child_id = [child_id]
1123        deleted = []
1124        for id in child_id:
1125            try:
1126                del self.context[id]
1127                deleted.append(id)
1128            except:
1129                self.flash('Could not delete %s: %s: %s' % (
1130                        id, sys.exc_info()[0], sys.exc_info()[1]))
1131        if len(deleted):
1132            self.flash('Successfully removed: %s' % ', '.join(deleted))
1133            write_log_message(self,'removed: % s' % ', '.join(deleted))
1134        self.redirect(self.url(self.context))
1135        return
1136
1137    @property
1138    def selected_actions(self):
1139        sa = self.actions
1140        if self.is_student:
1141            sa = [action for action in self.actions
1142                  if not action.label in self.officers_only_actions]
1143        return sa
1144
1145class AddBedTicketActionButton(ManageActionButton):
1146    grok.order(1)
1147    grok.context(IStudentAccommodation)
1148    grok.view(AccommodationManageFormPage)
1149    grok.require('waeup.handleStudent')
1150    icon = 'actionicon_home.png'
1151    text = 'Book accommodation'
1152    target = 'add'
1153
1154class BedTicketAddPage(WAeUPPage):
1155    """ Page to add an online payment ticket
1156    """
1157    grok.context(IStudentAccommodation)
1158    grok.name('add')
1159    grok.require('waeup.handleStudent')
1160    grok.template('enterpin')
1161    ac_prefix = 'HOS'
1162    label = 'Add bed ticket'
1163    title = 'Add bed ticket'
1164    pnav = 4
1165    buttonname = 'Create bed ticket'
1166    notice = ''
1167
1168    # To be sepezified in customization packages
1169    def getAccommodationDetails(self, student):
1170        return getAccommodationDetails(student)
1171
1172    # To be sepezified in customization packages
1173    def selectBed(self, available_beds):
1174        return selectBed(available_beds)
1175
1176    def update(self, SUBMIT=None):
1177        student = self.context.getStudent()
1178        acc_details  = self.getAccommodationDetails(student)
1179        if not student.state in acc_details['allowed_states']:
1180            self.flash("You are in the wrong registration state.")
1181            self.redirect(self.url(self.context))
1182            return
1183        if str(grok.getSite()[
1184                'configuration'].accommodation_session) in self.context.keys():
1185            self.flash('You already booked a bed space for the current accommodation session.')
1186            self.redirect(self.url(self.context))
1187            return
1188        self.ac_series = self.request.form.get('ac_series', None)
1189        self.ac_number = self.request.form.get('ac_number', None)
1190        if SUBMIT is None:
1191            return
1192        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1193        code = get_access_code(pin)
1194        if not code:
1195            self.flash('Activation code is invalid.')
1196            return
1197        # Search a bed and exit if no bed is found or if the student
1198        # has already booked a bed
1199        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1200        entries = cat.searchResults(
1201            bed_type=(acc_details['bt'],acc_details['bt']))
1202        available_beds = [
1203            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1204        if not available_beds:
1205            self.flash('There is no free bed in your category %s.'
1206                        % acc_details['bt'])
1207            return
1208        entries = cat.searchResults(
1209            owner=(student.student_id,student.student_id))
1210        # Cannot happen but anyway ...
1211        if len(entries):
1212            bed = [entry for entry in entries][0]
1213            self.flash('You already booked bed %s.'
1214                        % bed.bed_id)
1215            return
1216        # Mark pin as used (this also fires a pin related transition)
1217        if code.state == USED:
1218            self.flash('Activation code has already been used.')
1219            return
1220        else:
1221            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1222            # Here we know that the ac is in state initialized so we do not
1223            # expect an exception, but the owner might be different
1224            if not invalidate_accesscode(
1225                pin,comment,self.context.getStudent().student_id):
1226                self.flash('You are not the owner of this access code.')
1227                return
1228        # Create bed ticket and book bed
1229        bedticket = createObject(u'waeup.BedTicket')
1230        bedticket.booking_code = pin
1231        bedticket.booking_session = acc_details['booking_session']
1232        bedticket.bed_type = acc_details['bt']
1233        bed = self.selectBed(available_beds)
1234        bed.bookBed(student.student_id)
1235        bedticket.bed = bed
1236        hall_title = bed.__parent__.hostel_name
1237        coordinates = bed.getBedCoordinates()[1:]
1238        block, room_nr, bed_nr = coordinates
1239        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1240            hall_title, block, room_nr, bed_nr)
1241        key = str(acc_details['booking_session'])
1242        self.context[key] = bedticket
1243        self.flash('Bed ticket created and bed booked: %s'
1244            % bedticket.bed_coordinates)
1245        self.redirect(self.url(self.context))
1246        return
1247
1248class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1249    """ Page to display bed tickets
1250    """
1251    grok.context(IBedTicket)
1252    grok.name('index')
1253    grok.require('waeup.viewStudent')
1254    form_fields = grok.AutoFields(IBedTicket)
1255    form_fields[
1256        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1257    pnav = 4
1258
1259    @property
1260    def label(self):
1261        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1262
1263    @property
1264    def title(self):
1265        return 'Bed Ticket %s' % self.context.getSessionString()
1266
1267class RelocateStudentActionButton(ManageActionButton):
1268    grok.order(1)
1269    grok.context(IBedTicket)
1270    grok.view(BedTicketDisplayFormPage)
1271    grok.require('waeup.manageHostels')
1272    icon = 'actionicon_reload.png'
1273    text = 'Relocate student'
1274    target = 'relocate'
1275
1276class BedTicketRelocationPage(grok.View):
1277    """ Callback view
1278    """
1279    grok.context(IBedTicket)
1280    grok.name('relocate')
1281    grok.require('waeup.manageHostels')
1282
1283    # To be sepezified in customization packages
1284    def getAccommodationDetails(self, student):
1285        return getAccommodationDetails(student)
1286
1287    # To be sepezified in customization packages
1288    def selectBed(self, available_beds):
1289        return selectBed(available_beds)
1290
1291    # This update method simulates a valid callback und must be
1292    # specified in the customization package. The parameters must be taken
1293    # from the incoming request.
1294    def update(self):
1295        student = self.context.getStudent()
1296        acc_details  = self.getAccommodationDetails(student)
1297        if acc_details['bt'] == self.context.bed_type:
1298            self.flash("Bed category hasn't changed. Student can't be relocated.")
1299            return
1300        # Search a bed and exit if no bed is found or if the student
1301        # has already booked a bed
1302        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1303        entries = cat.searchResults(
1304            bed_type=(acc_details['bt'],acc_details['bt']))
1305        available_beds = [
1306            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1307        if not available_beds:
1308            self.flash('There is no free bed in your category %s.'
1309                        % acc_details['bt'])
1310            return
1311        entries = cat.searchResults(
1312            owner=(student.student_id,student.student_id))
1313        # Rlease old bed
1314        allocated_bed = self.context.bed
1315        allocated_bed.owner = NOT_OCCUPIED
1316        notify(grok.ObjectModifiedEvent(allocated_bed))
1317        # Alocate new bed
1318        self.context.bed_type = acc_details['bt']
1319        bed = self.selectBed(available_beds)
1320        bed.bookBed(student.student_id)
1321        self.context.bed = bed
1322        hall_title = bed.__parent__.hostel_name
1323        coordinates = bed.getBedCoordinates()[1:]
1324        block, room_nr, bed_nr = coordinates
1325        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1326            hall_title, block, room_nr, bed_nr)
1327        self.flash('Student relocated and new bed booked: %s'
1328            % self.context.bed_coordinates)
1329        self.redirect(self.url(self.context))
1330        return
1331
1332    def render(self):
1333        self.redirect(self.url(self.context, '@@index'))
1334        return
1335
1336class StudentHistoryPage(WAeUPPage):
1337    """ Page to display student clearance data
1338    """
1339    grok.context(IStudent)
1340    grok.name('history')
1341    grok.require('waeup.viewStudent')
1342    grok.template('studenthistory')
1343    title = 'History'
1344    pnav = 4
1345
1346    @property
1347    def label(self):
1348        return '%s: History' % self.context.fullname
1349
1350# Pages for students only
1351
1352class StudentBaseEditActionButton(ManageActionButton):
1353    grok.order(1)
1354    grok.context(IStudent)
1355    grok.view(StudentBaseDisplayFormPage)
1356    grok.require('waeup.handleStudent')
1357    text = 'Change password'
1358    target = 'bedit'
1359
1360class StudentPasswordSetting(grok.Adapter):
1361    """Adapt IStudent to data needed for password settings.
1362
1363    We provide password getters/setters for the attached context (an
1364    IStudent object) that cooperate seamless with the usual
1365    formlib/form techniques.
1366    """
1367    grok.context(IStudent)
1368    grok.provides(IStudentPasswordSetting)
1369
1370    def __init__(self, context):
1371        self.name = context.fullname
1372        self.password_repeat = context.password
1373        self.context = context
1374        return
1375
1376    def getPassword(self):
1377        return self.context.password
1378
1379    def setPassword(self, password):
1380        IUserAccount(self.context).setPassword(password)
1381        return
1382
1383    password = property(getPassword, setPassword)
1384
1385class StudentBaseEditFormPage(WAeUPEditFormPage):
1386    """ View to edit student base data by student
1387    """
1388    grok.context(IStudent)
1389    grok.name('bedit')
1390    grok.require('waeup.handleStudent')
1391    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1392    #    'student_id', 'reg_number', 'matric_number')
1393    form_fields = grok.AutoFields(IStudentPasswordSetting)
1394    grok.template('baseeditpage')
1395    label = 'Change password'
1396    title = 'Base Data'
1397    pnav = 4
1398
1399    def update(self):
1400        super(StudentBaseEditFormPage, self).update()
1401        self.wf_info = IWorkflowInfo(self.context)
1402        return
1403
1404    def onFailure(self, action, data, errors):
1405        new_status = []
1406        other_errors = False
1407        for error in errors:
1408            msg = getattr(error, 'message', '')
1409            if isinstance(msg, basestring) and msg != '':
1410                new_status.append(msg)
1411            else:
1412                other_errors = True
1413        if other_errors:
1414            if new_status:
1415                new_status.append('see below for further errors')
1416            else:
1417                new_status.append('See below for details.')
1418        if new_status:
1419            self.status = u'There were errors: %s' % ', '.join(new_status)
1420        return
1421
1422    @grok.action('Save', failure=onFailure)
1423    def save(self, **data):
1424        self.applyData(self.context, **data)
1425        self.flash('Form has been saved.')
1426        return
1427
1428class StudentClearanceStartActionButton(ManageActionButton):
1429    grok.order(1)
1430    grok.context(IStudent)
1431    grok.view(StudentClearanceDisplayFormPage)
1432    grok.require('waeup.handleStudent')
1433    icon = 'actionicon_start.png'
1434    text = 'Start clearance'
1435    target = 'start_clearance'
1436
1437    @property
1438    def target_url(self):
1439        if self.context.state != 'admitted':
1440            return ''
1441        return self.view.url(self.view.context, self.target)
1442
1443class StartClearancePage(WAeUPPage):
1444    grok.context(IStudent)
1445    grok.name('start_clearance')
1446    grok.require('waeup.handleStudent')
1447    grok.template('enterpin')
1448    title = 'Start clearance'
1449    label = 'Start clearance'
1450    ac_prefix = 'CLR'
1451    notice = ''
1452    pnav = 4
1453    buttonname = 'Start clearance now'
1454
1455    def update(self, SUBMIT=None):
1456        if not self.context.state == 'admitted':
1457            self.flash("Wrong state.")
1458            self.redirect(self.url(self.context))
1459            return
1460        self.ac_series = self.request.form.get('ac_series', None)
1461        self.ac_number = self.request.form.get('ac_number', None)
1462
1463        if SUBMIT is None:
1464            return
1465        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1466        code = get_access_code(pin)
1467        if not code:
1468            self.flash('Activation code is invalid.')
1469            return
1470        # Mark pin as used (this also fires a pin related transition)
1471        # and fire transition start_clearance
1472        if code.state == USED:
1473            self.flash('Activation code has already been used.')
1474            return
1475        else:
1476            comment = u"AC invalidated for %s" % self.context.student_id
1477            # Here we know that the ac is in state initialized so we do not
1478            # expect an exception, but the owner might be different
1479            if not invalidate_accesscode(pin,comment,self.context.student_id):
1480                self.flash('You are not the owner of this access code.')
1481                return
1482            self.context.clr_code = pin
1483        IWorkflowInfo(self.context).fireTransition('start_clearance')
1484        self.flash('Clearance process has been started.')
1485        self.redirect(self.url(self.context,'cedit'))
1486        return
1487
1488class StudentClearanceEditActionButton(ManageActionButton):
1489    grok.order(1)
1490    grok.context(IStudent)
1491    grok.view(StudentClearanceDisplayFormPage)
1492    grok.require('waeup.handleStudent')
1493    text = 'Edit'
1494    target = 'cedit'
1495
1496    @property
1497    def target_url(self):
1498        if self.context.clearance_locked:
1499            return ''
1500        return self.view.url(self.view.context, self.target)
1501
1502class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1503    """ View to edit student clearance data by student
1504    """
1505    grok.context(IStudent)
1506    grok.name('cedit')
1507    grok.require('waeup.handleStudent')
1508    form_fields = grok.AutoFields(
1509        IStudentClearanceEdit).omit('clearance_locked')
1510    label = 'Edit clearance data'
1511    title = 'Clearance Data'
1512    pnav = 4
1513    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1514
1515    def emitLockMessage(self):
1516        self.flash('The requested form is locked (read-only).')
1517        self.redirect(self.url(self.context))
1518        return
1519
1520    def update(self):
1521        if self.context.clearance_locked:
1522            self.emitLockMessage()
1523            return
1524        datepicker.need()
1525        return super(StudentClearanceEditFormPage, self).update()
1526
1527    @grok.action('Save')
1528    def save(self, **data):
1529        self.applyData(self.context, **data)
1530        self.flash('Clearance form has been saved.')
1531        return
1532
1533    @grok.action('Save and request clearance')
1534    def requestclearance(self, **data):
1535        self.applyData(self.context, **data)
1536        self.context._p_changed = True
1537        #if self.dataNotComplete():
1538        #    self.flash(self.dataNotComplete())
1539        #    return
1540        self.flash('Clearance form has been saved.')
1541        self.redirect(self.url(self.context,'request_clearance'))
1542        return
1543
1544class RequestClearancePage(WAeUPPage):
1545    grok.context(IStudent)
1546    grok.name('request_clearance')
1547    grok.require('waeup.handleStudent')
1548    grok.template('enterpin')
1549    title = 'Request clearance'
1550    label = 'Request clearance'
1551    notice = 'Enter the CLR access code used for starting clearance.'
1552    ac_prefix = 'CLR'
1553    pnav = 4
1554    buttonname = 'Request clearance now'
1555
1556    def update(self, SUBMIT=None):
1557        self.ac_series = self.request.form.get('ac_series', None)
1558        self.ac_number = self.request.form.get('ac_number', None)
1559        if SUBMIT is None:
1560            return
1561        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1562        if self.context.clr_code != pin:
1563            self.flash("This isn't your CLR access code.")
1564            return
1565        state = IWorkflowState(self.context).getState()
1566        # This shouldn't happen, but the application officer
1567        # might have forgotten to lock the form after changing the state
1568        if state != CLEARANCE:
1569            self.flash('This form cannot be submitted. Wrong state!')
1570            return
1571        IWorkflowInfo(self.context).fireTransition('request_clearance')
1572        self.flash('Clearance has been requested.')
1573        self.redirect(self.url(self.context))
1574        return
1575
1576class CourseRegistrationStartActionButton(ManageActionButton):
1577    grok.order(1)
1578    grok.context(IStudentStudyCourse)
1579    grok.view(StudyCourseDisplayFormPage)
1580    grok.require('waeup.handleStudent')
1581    icon = 'actionicon_start.png'
1582    text = 'Start course registration'
1583    target = 'start_course_registration'
1584
1585    @property
1586    def target_url(self):
1587        if not self.context.getStudent().state in (CLEARED,RETURNING):
1588            return ''
1589        return self.view.url(self.view.context, self.target)
1590
1591class StartCourseRegistrationPage(WAeUPPage):
1592    grok.context(IStudentStudyCourse)
1593    grok.name('start_course_registration')
1594    grok.require('waeup.handleStudent')
1595    grok.template('enterpin')
1596    title = 'Start course registration'
1597    label = 'Start course registration'
1598    ac_prefix = 'SFE'
1599    notice = ''
1600    pnav = 4
1601    buttonname = 'Start course registration now'
1602
1603    def update(self, SUBMIT=None):
1604        if not self.context.getStudent().state in (CLEARED,RETURNING):
1605            self.flash("Wrong state.")
1606            self.redirect(self.url(self.context))
1607            return
1608        self.ac_series = self.request.form.get('ac_series', None)
1609        self.ac_number = self.request.form.get('ac_number', None)
1610
1611        if SUBMIT is None:
1612            return
1613        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1614        code = get_access_code(pin)
1615        if not code:
1616            self.flash('Activation code is invalid.')
1617            return
1618        # Mark pin as used (this also fires a pin related transition)
1619        # and fire transition start_clearance
1620        if code.state == USED:
1621            self.flash('Activation code has already been used.')
1622            return
1623        else:
1624            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1625            # Here we know that the ac is in state initialized so we do not
1626            # expect an exception, but the owner might be different
1627            if not invalidate_accesscode(
1628                pin,comment,self.context.getStudent().student_id):
1629                self.flash('You are not the owner of this access code.')
1630                return
1631        if self.context.getStudent().state == CLEARED:
1632            IWorkflowInfo(self.context.getStudent()).fireTransition(
1633                'pay_first_school_fee')
1634        elif self.context.getStudent().state == RETURNING:
1635            IWorkflowInfo(self.context.getStudent()).fireTransition(
1636                'pay_school_fee')
1637        self.flash('Course registration has been started.')
1638        self.redirect(self.url(self.context))
1639        return
1640
1641
1642class AddStudyLevelActionButton(AddActionButton):
1643    grok.order(1)
1644    grok.context(IStudentStudyCourse)
1645    grok.view(StudyCourseDisplayFormPage)
1646    grok.require('waeup.handleStudent')
1647    text = 'Add course list'
1648    target = 'add'
1649
1650    @property
1651    def target_url(self):
1652        student = self.view.context.getStudent()
1653        condition1 = student.state != 'school fee paid'
1654        condition2 = str(student['studycourse'].current_level) in \
1655            self.view.context.keys()
1656        if condition1 or condition2:
1657            return ''
1658        return self.view.url(self.view.context, self.target)
1659
1660class AddStudyLevelFormPage(WAeUPEditFormPage):
1661    """ Page for students to add current study levels
1662    """
1663    grok.context(IStudentStudyCourse)
1664    grok.name('add')
1665    grok.require('waeup.handleStudent')
1666    grok.template('studyleveladdpage')
1667    form_fields = grok.AutoFields(IStudentStudyCourse)
1668    title = 'Study Course'
1669    pnav = 4
1670
1671    @property
1672    def label(self):
1673        studylevelsource = StudyLevelSource().factory
1674        code = self.context.current_level
1675        title = studylevelsource.getTitle(self.context, code)
1676        return 'Add current level %s' % title
1677
1678    def emitLockMessage(self):
1679        self.flash('The requested form is locked (read-only).')
1680        self.redirect(self.url(self.context))
1681        return
1682
1683    def update(self):
1684        if self.context.getStudent().state != 'school fee paid':
1685            self.emitLockMessage()
1686            return
1687        super(AddStudyLevelFormPage, self).update()
1688        return
1689
1690    @grok.action('Create course list now')
1691    def addStudyLevel(self, **data):
1692        studylevel = StudentStudyLevel()
1693        studylevel.level = self.context.current_level
1694        studylevel.level_session = self.context.current_session
1695        try:
1696            self.context.addStudentStudyLevel(
1697                self.context.certificate,studylevel)
1698        except KeyError:
1699            self.flash('This level exists.')
1700        self.redirect(self.url(self.context))
1701        return
1702
1703class StudyLevelEditActionButton(ManageActionButton):
1704    grok.order(1)
1705    grok.context(IStudentStudyLevel)
1706    grok.view(StudyLevelDisplayFormPage)
1707    grok.require('waeup.handleStudent')
1708    text = 'Add and remove courses'
1709    target = 'edit'
1710
1711    @property
1712    def target_url(self):
1713        student = self.view.context.getStudent()
1714        condition1 = student.state != 'school fee paid'
1715        condition2 = student[
1716            'studycourse'].current_level != self.view.context.level
1717        if condition1 or condition2:
1718            return ''
1719        return self.view.url(self.view.context, self.target)
1720
1721class StudyLevelEditFormPage(WAeUPEditFormPage):
1722    """ Page to edit the student study level data by students
1723    """
1724    grok.context(IStudentStudyLevel)
1725    grok.name('edit')
1726    grok.require('waeup.handleStudent')
1727    grok.template('studyleveleditpage')
1728    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1729        'level_session', 'level_verdict')
1730    pnav = 4
1731
1732    def update(self):
1733        super(StudyLevelEditFormPage, self).update()
1734    #    tabs.need()
1735        datatable.need()
1736        return
1737
1738    @property
1739    def title(self):
1740        return 'Study Level %s' % self.context.level_title
1741
1742    @property
1743    def label(self):
1744        return 'Add and remove course tickets of study level %s' % self.context.level_title
1745
1746    @property
1747    def total_credits(self):
1748        total_credits = 0
1749        for key, val in self.context.items():
1750            total_credits += val.credits
1751        return total_credits
1752
1753    @grok.action('Add course ticket')
1754    def addCourseTicket(self, **data):
1755        self.redirect(self.url(self.context, 'ctadd'))
1756
1757    @grok.action('Remove selected tickets')
1758    def delCourseTicket(self, **data):
1759        form = self.request.form
1760        if form.has_key('val_id'):
1761            child_id = form['val_id']
1762        else:
1763            self.flash('No ticket selected.')
1764            self.redirect(self.url(self.context, '@@edit'))
1765            return
1766        if not isinstance(child_id, list):
1767            child_id = [child_id]
1768        deleted = []
1769        for id in child_id:
1770            # Students are not allowed to remove core tickets
1771            if not self.context[id].core_or_elective:
1772                try:
1773                    del self.context[id]
1774                    deleted.append(id)
1775                except:
1776                    self.flash('Could not delete %s: %s: %s' % (
1777                            id, sys.exc_info()[0], sys.exc_info()[1]))
1778        if len(deleted):
1779            self.flash('Successfully removed: %s' % ', '.join(deleted))
1780        self.redirect(self.url(self.context, u'@@edit'))
1781        return
1782
1783    @grok.action('Register course list')
1784    def register_courses(self, **data):
1785        state = IWorkflowState(self.context.getStudent()).getState()
1786        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1787        self.flash('Course list has been registered.')
1788        self.redirect(self.url(self.context))
1789        return
1790
1791class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1792    """Add a course ticket by student.
1793    """
1794    grok.name('ctadd')
1795    grok.require('waeup.handleStudent')
1796    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1797        'grade', 'score', 'core_or_elective', 'automatic')
1798
1799    @grok.action('Add course ticket')
1800    def addCourseTicket(self, **data):
1801        ticket = CourseTicket()
1802        course = data['course']
1803        ticket.automatic = False
1804        ticket.code = course.code
1805        ticket.title = course.title
1806        ticket.faculty = course.__parent__.__parent__.__parent__.title
1807        ticket.department = course.__parent__.__parent__.title
1808        ticket.credits = course.credits
1809        ticket.passmark = course.passmark
1810        ticket.semester = course.semester
1811        try:
1812            self.context.addCourseTicket(ticket)
1813        except KeyError:
1814            self.flash('The ticket exists.')
1815            return
1816        self.flash('Successfully added %s.' % ticket.code)
1817        self.redirect(self.url(self.context, u'@@edit'))
1818        return
Note: See TracBrowser for help on using the repository browser.