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

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

This my recent version of the createStudent method.

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