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

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

Add StudentApplication? view components.

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