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

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

Export course registration slip on two pages.
Set columns width of each column.
Show semester on all course registration view.

ToDo?: Sort course tickets by semester on course registration slip.

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