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

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

Implement local CourseAdviser? roles. These roles can be assigned in departments and certificates. There are 6 different roles, one for each study level. getRolesForPrincipal grants the additional waeup.StudentsCourseAdviser? role only if the current level of a student corresponds with the level number in the external role name.

To do: Assign local roles on CertificateManageFormPage?. Add browser tests.

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