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

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

Add action buttons for clearance officers.

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