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

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

Rebuild applicants package (1st part). Applicants now have an applicant_id and a password and can use the regular login page to enter the portal.

Add user_type attribute to SIRPPrincipal objects.

Add some permissions in students package.

Some tests are still missing and will be re-added soon.

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