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

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

Use reduced display form page as header in payment receipts.
Add more tests.

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