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

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

Replace the term 'WAeUP' by SIRP which is a WAeUP product.

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