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

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

Much more logic for bed allocation, bed release.

Implement student relocation.

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