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

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

Adjust copyright statement and svn keyword in students.

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