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

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

Define formatDatetime (for displaying datetime objects in data tables) centrally in layout.py.

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