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

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

Minor changes.

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