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

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

Seat up a global utility class to ease customization. Transform send_mail function into a utility method.

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