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

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

Implement realocation page.

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