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

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

An existing HOS code can only be used if students' current session is equal to accommodation session.

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