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

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

Implement StudentFileNameChooser? and StudentFileStoreHandler?.

Define viewlet manager FileManager? in viewlets.py and define display and upload viewlets for the StudentClearanceDisplayFormPage?.

Also define image views. Since these views are part of the viewlets they are defined also in the viewlets module. In browser.py we only add an empty files_changed list attribute to the StudentClearanceManageFormPage?. This will be needed for logging (to be done).

  • Property svn:keywords set to Id
File size: 65.5 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    files_changed = []
473
474    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
475
476    def update(self):
477        datepicker.need() # Enable jQuery datepicker in date fields.
478        return super(StudentClearanceManageFormPage, self).update()
479
480    @grok.action('Save')
481    def save(self, **data):
482        msave(self, **data)
483        return
484
485class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
486    """ Page to display student personal data
487    """
488    grok.context(IStudent)
489    grok.name('view_personal')
490    grok.require('waeup.viewStudent')
491    form_fields = grok.AutoFields(IStudentPersonal)
492    title = 'Personal Data'
493    pnav = 4
494
495    @property
496    def label(self):
497        return '%s: Personal Data' % self.context.fullname
498
499class StudentPersonalManageActionButton(ManageActionButton):
500    grok.order(1)
501    grok.context(IStudent)
502    grok.view(StudentPersonalDisplayFormPage)
503    grok.require('waeup.manageStudents')
504    text = 'Manage'
505    target = 'edit_personal'
506
507class StudentPersonalManageFormPage(WAeUPEditFormPage):
508    """ Page to edit student clearance data
509    """
510    grok.context(IStudent)
511    grok.name('edit_personal')
512    grok.require('waeup.viewStudent')
513    form_fields = grok.AutoFields(IStudentPersonal)
514    label = 'Manage personal data'
515    title = 'Personal Data'
516    pnav = 4
517
518    @grok.action('Save')
519    def save(self, **data):
520        msave(self, **data)
521        return
522
523class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
524    """ Page to display the student study course data
525    """
526    grok.context(IStudentStudyCourse)
527    grok.name('index')
528    grok.require('waeup.viewStudent')
529    form_fields = grok.AutoFields(IStudentStudyCourse)
530    grok.template('studycoursepage')
531    title = 'Study Course'
532    pnav = 4
533
534    @property
535    def label(self):
536        return '%s: Study Course' % self.context.__parent__.fullname
537
538    @property
539    def current_mode(self):
540        return study_modes.getTermByToken(
541            self.context.certificate.study_mode).title
542
543class StudyCourseManageActionButton(ManageActionButton):
544    grok.order(1)
545    grok.context(IStudentStudyCourse)
546    grok.view(StudyCourseDisplayFormPage)
547    grok.require('waeup.manageStudents')
548    text = 'Manage'
549    target = 'manage'
550
551class StudyCourseManageFormPage(WAeUPEditFormPage):
552    """ Page to edit the student study course data
553    """
554    grok.context(IStudentStudyCourse)
555    grok.name('manage')
556    grok.require('waeup.manageStudents')
557    grok.template('studycoursemanagepage')
558    form_fields = grok.AutoFields(IStudentStudyCourse)
559    title = 'Study Course'
560    label = 'Manage study course'
561    pnav = 4
562    taboneactions = ['Save','Cancel']
563    tabtwoactions = ['Remove selected levels','Cancel']
564    tabthreeactions = ['Add study level']
565
566    def update(self):
567        super(StudyCourseManageFormPage, self).update()
568        tabs.need()
569        datatable.need()
570        return
571
572    @grok.action('Save')
573    def save(self, **data):
574        msave(self, **data)
575        return
576
577    @property
578    def level_dict(self):
579        studylevelsource = StudyLevelSource().factory
580        for code in studylevelsource.getValues(self.context):
581            title = studylevelsource.getTitle(self.context, code)
582            yield(dict(code=code, title=title))
583
584    @grok.action('Add study level')
585    def addStudyLevel(self, **data):
586        level_code = self.request.form.get('addlevel', None)
587        studylevel = StudentStudyLevel()
588        studylevel.level = int(level_code)
589        try:
590            self.context.addStudentStudyLevel(
591                self.context.certificate,studylevel)
592        except KeyError:
593            self.flash('This level exists.')
594        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
595        return
596
597    @grok.action('Remove selected levels')
598    def delStudyLevels(self, **data):
599        form = self.request.form
600        if form.has_key('val_id'):
601            child_id = form['val_id']
602        else:
603            self.flash('No study level selected.')
604            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
605            return
606        if not isinstance(child_id, list):
607            child_id = [child_id]
608        deleted = []
609        for id in child_id:
610            try:
611                del self.context[id]
612                deleted.append(id)
613            except:
614                self.flash('Could not delete %s: %s: %s' % (
615                        id, sys.exc_info()[0], sys.exc_info()[1]))
616        if len(deleted):
617            self.flash('Successfully removed: %s' % ', '.join(deleted))
618        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
619        return
620
621class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
622    """ Page to display student study levels
623    """
624    grok.context(IStudentStudyLevel)
625    grok.name('index')
626    grok.require('waeup.viewStudent')
627    form_fields = grok.AutoFields(IStudentStudyLevel)
628    grok.template('studylevelpage')
629    pnav = 4
630
631    @property
632    def title(self):
633        return 'Study Level %s' % self.context.level_title
634
635    @property
636    def label(self):
637        return '%s: Study Level %s' % (
638            self.context.getStudent().fullname,self.context.level_title)
639
640    @property
641    def total_credits(self):
642        total_credits = 0
643        for key, val in self.context.items():
644            total_credits += val.credits
645        return total_credits
646
647class CourseRegistrationSlipActionButton(ManageActionButton):
648    grok.order(1)
649    grok.context(IStudentStudyLevel)
650    grok.view(StudyLevelDisplayFormPage)
651    grok.require('waeup.viewStudent')
652    icon = 'actionicon_pdf.png'
653    text = 'Download course registration slip'
654    target = 'course_registration.pdf'
655
656class ExportPDFCourseRegistrationSlipPage(grok.View):
657    """Deliver a PDF slip of the context.
658    """
659    grok.context(IStudentStudyLevel)
660    grok.name('course_registration.pdf')
661    grok.require('waeup.viewStudent')
662    form_fields = grok.AutoFields(IStudentStudyLevel)
663    prefix = 'form'
664
665    @property
666    def label(self):
667        return 'Course Registration Slip %s' % self.context.level_title
668
669    def render(self):
670        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
671            self.request)
672        return renderPDF(self,'Course Registration', 'course_registration.pdf',
673            self.context.getStudent, studentview)
674
675class StudyLevelManageActionButton(ManageActionButton):
676    grok.order(2)
677    grok.context(IStudentStudyLevel)
678    grok.view(StudyLevelDisplayFormPage)
679    grok.require('waeup.manageStudents')
680    text = 'Manage'
681    target = 'manage'
682
683class StudyLevelManageFormPage(WAeUPEditFormPage):
684    """ Page to edit the student study level data
685    """
686    grok.context(IStudentStudyLevel)
687    grok.name('manage')
688    grok.require('waeup.manageStudents')
689    grok.template('studylevelmanagepage')
690    form_fields = grok.AutoFields(IStudentStudyLevel)
691    pnav = 4
692    taboneactions = ['Save','Cancel']
693    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
694
695    def update(self):
696        super(StudyLevelManageFormPage, self).update()
697        tabs.need()
698        datatable.need()
699        return
700
701    @property
702    def title(self):
703        return 'Study Level %s' % self.context.level_title
704
705    @property
706    def label(self):
707        return 'Manage study level %s' % self.context.level_title
708
709    @grok.action('Save')
710    def save(self, **data):
711        msave(self, **data)
712        return
713
714    @grok.action('Add course ticket')
715    def addCourseTicket(self, **data):
716        self.redirect(self.url(self.context, '@@add'))
717
718    @grok.action('Remove selected tickets')
719    def delCourseTicket(self, **data):
720        form = self.request.form
721        if form.has_key('val_id'):
722            child_id = form['val_id']
723        else:
724            self.flash('No ticket selected.')
725            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
726            return
727        if not isinstance(child_id, list):
728            child_id = [child_id]
729        deleted = []
730        for id in child_id:
731            try:
732                del self.context[id]
733                deleted.append(id)
734            except:
735                self.flash('Could not delete %s: %s: %s' % (
736                        id, sys.exc_info()[0], sys.exc_info()[1]))
737        if len(deleted):
738            self.flash('Successfully removed: %s' % ', '.join(deleted))
739        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
740        return
741
742class CourseTicketAddFormPage(WAeUPAddFormPage):
743    """Add a course ticket.
744    """
745    grok.context(IStudentStudyLevel)
746    grok.name('add')
747    grok.require('waeup.manageStudents')
748    label = 'Add course ticket'
749    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
750        'grade', 'score', 'automatic')
751    pnav = 4
752
753    @property
754    def title(self):
755        return 'Study Level %s' % self.context.level_title
756
757    @grok.action('Add course ticket')
758    def addCourseTicket(self, **data):
759        ticket = CourseTicket()
760        course = data['course']
761        ticket.core_or_elective = data['core_or_elective']
762        ticket.automatic = False
763        ticket.code = course.code
764        ticket.title = course.title
765        ticket.faculty = course.__parent__.__parent__.__parent__.title
766        ticket.department = course.__parent__.__parent__.title
767        ticket.credits = course.credits
768        ticket.passmark = course.passmark
769        ticket.semester = course.semester
770        try:
771            self.context.addCourseTicket(ticket)
772        except KeyError:
773            self.flash('The ticket exists.')
774            return
775        self.flash('Successfully added %s.' % ticket.code)
776        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
777        return
778
779    @grok.action('Cancel')
780    def cancel(self, **data):
781        self.redirect(self.url(self.context))
782
783class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
784    """ Page to display course tickets
785    """
786    grok.context(ICourseTicket)
787    grok.name('index')
788    grok.require('waeup.viewStudent')
789    form_fields = grok.AutoFields(ICourseTicket)
790    grok.template('courseticketpage')
791    pnav = 4
792
793    @property
794    def title(self):
795        return 'Course Ticket %s' % self.context.code
796
797    @property
798    def label(self):
799        return '%s: Course Ticket %s' % (
800            self.context.getStudent().fullname,self.context.code)
801
802class CourseTicketManageActionButton(ManageActionButton):
803    grok.order(1)
804    grok.context(ICourseTicket)
805    grok.view(CourseTicketDisplayFormPage)
806    grok.require('waeup.manageStudents')
807    text = 'Manage'
808    target = 'manage'
809
810class CourseTicketManageFormPage(WAeUPEditFormPage):
811    """ Page to manage course tickets
812    """
813    grok.context(ICourseTicket)
814    grok.name('manage')
815    grok.require('waeup.manageStudents')
816    form_fields = grok.AutoFields(ICourseTicket)
817    grok.template('courseticketmanagepage')
818    pnav = 4
819
820    @property
821    def title(self):
822        return 'Course Ticket %s' % self.context.code
823
824    @property
825    def label(self):
826        return 'Manage course ticket %s' % self.context.code
827
828    @grok.action('Save')
829    def save(self, **data):
830        msave(self, **data)
831        return
832
833# We don't need the display form page yet
834#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
835#    """ Page to display the student payments
836#    """
837#    grok.context(IStudentPaymentsContainer)
838#    grok.name('view')
839#    grok.require('waeup.viewStudent')
840#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
841#    grok.template('paymentspage')
842#    title = 'Payments'
843#    pnav = 4
844
845#    def formatDatetime(self,datetimeobj):
846#        if isinstance(datetimeobj, datetime):
847#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
848#        else:
849#            return None
850
851#    @property
852#    def label(self):
853#        return '%s: Payments' % self.context.__parent__.fullname
854
855#    def update(self):
856#        super(PaymentsDisplayFormPage, self).update()
857#        datatable.need()
858#        return
859
860# This manage form page is for both students and students officers.
861class PaymentsManageFormPage(WAeUPEditFormPage):
862    """ Page to manage the student payments
863    """
864    grok.context(IStudentPaymentsContainer)
865    grok.name('index')
866    grok.require('waeup.handleStudent')
867    form_fields = grok.AutoFields(IStudentPaymentsContainer)
868    grok.template('paymentsmanagepage')
869    title = 'Payments'
870    pnav = 4
871
872    def unremovable(self, ticket):
873        prm = get_principal_role_manager()
874        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
875        return ('waeup.Student' in roles and ticket.r_code)
876
877    def formatDatetime(self,datetimeobj):
878        if isinstance(datetimeobj, datetime):
879            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
880        else:
881            return None
882
883    @property
884    def label(self):
885        return '%s: Payments' % self.context.__parent__.fullname
886
887    def update(self):
888        super(PaymentsManageFormPage, self).update()
889        datatable.need()
890        return
891
892    @grok.action('Remove selected tickets')
893    def delPaymentTicket(self, **data):
894        form = self.request.form
895        if form.has_key('val_id'):
896            child_id = form['val_id']
897        else:
898            self.flash('No payment selected.')
899            self.redirect(self.url(self.context))
900            return
901        if not isinstance(child_id, list):
902            child_id = [child_id]
903        deleted = []
904        for id in child_id:
905            # Students are not allowed to remove used payment tickets
906            if not self.unremovable(self.context[id]):
907                try:
908                    del self.context[id]
909                    deleted.append(id)
910                except:
911                    self.flash('Could not delete %s: %s: %s' % (
912                            id, sys.exc_info()[0], sys.exc_info()[1]))
913        if len(deleted):
914            self.flash('Successfully removed: %s' % ', '.join(deleted))
915            write_log_message(self,'removed: % s' % ', '.join(deleted))
916        self.redirect(self.url(self.context))
917        return
918
919    @grok.action('Add online payment ticket')
920    def addPaymentTicket(self, **data):
921        self.redirect(self.url(self.context, '@@addop'))
922
923#class OnlinePaymentManageActionButton(ManageActionButton):
924#    grok.order(1)
925#    grok.context(IStudentPaymentsContainer)
926#    grok.view(PaymentsDisplayFormPage)
927#    grok.require('waeup.manageStudents')
928#    text = 'Manage payments'
929#    target = 'manage'
930
931class OnlinePaymentAddFormPage(WAeUPAddFormPage):
932    """ Page to add an online payment ticket
933    """
934    grok.context(IStudentPaymentsContainer)
935    grok.name('addop')
936    grok.require('waeup.handleStudent')
937    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
938        'p_category')
939    #zzgrok.template('addpaymentpage')
940    label = 'Add online payment'
941    title = 'Payments'
942    pnav = 4
943   
944    # To be sepezified in customization packages
945    def getPaymentDetails(self, category, student):
946        return getPaymentDetails(category, student)
947
948    @grok.action('Create ticket')
949    def createTicket(self, **data):
950        p_category = data['p_category']
951        student = self.context.__parent__
952        if p_category == 'bed_allocation' and student[
953            'studycourse'].current_session != grok.getSite()[
954            'configuration'].accommodation_session:
955                self.flash(
956                    'Your current session does not match accommodation session.')
957                self.redirect(self.url(self.context))
958                return
959        pay_details  = self.getPaymentDetails(
960            p_category,student)
961        if pay_details['error']:
962            self.flash(pay_details['error'])
963            self.redirect(self.url(self.context))
964            return
965        p_item = pay_details['p_item']
966        p_session = pay_details['p_session']
967        for key in self.context.keys():
968            ticket = self.context[key]
969            if ticket.p_category == p_category and \
970               ticket.p_item == p_item and \
971               ticket.p_session == p_session:
972                  self.flash(
973                      'This payment ticket already exists.')
974                  self.redirect(self.url(self.context))
975                  return
976        payment = createObject(u'waeup.StudentOnlinePayment')
977        self.applyData(payment, **data)
978        timestamp = "%d" % int(time()*1000)
979        #order_id = "%s%s" % (student_id[1:],timestamp)
980        payment.p_id = "p%s" % timestamp
981        payment.p_item = p_item
982        payment.p_session = p_session
983        payment.amount_auth = pay_details['amount']
984        payment.surcharge_1 = pay_details['surcharge_1']
985        payment.surcharge_2 = pay_details['surcharge_2']
986        payment.surcharge_3 = pay_details['surcharge_3']
987        self.context[payment.p_id] = payment
988        self.flash('Payment ticket created.')
989        self.redirect(self.url(self.context))
990        return
991
992class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
993    """ Page to view an online payment ticket
994    """
995    grok.context(IStudentOnlinePayment)
996    grok.name('index')
997    grok.require('waeup.viewStudent')
998    form_fields = grok.AutoFields(IStudentOnlinePayment)
999    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1000    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1001    pnav = 4
1002
1003    @property
1004    def title(self):
1005        return 'Online Payment Ticket %s' % self.context.p_id
1006
1007    @property
1008    def label(self):
1009        return '%s: Online Payment Ticket %s' % (
1010            self.context.getStudent().fullname,self.context.p_id)
1011
1012class PaymentReceiptActionButton(ManageActionButton):
1013    grok.order(1)
1014    grok.context(IStudentOnlinePayment)
1015    grok.view(OnlinePaymentDisplayFormPage)
1016    grok.require('waeup.viewStudent')
1017    icon = 'actionicon_pdf.png'
1018    text = 'Download payment receipt'
1019    target = 'payment_receipt.pdf'
1020
1021    @property
1022    def target_url(self):
1023        if self.context.p_state != 'paid':
1024            return ''
1025        return self.view.url(self.view.context, self.target)
1026
1027class RequestCallbackActionButton(ManageActionButton):
1028    grok.order(2)
1029    grok.context(IStudentOnlinePayment)
1030    grok.view(OnlinePaymentDisplayFormPage)
1031    grok.require('waeup.handleStudent')
1032    icon = 'actionicon_call.png'
1033    text = 'Request callback'
1034    target = 'callback'
1035
1036    @property
1037    def target_url(self):
1038        if self.context.p_state != 'unpaid':
1039            return ''
1040        return self.view.url(self.view.context, self.target)
1041
1042class OnlinePaymentCallbackPage(grok.View):
1043    """ Callback view
1044    """
1045    grok.context(IStudentOnlinePayment)
1046    grok.name('callback')
1047    grok.require('waeup.payStudent')
1048
1049    # This update method simulates a valid callback und must be
1050    # specified in the customization package. The parameters must be taken
1051    # from the incoming request.
1052    def update(self):
1053        if self.context.p_state == 'paid':
1054            self.flash('This ticket has already been paid.')
1055            return
1056        student = self.context.getStudent()
1057        write_log_message(self,'valid callback: %s' % self.context.p_id)
1058        self.context.r_amount_approved = self.context.amount_auth
1059        self.context.r_card_num = u'0000'
1060        self.context.r_code = u'00'
1061        self.context.p_state = 'paid'
1062        self.context.payment_date = datetime.now()
1063        if self.context.p_category == 'clearance':
1064            # Create CLR access code
1065            pin, error = create_accesscode('CLR',0,student.student_id)
1066            if error:
1067                self.flash('Valid callback received. ' + error)
1068                return
1069            self.context.ac = pin
1070        elif self.context.p_category == 'schoolfee':
1071            # Create SFE access code
1072            pin, error = create_accesscode('SFE',0,student.student_id)
1073            if error:
1074                self.flash('Valid callback received. ' + error)
1075                return
1076            self.context.ac = pin
1077        elif self.context.p_category == 'bed_allocation':
1078            # Create HOS access code
1079            pin, error = create_accesscode('HOS',0,student.student_id)
1080            if error:
1081                self.flash('Valid callback received. ' + error)
1082                return
1083            self.context.ac = pin
1084        self.flash('Valid callback received.')
1085        return
1086
1087    def render(self):
1088        self.redirect(self.url(self.context, '@@index'))
1089        return
1090
1091class ExportPDFPaymentSlipPage(grok.View):
1092    """Deliver a PDF slip of the context.
1093    """
1094    grok.context(IStudentOnlinePayment)
1095    grok.name('payment_receipt.pdf')
1096    grok.require('waeup.viewStudent')
1097    form_fields = grok.AutoFields(IStudentOnlinePayment)
1098    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1099    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1100    prefix = 'form'
1101
1102    @property
1103    def label(self):
1104        return 'Online Payment Receipt %s' % self.context.p_id
1105
1106    def render(self):
1107        if self.context.p_state != 'paid':
1108            self.flash('Ticket not yet paid.')
1109            self.redirect(self.url(self.context))
1110            return
1111        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1112            self.request)
1113        return renderPDF(self,'Payment', 'payment_receipt.pdf',
1114            self.context.getStudent, studentview)
1115
1116# We don't need the display form page yet
1117#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1118#    """ Page to display the student accommodation data
1119#    """
1120#    grok.context(IStudentAccommodation)
1121#    grok.name('xxx')
1122#    grok.require('waeup.viewStudent')
1123#    form_fields = grok.AutoFields(IStudentAccommodation)
1124#    #grok.template('accommodationpage')
1125#    title = 'Accommodation'
1126#    pnav = 4
1127
1128#    @property
1129#    def label(self):
1130#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1131
1132# This manage form page is for both students and students officers.
1133class AccommodationManageFormPage(WAeUPEditFormPage):
1134    """ Page to manage bed tickets.
1135    """
1136    grok.context(IStudentAccommodation)
1137    grok.name('index')
1138    grok.require('waeup.handleStudent')
1139    form_fields = grok.AutoFields(IStudentAccommodation)
1140    grok.template('accommodationmanagepage')
1141    title = 'Accommodation'
1142    pnav = 4
1143    officers_only_actions = ['Remove selected']
1144
1145    def formatDatetime(self,datetimeobj):
1146        if isinstance(datetimeobj, datetime):
1147            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1148        else:
1149            return None
1150
1151    @property
1152    def label(self):
1153        return '%s: Accommodation' % self.context.__parent__.fullname
1154
1155    def update(self):
1156        super(AccommodationManageFormPage, self).update()
1157        datatable.need()
1158        return
1159
1160    @property
1161    def is_student(self):
1162        prm = get_principal_role_manager()
1163        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1164        return 'waeup.Student' in roles
1165
1166    @grok.action('Remove selected')
1167    def delBedTickets(self, **data):
1168        if self.is_student:
1169            self.flash('You are not allowed to remove bed tickets.')
1170            self.redirect(self.url(self.context))
1171            return
1172        form = self.request.form
1173        if form.has_key('val_id'):
1174            child_id = form['val_id']
1175        else:
1176            self.flash('No bed ticket selected.')
1177            self.redirect(self.url(self.context))
1178            return
1179        if not isinstance(child_id, list):
1180            child_id = [child_id]
1181        deleted = []
1182        for id in child_id:
1183            del self.context[id]
1184            deleted.append(id)
1185        if len(deleted):
1186            self.flash('Successfully removed: %s' % ', '.join(deleted))
1187            write_log_message(self,'removed: % s' % ', '.join(deleted))
1188        self.redirect(self.url(self.context))
1189        return
1190
1191    @property
1192    def selected_actions(self):
1193        sa = self.actions
1194        if self.is_student:
1195            sa = [action for action in self.actions
1196                  if not action.label in self.officers_only_actions]
1197        return sa
1198
1199class AddBedTicketActionButton(ManageActionButton):
1200    grok.order(1)
1201    grok.context(IStudentAccommodation)
1202    grok.view(AccommodationManageFormPage)
1203    grok.require('waeup.handleStudent')
1204    icon = 'actionicon_home.png'
1205    text = 'Book accommodation'
1206    target = 'add'
1207
1208class BedTicketAddPage(WAeUPPage):
1209    """ Page to add an online payment ticket
1210    """
1211    grok.context(IStudentAccommodation)
1212    grok.name('add')
1213    grok.require('waeup.handleStudent')
1214    grok.template('enterpin')
1215    ac_prefix = 'HOS'
1216    label = 'Add bed ticket'
1217    title = 'Add bed ticket'
1218    pnav = 4
1219    buttonname = 'Create bed ticket'
1220    notice = ''
1221
1222    # To be sepezified in customization packages
1223    def getAccommodationDetails(self, student):
1224        return getAccommodationDetails(student)
1225
1226    # To be sepezified in customization packages
1227    def selectBed(self, available_beds):
1228        return selectBed(available_beds)
1229
1230    def update(self, SUBMIT=None):
1231        student = self.context.getStudent()
1232        acc_details  = self.getAccommodationDetails(student)
1233        if not student.state in acc_details['allowed_states']:
1234            self.flash("You are in the wrong registration state.")
1235            self.redirect(self.url(self.context))
1236            return
1237        if student['studycourse'].current_session != acc_details['booking_session']:
1238            self.flash(
1239                'Your current session does not match accommodation session.')
1240            self.redirect(self.url(self.context))
1241            return
1242        if str(acc_details['booking_session']) in self.context.keys():
1243            self.flash('You already booked a bed space in current accommodation session.')
1244            self.redirect(self.url(self.context))
1245            return
1246        self.ac_series = self.request.form.get('ac_series', None)
1247        self.ac_number = self.request.form.get('ac_number', None)
1248        if SUBMIT is None:
1249            return
1250        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1251        code = get_access_code(pin)
1252        if not code:
1253            self.flash('Activation code is invalid.')
1254            return
1255        # Search and book bed
1256        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1257        entries = cat.searchResults(
1258            owner=(student.student_id,student.student_id))
1259        if len(entries):
1260            # If bed space has bee manually allocated use this bed
1261            bed = [entry for entry in entries][0]
1262        else:
1263            # else search for other available beds
1264            entries = cat.searchResults(
1265                bed_type=(acc_details['bt'],acc_details['bt']))
1266            available_beds = [
1267                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1268            if available_beds:
1269                bed = self.selectBed(available_beds)
1270                bed.bookBed(student.student_id)
1271            else:
1272                self.flash('There is no free bed in your category %s.'
1273                            % acc_details['bt'])
1274                return
1275        # Mark pin as used (this also fires a pin related transition)
1276        if code.state == USED:
1277            self.flash('Activation code has already been used.')
1278            return
1279        else:
1280            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1281            # Here we know that the ac is in state initialized so we do not
1282            # expect an exception, but the owner might be different
1283            if not invalidate_accesscode(
1284                pin,comment,self.context.getStudent().student_id):
1285                self.flash('You are not the owner of this access code.')
1286                return
1287        # Create bed ticket
1288        bedticket = createObject(u'waeup.BedTicket')
1289        bedticket.booking_code = pin
1290        bedticket.booking_session = acc_details['booking_session']
1291        bedticket.bed_type = acc_details['bt']
1292        bedticket.bed = bed
1293        hall_title = bed.__parent__.hostel_name
1294        coordinates = bed.getBedCoordinates()[1:]
1295        block, room_nr, bed_nr = coordinates
1296        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1297            hall_title, block, room_nr, bed_nr, bed.bed_type)
1298        key = str(acc_details['booking_session'])
1299        self.context[key] = bedticket
1300        self.flash('Bed ticket created and bed booked: %s'
1301            % bedticket.bed_coordinates)
1302        self.redirect(self.url(self.context))
1303        return
1304
1305class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1306    """ Page to display bed tickets
1307    """
1308    grok.context(IBedTicket)
1309    grok.name('index')
1310    grok.require('waeup.viewStudent')
1311    form_fields = grok.AutoFields(IBedTicket)
1312    form_fields[
1313        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1314    pnav = 4
1315
1316    @property
1317    def label(self):
1318        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1319
1320    @property
1321    def title(self):
1322        return 'Bed Ticket %s' % self.context.getSessionString()
1323
1324class BedTicketSlipActionButton(ManageActionButton):
1325    grok.order(1)
1326    grok.context(IBedTicket)
1327    grok.view(BedTicketDisplayFormPage)
1328    grok.require('waeup.viewStudent')
1329    icon = 'actionicon_pdf.png'
1330    text = 'Download bed allocation slip'
1331    target = 'bed_allocation.pdf'
1332
1333class ExportPDFBedTicketSlipPage(grok.View):
1334    """Deliver a PDF slip of the context.
1335    """
1336    grok.context(IBedTicket)
1337    grok.name('bed_allocation.pdf')
1338    grok.require('waeup.viewStudent')
1339    form_fields = grok.AutoFields(IBedTicket)
1340    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1341    prefix = 'form'
1342
1343    @property
1344    def label(self):
1345        return 'Bed Allocation %s' % self.context.bed_coordinates
1346
1347    def render(self):
1348        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1349            self.request)
1350        return renderPDF(self,'Bed Allocation', 'bed_allocation.pdf',
1351            self.context.getStudent, studentview)
1352
1353class RelocateStudentActionButton(ManageActionButton):
1354    grok.order(2)
1355    grok.context(IBedTicket)
1356    grok.view(BedTicketDisplayFormPage)
1357    grok.require('waeup.manageHostels')
1358    icon = 'actionicon_reload.png'
1359    text = 'Relocate student'
1360    target = 'relocate'
1361
1362class BedTicketRelocationPage(grok.View):
1363    """ Callback view
1364    """
1365    grok.context(IBedTicket)
1366    grok.name('relocate')
1367    grok.require('waeup.manageHostels')
1368
1369    # To be sepezified in customization packages
1370    def getAccommodationDetails(self, student):
1371        return getAccommodationDetails(student)
1372
1373    # To be sepezified in customization packages
1374    def selectBed(self, available_beds):
1375        return selectBed(available_beds)
1376
1377    # Relocate student if student parameters have changed or the bed_type
1378    # of the bed has changed
1379    def update(self):
1380        student = self.context.getStudent()
1381        acc_details  = self.getAccommodationDetails(student)
1382        if self.context.bed != None and \
1383              'reserved' in self.context.bed.bed_type:
1384            self.flash("Students in reserved beds can't be relocated.")
1385            self.redirect(self.url(self.context))
1386            return
1387        if acc_details['bt'] == self.context.bed_type and \
1388                self.context.bed != None and \
1389                self.context.bed.bed_type == self.context.bed_type:
1390            self.flash("Student can't be relocated.")
1391            self.redirect(self.url(self.context))
1392            return
1393        # Search a bed
1394        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1395        entries = cat.searchResults(
1396            owner=(student.student_id,student.student_id))
1397        if len(entries) and self.context.bed == None:
1398            # If booking has been cancelled but other bed space has been
1399            # manually allocated after cancellation use this bed
1400            new_bed = [entry for entry in entries][0]
1401        else:
1402            # Search for other available beds
1403            entries = cat.searchResults(
1404                bed_type=(acc_details['bt'],acc_details['bt']))
1405            available_beds = [
1406                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1407            if available_beds:
1408                new_bed = self.selectBed(available_beds)
1409                new_bed.bookBed(student.student_id)
1410            else:
1411                self.flash('There is no free bed in your category %s.'
1412                            % acc_details['bt'])
1413                self.redirect(self.url(self.context))
1414                return
1415        # Rlease old bed if exists
1416        if self.context.bed != None:
1417            self.context.bed.owner = NOT_OCCUPIED
1418            notify(grok.ObjectModifiedEvent(self.context.bed))
1419        # Alocate new bed
1420        self.context.bed_type = acc_details['bt']
1421        self.context.bed = new_bed
1422        hall_title = new_bed.__parent__.hostel_name
1423        coordinates = new_bed.getBedCoordinates()[1:]
1424        block, room_nr, bed_nr = coordinates
1425        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1426            hall_title, block, room_nr, bed_nr, new_bed.bed_type)
1427        self.flash('Student relocated: %s' % self.context.bed_coordinates)
1428        self.redirect(self.url(self.context))
1429        return
1430
1431    def render(self):
1432        #self.redirect(self.url(self.context, '@@index'))
1433        return
1434
1435class StudentHistoryPage(WAeUPPage):
1436    """ Page to display student clearance data
1437    """
1438    grok.context(IStudent)
1439    grok.name('history')
1440    grok.require('waeup.viewStudent')
1441    grok.template('studenthistory')
1442    title = 'History'
1443    pnav = 4
1444
1445    @property
1446    def label(self):
1447        return '%s: History' % self.context.fullname
1448
1449# Pages for students only
1450
1451class StudentBaseEditActionButton(ManageActionButton):
1452    grok.order(1)
1453    grok.context(IStudent)
1454    grok.view(StudentBaseDisplayFormPage)
1455    grok.require('waeup.handleStudent')
1456    text = 'Change password'
1457    target = 'bedit'
1458
1459class StudentPasswordSetting(grok.Adapter):
1460    """Adapt IStudent to data needed for password settings.
1461
1462    We provide password getters/setters for the attached context (an
1463    IStudent object) that cooperate seamless with the usual
1464    formlib/form techniques.
1465    """
1466    grok.context(IStudent)
1467    grok.provides(IStudentPasswordSetting)
1468
1469    def __init__(self, context):
1470        self.name = context.fullname
1471        self.password_repeat = context.password
1472        self.context = context
1473        return
1474
1475    def getPassword(self):
1476        return self.context.password
1477
1478    def setPassword(self, password):
1479        IUserAccount(self.context).setPassword(password)
1480        return
1481
1482    password = property(getPassword, setPassword)
1483
1484class StudentBaseEditFormPage(WAeUPEditFormPage):
1485    """ View to edit student base data by student
1486    """
1487    grok.context(IStudent)
1488    grok.name('bedit')
1489    grok.require('waeup.handleStudent')
1490    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1491    #    'student_id', 'reg_number', 'matric_number')
1492    form_fields = grok.AutoFields(IStudentPasswordSetting)
1493    grok.template('baseeditpage')
1494    label = 'Change password'
1495    title = 'Base Data'
1496    pnav = 4
1497
1498    def update(self):
1499        super(StudentBaseEditFormPage, self).update()
1500        self.wf_info = IWorkflowInfo(self.context)
1501        return
1502
1503    def onFailure(self, action, data, errors):
1504        new_status = []
1505        other_errors = False
1506        for error in errors:
1507            msg = getattr(error, 'message', '')
1508            if isinstance(msg, basestring) and msg != '':
1509                new_status.append(msg)
1510            else:
1511                other_errors = True
1512        if other_errors:
1513            if new_status:
1514                new_status.append('see below for further errors')
1515            else:
1516                new_status.append('See below for details.')
1517        if new_status:
1518            self.status = u'There were errors: %s' % ', '.join(new_status)
1519        return
1520
1521    @grok.action('Save', failure=onFailure)
1522    def save(self, **data):
1523        self.applyData(self.context, **data)
1524        self.flash('Form has been saved.')
1525        return
1526
1527class StudentClearanceStartActionButton(ManageActionButton):
1528    grok.order(1)
1529    grok.context(IStudent)
1530    grok.view(StudentClearanceDisplayFormPage)
1531    grok.require('waeup.handleStudent')
1532    icon = 'actionicon_start.png'
1533    text = 'Start clearance'
1534    target = 'start_clearance'
1535
1536    @property
1537    def target_url(self):
1538        if self.context.state != 'admitted':
1539            return ''
1540        return self.view.url(self.view.context, self.target)
1541
1542class StartClearancePage(WAeUPPage):
1543    grok.context(IStudent)
1544    grok.name('start_clearance')
1545    grok.require('waeup.handleStudent')
1546    grok.template('enterpin')
1547    title = 'Start clearance'
1548    label = 'Start clearance'
1549    ac_prefix = 'CLR'
1550    notice = ''
1551    pnav = 4
1552    buttonname = 'Start clearance now'
1553
1554    def update(self, SUBMIT=None):
1555        if not self.context.state == 'admitted':
1556            self.flash("Wrong state.")
1557            self.redirect(self.url(self.context))
1558            return
1559        self.ac_series = self.request.form.get('ac_series', None)
1560        self.ac_number = self.request.form.get('ac_number', None)
1561
1562        if SUBMIT is None:
1563            return
1564        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1565        code = get_access_code(pin)
1566        if not code:
1567            self.flash('Activation code is invalid.')
1568            return
1569        # Mark pin as used (this also fires a pin related transition)
1570        # and fire transition start_clearance
1571        if code.state == USED:
1572            self.flash('Activation code has already been used.')
1573            return
1574        else:
1575            comment = u"AC invalidated for %s" % self.context.student_id
1576            # Here we know that the ac is in state initialized so we do not
1577            # expect an exception, but the owner might be different
1578            if not invalidate_accesscode(pin,comment,self.context.student_id):
1579                self.flash('You are not the owner of this access code.')
1580                return
1581            self.context.clr_code = pin
1582        IWorkflowInfo(self.context).fireTransition('start_clearance')
1583        self.flash('Clearance process has been started.')
1584        self.redirect(self.url(self.context,'cedit'))
1585        return
1586
1587class StudentClearanceEditActionButton(ManageActionButton):
1588    grok.order(1)
1589    grok.context(IStudent)
1590    grok.view(StudentClearanceDisplayFormPage)
1591    grok.require('waeup.handleStudent')
1592    text = 'Edit'
1593    target = 'cedit'
1594
1595    @property
1596    def target_url(self):
1597        if self.context.clearance_locked:
1598            return ''
1599        return self.view.url(self.view.context, self.target)
1600
1601class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1602    """ View to edit student clearance data by student
1603    """
1604    grok.context(IStudent)
1605    grok.name('cedit')
1606    grok.require('waeup.handleStudent')
1607    form_fields = grok.AutoFields(
1608        IStudentClearanceEdit).omit('clearance_locked')
1609    label = 'Edit clearance data'
1610    title = 'Clearance Data'
1611    pnav = 4
1612    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1613
1614    def emitLockMessage(self):
1615        self.flash('The requested form is locked (read-only).')
1616        self.redirect(self.url(self.context))
1617        return
1618
1619    def update(self):
1620        if self.context.clearance_locked:
1621            self.emitLockMessage()
1622            return
1623        datepicker.need()
1624        return super(StudentClearanceEditFormPage, self).update()
1625
1626    @grok.action('Save')
1627    def save(self, **data):
1628        self.applyData(self.context, **data)
1629        self.flash('Clearance form has been saved.')
1630        return
1631
1632    @grok.action('Save and request clearance')
1633    def requestclearance(self, **data):
1634        self.applyData(self.context, **data)
1635        self.context._p_changed = True
1636        #if self.dataNotComplete():
1637        #    self.flash(self.dataNotComplete())
1638        #    return
1639        self.flash('Clearance form has been saved.')
1640        self.redirect(self.url(self.context,'request_clearance'))
1641        return
1642
1643class RequestClearancePage(WAeUPPage):
1644    grok.context(IStudent)
1645    grok.name('request_clearance')
1646    grok.require('waeup.handleStudent')
1647    grok.template('enterpin')
1648    title = 'Request clearance'
1649    label = 'Request clearance'
1650    notice = 'Enter the CLR access code used for starting clearance.'
1651    ac_prefix = 'CLR'
1652    pnav = 4
1653    buttonname = 'Request clearance now'
1654
1655    def update(self, SUBMIT=None):
1656        self.ac_series = self.request.form.get('ac_series', None)
1657        self.ac_number = self.request.form.get('ac_number', None)
1658        if SUBMIT is None:
1659            return
1660        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1661        if self.context.clr_code != pin:
1662            self.flash("This isn't your CLR access code.")
1663            return
1664        state = IWorkflowState(self.context).getState()
1665        # This shouldn't happen, but the application officer
1666        # might have forgotten to lock the form after changing the state
1667        if state != CLEARANCE:
1668            self.flash('This form cannot be submitted. Wrong state!')
1669            return
1670        IWorkflowInfo(self.context).fireTransition('request_clearance')
1671        self.flash('Clearance has been requested.')
1672        self.redirect(self.url(self.context))
1673        return
1674
1675class CourseRegistrationStartActionButton(ManageActionButton):
1676    grok.order(1)
1677    grok.context(IStudentStudyCourse)
1678    grok.view(StudyCourseDisplayFormPage)
1679    grok.require('waeup.handleStudent')
1680    icon = 'actionicon_start.png'
1681    text = 'Start course registration'
1682    target = 'start_course_registration'
1683
1684    @property
1685    def target_url(self):
1686        if not self.context.getStudent().state in (CLEARED,RETURNING):
1687            return ''
1688        return self.view.url(self.view.context, self.target)
1689
1690class StartCourseRegistrationPage(WAeUPPage):
1691    grok.context(IStudentStudyCourse)
1692    grok.name('start_course_registration')
1693    grok.require('waeup.handleStudent')
1694    grok.template('enterpin')
1695    title = 'Start course registration'
1696    label = 'Start course registration'
1697    ac_prefix = 'SFE'
1698    notice = ''
1699    pnav = 4
1700    buttonname = 'Start course registration now'
1701
1702    def update(self, SUBMIT=None):
1703        if not self.context.getStudent().state in (CLEARED,RETURNING):
1704            self.flash("Wrong state.")
1705            self.redirect(self.url(self.context))
1706            return
1707        self.ac_series = self.request.form.get('ac_series', None)
1708        self.ac_number = self.request.form.get('ac_number', None)
1709
1710        if SUBMIT is None:
1711            return
1712        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1713        code = get_access_code(pin)
1714        if not code:
1715            self.flash('Activation code is invalid.')
1716            return
1717        # Mark pin as used (this also fires a pin related transition)
1718        # and fire transition start_clearance
1719        if code.state == USED:
1720            self.flash('Activation code has already been used.')
1721            return
1722        else:
1723            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1724            # Here we know that the ac is in state initialized so we do not
1725            # expect an exception, but the owner might be different
1726            if not invalidate_accesscode(
1727                pin,comment,self.context.getStudent().student_id):
1728                self.flash('You are not the owner of this access code.')
1729                return
1730        if self.context.getStudent().state == CLEARED:
1731            IWorkflowInfo(self.context.getStudent()).fireTransition(
1732                'pay_first_school_fee')
1733        elif self.context.getStudent().state == RETURNING:
1734            IWorkflowInfo(self.context.getStudent()).fireTransition(
1735                'pay_school_fee')
1736        self.flash('Course registration has been started.')
1737        self.redirect(self.url(self.context))
1738        return
1739
1740
1741class AddStudyLevelActionButton(AddActionButton):
1742    grok.order(1)
1743    grok.context(IStudentStudyCourse)
1744    grok.view(StudyCourseDisplayFormPage)
1745    grok.require('waeup.handleStudent')
1746    text = 'Add course list'
1747    target = 'add'
1748
1749    @property
1750    def target_url(self):
1751        student = self.view.context.getStudent()
1752        condition1 = student.state != 'school fee paid'
1753        condition2 = str(student['studycourse'].current_level) in \
1754            self.view.context.keys()
1755        if condition1 or condition2:
1756            return ''
1757        return self.view.url(self.view.context, self.target)
1758
1759class AddStudyLevelFormPage(WAeUPEditFormPage):
1760    """ Page for students to add current study levels
1761    """
1762    grok.context(IStudentStudyCourse)
1763    grok.name('add')
1764    grok.require('waeup.handleStudent')
1765    grok.template('studyleveladdpage')
1766    form_fields = grok.AutoFields(IStudentStudyCourse)
1767    title = 'Study Course'
1768    pnav = 4
1769
1770    @property
1771    def label(self):
1772        studylevelsource = StudyLevelSource().factory
1773        code = self.context.current_level
1774        title = studylevelsource.getTitle(self.context, code)
1775        return 'Add current level %s' % title
1776
1777    def emitLockMessage(self):
1778        self.flash('The requested form is locked (read-only).')
1779        self.redirect(self.url(self.context))
1780        return
1781
1782    def update(self):
1783        if self.context.getStudent().state != 'school fee paid':
1784            self.emitLockMessage()
1785            return
1786        super(AddStudyLevelFormPage, self).update()
1787        return
1788
1789    @grok.action('Create course list now')
1790    def addStudyLevel(self, **data):
1791        studylevel = StudentStudyLevel()
1792        studylevel.level = self.context.current_level
1793        studylevel.level_session = self.context.current_session
1794        try:
1795            self.context.addStudentStudyLevel(
1796                self.context.certificate,studylevel)
1797        except KeyError:
1798            self.flash('This level exists.')
1799        self.redirect(self.url(self.context))
1800        return
1801
1802class StudyLevelEditActionButton(ManageActionButton):
1803    grok.order(1)
1804    grok.context(IStudentStudyLevel)
1805    grok.view(StudyLevelDisplayFormPage)
1806    grok.require('waeup.handleStudent')
1807    text = 'Add and remove courses'
1808    target = 'edit'
1809
1810    @property
1811    def target_url(self):
1812        student = self.view.context.getStudent()
1813        condition1 = student.state != 'school fee paid'
1814        condition2 = student[
1815            'studycourse'].current_level != self.view.context.level
1816        if condition1 or condition2:
1817            return ''
1818        return self.view.url(self.view.context, self.target)
1819
1820class StudyLevelEditFormPage(WAeUPEditFormPage):
1821    """ Page to edit the student study level data by students
1822    """
1823    grok.context(IStudentStudyLevel)
1824    grok.name('edit')
1825    grok.require('waeup.handleStudent')
1826    grok.template('studyleveleditpage')
1827    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1828        'level_session', 'level_verdict')
1829    pnav = 4
1830
1831    def update(self):
1832        super(StudyLevelEditFormPage, self).update()
1833    #    tabs.need()
1834        datatable.need()
1835        return
1836
1837    @property
1838    def title(self):
1839        return 'Study Level %s' % self.context.level_title
1840
1841    @property
1842    def label(self):
1843        return 'Add and remove course tickets of study level %s' % self.context.level_title
1844
1845    @property
1846    def total_credits(self):
1847        total_credits = 0
1848        for key, val in self.context.items():
1849            total_credits += val.credits
1850        return total_credits
1851
1852    @grok.action('Add course ticket')
1853    def addCourseTicket(self, **data):
1854        self.redirect(self.url(self.context, 'ctadd'))
1855
1856    @grok.action('Remove selected tickets')
1857    def delCourseTicket(self, **data):
1858        form = self.request.form
1859        if form.has_key('val_id'):
1860            child_id = form['val_id']
1861        else:
1862            self.flash('No ticket selected.')
1863            self.redirect(self.url(self.context, '@@edit'))
1864            return
1865        if not isinstance(child_id, list):
1866            child_id = [child_id]
1867        deleted = []
1868        for id in child_id:
1869            # Students are not allowed to remove core tickets
1870            if not self.context[id].core_or_elective:
1871                try:
1872                    del self.context[id]
1873                    deleted.append(id)
1874                except:
1875                    self.flash('Could not delete %s: %s: %s' % (
1876                            id, sys.exc_info()[0], sys.exc_info()[1]))
1877        if len(deleted):
1878            self.flash('Successfully removed: %s' % ', '.join(deleted))
1879        self.redirect(self.url(self.context, u'@@edit'))
1880        return
1881
1882    @grok.action('Register course list')
1883    def register_courses(self, **data):
1884        state = IWorkflowState(self.context.getStudent()).getState()
1885        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1886        self.flash('Course list has been registered.')
1887        self.redirect(self.url(self.context))
1888        return
1889
1890class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1891    """Add a course ticket by student.
1892    """
1893    grok.name('ctadd')
1894    grok.require('waeup.handleStudent')
1895    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1896        'grade', 'score', 'core_or_elective', 'automatic')
1897
1898    @grok.action('Add course ticket')
1899    def addCourseTicket(self, **data):
1900        ticket = CourseTicket()
1901        course = data['course']
1902        ticket.automatic = False
1903        ticket.code = course.code
1904        ticket.title = course.title
1905        ticket.faculty = course.__parent__.__parent__.__parent__.title
1906        ticket.department = course.__parent__.__parent__.title
1907        ticket.credits = course.credits
1908        ticket.passmark = course.passmark
1909        ticket.semester = course.semester
1910        try:
1911            self.context.addCourseTicket(ticket)
1912        except KeyError:
1913            self.flash('The ticket exists.')
1914            return
1915        self.flash('Successfully added %s.' % ticket.code)
1916        self.redirect(self.url(self.context, u'@@edit'))
1917        return
Note: See TracBrowser for help on using the repository browser.