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

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

Use always view directive for action buttons.

Add test for opening the pdf course registration slip.

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