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

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

Redirect to specified tabs using the request's QUERY_STRING value. This suboptimal solution will be replaced later by a pure Javascript solution.

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