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

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

Do not store faculty and department titles in each course ticket.

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