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

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

pyflakes

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