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

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

The StudentApplication? class is deprecated. We want to store the application_slip pdf file file instead.

Prepare everything in the students package for downloading such a pdf file.

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