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

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

Add first version of ExportPDFClearanceSlipPage.

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