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

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

Use proper action icons (hopefully we can use them also with the bootstrap toolkit).

  • Property svn:keywords set to Id
File size: 67.9 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    icon = 'actionicon_accept.png'
464
465    @property
466    def target_url(self):
467        if self.context.state != REQUESTED:
468            return ''
469        return self.view.url(self.view.context, self.target)
470
471class StudentRejectClearanceActionButton(ManageActionButton):
472    grok.order(2)
473    grok.context(IStudent)
474    grok.view(StudentClearanceDisplayFormPage)
475    grok.require('waeup.clearStudent')
476    text = 'Reject clearance'
477    target = 'reject_clearance'
478    icon = 'actionicon_reject.png'
479
480    @property
481    def target_url(self):
482        if self.context.state not in (REQUESTED, CLEARED):
483            return ''
484        return self.view.url(self.view.context, self.target)
485
486class StudentClearanceManageFormPage(WAeUPEditFormPage):
487    """ Page to edit student clearance data
488    """
489    grok.context(IStudent)
490    grok.name('edit_clearance')
491    grok.require('waeup.manageStudent')
492    grok.template('clearanceeditpage')
493    form_fields = grok.AutoFields(IStudentClearance)
494    label = 'Manage clearance data'
495    title = 'Clearance Data'
496    pnav = 4
497    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
498
499    def update(self):
500        datepicker.need() # Enable jQuery datepicker in date fields.
501        tabs.need()
502        return super(StudentClearanceManageFormPage, self).update()
503
504    @grok.action('Save')
505    def save(self, **data):
506        msave(self, **data)
507        return
508
509class StudentClearPage(grok.View):
510    """ Clear student by clearance officer
511    """
512    grok.context(IStudent)
513    grok.name('clear')
514    grok.require('waeup.clearStudent')
515
516    def update(self):
517        if self.context.state == REQUESTED:
518            IWorkflowInfo(self.context).fireTransition('clear')
519            self.flash('Student has been cleared.')
520        else:
521            self.flash('Student is in the wrong state.')
522        self.redirect(self.url(self.context,'view_clearance'))
523        return
524
525    def render(self):
526        self.redirect(self.url(self.context, 'view_clearance'))
527        return
528
529class StudentRejectClearancePage(grok.View):
530    """ Reject clearance by clearance officers
531    """
532    grok.context(IStudent)
533    grok.name('reject_clearance')
534    grok.require('waeup.clearStudent')
535
536    def update(self):
537        if self.context.state == CLEARED:
538            IWorkflowInfo(self.context).fireTransition('reset4')
539            self.flash('Clearance has been annulled.')
540        elif self.context.state == REQUESTED:
541            IWorkflowInfo(self.context).fireTransition('reset3')
542            self.flash('Clearance request has been rejected.')
543        else:
544            self.flash('Student is in the wrong state.')
545        self.redirect(self.url(self.context,'view_clearance'))
546        return
547
548    def render(self):
549        self.redirect(self.url(self.context, 'view_clearance'))
550        return
551
552class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
553    """ Page to display student personal data
554    """
555    grok.context(IStudent)
556    grok.name('view_personal')
557    grok.require('waeup.viewStudent')
558    form_fields = grok.AutoFields(IStudentPersonal)
559    title = 'Personal Data'
560    pnav = 4
561
562    @property
563    def label(self):
564        return '%s: Personal Data' % self.context.fullname
565
566class StudentPersonalManageActionButton(ManageActionButton):
567    grok.order(1)
568    grok.context(IStudent)
569    grok.view(StudentPersonalDisplayFormPage)
570    grok.require('waeup.manageStudent')
571    text = 'Manage'
572    target = 'edit_personal'
573
574class StudentPersonalManageFormPage(WAeUPEditFormPage):
575    """ Page to edit student clearance data
576    """
577    grok.context(IStudent)
578    grok.name('edit_personal')
579    grok.require('waeup.viewStudent')
580    form_fields = grok.AutoFields(IStudentPersonal)
581    label = 'Manage personal data'
582    title = 'Personal Data'
583    pnav = 4
584
585    @grok.action('Save')
586    def save(self, **data):
587        msave(self, **data)
588        return
589
590class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
591    """ Page to display the student study course data
592    """
593    grok.context(IStudentStudyCourse)
594    grok.name('index')
595    grok.require('waeup.viewStudent')
596    form_fields = grok.AutoFields(IStudentStudyCourse)
597    grok.template('studycoursepage')
598    title = 'Study Course'
599    pnav = 4
600
601    @property
602    def label(self):
603        return '%s: Study Course' % self.context.__parent__.fullname
604
605    @property
606    def current_mode(self):
607        return study_modes.getTermByToken(
608            self.context.certificate.study_mode).title
609
610class StudyCourseManageActionButton(ManageActionButton):
611    grok.order(1)
612    grok.context(IStudentStudyCourse)
613    grok.view(StudyCourseDisplayFormPage)
614    grok.require('waeup.manageStudent')
615    text = 'Manage'
616    target = 'manage'
617
618class StudyCourseManageFormPage(WAeUPEditFormPage):
619    """ Page to edit the student study course data
620    """
621    grok.context(IStudentStudyCourse)
622    grok.name('manage')
623    grok.require('waeup.manageStudent')
624    grok.template('studycoursemanagepage')
625    form_fields = grok.AutoFields(IStudentStudyCourse)
626    title = 'Study Course'
627    label = 'Manage study course'
628    pnav = 4
629    taboneactions = ['Save','Cancel']
630    tabtwoactions = ['Remove selected levels','Cancel']
631    tabthreeactions = ['Add study level']
632
633    def update(self):
634        super(StudyCourseManageFormPage, self).update()
635        tabs.need()
636        datatable.need()
637        return
638
639    @grok.action('Save')
640    def save(self, **data):
641        msave(self, **data)
642        return
643
644    @property
645    def level_dict(self):
646        studylevelsource = StudyLevelSource().factory
647        for code in studylevelsource.getValues(self.context):
648            title = studylevelsource.getTitle(self.context, code)
649            yield(dict(code=code, title=title))
650
651    @grok.action('Add study level')
652    def addStudyLevel(self, **data):
653        level_code = self.request.form.get('addlevel', None)
654        studylevel = StudentStudyLevel()
655        studylevel.level = int(level_code)
656        try:
657            self.context.addStudentStudyLevel(
658                self.context.certificate,studylevel)
659        except KeyError:
660            self.flash('This level exists.')
661        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
662        return
663
664    @grok.action('Remove selected levels')
665    def delStudyLevels(self, **data):
666        form = self.request.form
667        if form.has_key('val_id'):
668            child_id = form['val_id']
669        else:
670            self.flash('No study level selected.')
671            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
672            return
673        if not isinstance(child_id, list):
674            child_id = [child_id]
675        deleted = []
676        for id in child_id:
677            try:
678                del self.context[id]
679                deleted.append(id)
680            except:
681                self.flash('Could not delete %s: %s: %s' % (
682                        id, sys.exc_info()[0], sys.exc_info()[1]))
683        if len(deleted):
684            self.flash('Successfully removed: %s' % ', '.join(deleted))
685        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
686        return
687
688class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
689    """ Page to display student study levels
690    """
691    grok.context(IStudentStudyLevel)
692    grok.name('index')
693    grok.require('waeup.viewStudent')
694    form_fields = grok.AutoFields(IStudentStudyLevel)
695    grok.template('studylevelpage')
696    pnav = 4
697
698    @property
699    def title(self):
700        return 'Study Level %s' % self.context.level_title
701
702    @property
703    def label(self):
704        return '%s: Study Level %s' % (
705            self.context.getStudent().fullname,self.context.level_title)
706
707    @property
708    def total_credits(self):
709        total_credits = 0
710        for key, val in self.context.items():
711            total_credits += val.credits
712        return total_credits
713
714class CourseRegistrationSlipActionButton(ManageActionButton):
715    grok.order(1)
716    grok.context(IStudentStudyLevel)
717    grok.view(StudyLevelDisplayFormPage)
718    grok.require('waeup.viewStudent')
719    icon = 'actionicon_pdf.png'
720    text = 'Download course registration slip'
721    target = 'course_registration.pdf'
722
723class ExportPDFCourseRegistrationSlipPage(grok.View):
724    """Deliver a PDF slip of the context.
725    """
726    grok.context(IStudentStudyLevel)
727    grok.name('course_registration.pdf')
728    grok.require('waeup.viewStudent')
729    form_fields = grok.AutoFields(IStudentStudyLevel)
730    prefix = 'form'
731
732    @property
733    def label(self):
734        return 'Course Registration Slip %s' % self.context.level_title
735
736    def render(self):
737        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
738            self.request)
739        students_utils = getUtility(IStudentsUtils)
740        return students_utils.render_pdf(
741            self,'Course Registration', 'course_registration.pdf',
742            self.context.getStudent, studentview)
743
744class StudyLevelManageActionButton(ManageActionButton):
745    grok.order(2)
746    grok.context(IStudentStudyLevel)
747    grok.view(StudyLevelDisplayFormPage)
748    grok.require('waeup.manageStudent')
749    text = 'Manage'
750    target = 'manage'
751
752class StudyLevelManageFormPage(WAeUPEditFormPage):
753    """ Page to edit the student study level data
754    """
755    grok.context(IStudentStudyLevel)
756    grok.name('manage')
757    grok.require('waeup.manageStudent')
758    grok.template('studylevelmanagepage')
759    form_fields = grok.AutoFields(IStudentStudyLevel)
760    pnav = 4
761    taboneactions = ['Save','Cancel']
762    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
763
764    def update(self):
765        super(StudyLevelManageFormPage, self).update()
766        tabs.need()
767        datatable.need()
768        return
769
770    @property
771    def title(self):
772        return 'Study Level %s' % self.context.level_title
773
774    @property
775    def label(self):
776        return 'Manage study level %s' % self.context.level_title
777
778    @grok.action('Save')
779    def save(self, **data):
780        msave(self, **data)
781        return
782
783    @grok.action('Add course ticket')
784    def addCourseTicket(self, **data):
785        self.redirect(self.url(self.context, '@@add'))
786
787    @grok.action('Remove selected tickets')
788    def delCourseTicket(self, **data):
789        form = self.request.form
790        if form.has_key('val_id'):
791            child_id = form['val_id']
792        else:
793            self.flash('No ticket selected.')
794            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
795            return
796        if not isinstance(child_id, list):
797            child_id = [child_id]
798        deleted = []
799        for id in child_id:
800            try:
801                del self.context[id]
802                deleted.append(id)
803            except:
804                self.flash('Could not delete %s: %s: %s' % (
805                        id, sys.exc_info()[0], sys.exc_info()[1]))
806        if len(deleted):
807            self.flash('Successfully removed: %s' % ', '.join(deleted))
808        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
809        return
810
811class CourseTicketAddFormPage(WAeUPAddFormPage):
812    """Add a course ticket.
813    """
814    grok.context(IStudentStudyLevel)
815    grok.name('add')
816    grok.require('waeup.manageStudent')
817    label = 'Add course ticket'
818    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
819        'grade', 'score', 'automatic')
820    pnav = 4
821
822    @property
823    def title(self):
824        return 'Study Level %s' % self.context.level_title
825
826    @grok.action('Add course ticket')
827    def addCourseTicket(self, **data):
828        ticket = CourseTicket()
829        course = data['course']
830        ticket.core_or_elective = data['core_or_elective']
831        ticket.automatic = False
832        ticket.code = course.code
833        ticket.title = course.title
834        ticket.faculty = course.__parent__.__parent__.__parent__.title
835        ticket.department = course.__parent__.__parent__.title
836        ticket.credits = course.credits
837        ticket.passmark = course.passmark
838        ticket.semester = course.semester
839        try:
840            self.context.addCourseTicket(ticket)
841        except KeyError:
842            self.flash('The ticket exists.')
843            return
844        self.flash('Successfully added %s.' % ticket.code)
845        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
846        return
847
848    @grok.action('Cancel')
849    def cancel(self, **data):
850        self.redirect(self.url(self.context))
851
852class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
853    """ Page to display course tickets
854    """
855    grok.context(ICourseTicket)
856    grok.name('index')
857    grok.require('waeup.viewStudent')
858    form_fields = grok.AutoFields(ICourseTicket)
859    grok.template('courseticketpage')
860    pnav = 4
861
862    @property
863    def title(self):
864        return 'Course Ticket %s' % self.context.code
865
866    @property
867    def label(self):
868        return '%s: Course Ticket %s' % (
869            self.context.getStudent().fullname,self.context.code)
870
871class CourseTicketManageActionButton(ManageActionButton):
872    grok.order(1)
873    grok.context(ICourseTicket)
874    grok.view(CourseTicketDisplayFormPage)
875    grok.require('waeup.manageStudent')
876    text = 'Manage'
877    target = 'manage'
878
879class CourseTicketManageFormPage(WAeUPEditFormPage):
880    """ Page to manage course tickets
881    """
882    grok.context(ICourseTicket)
883    grok.name('manage')
884    grok.require('waeup.manageStudent')
885    form_fields = grok.AutoFields(ICourseTicket)
886    grok.template('courseticketmanagepage')
887    pnav = 4
888
889    @property
890    def title(self):
891        return 'Course Ticket %s' % self.context.code
892
893    @property
894    def label(self):
895        return 'Manage course ticket %s' % self.context.code
896
897    @grok.action('Save')
898    def save(self, **data):
899        msave(self, **data)
900        return
901
902# We don't need the display form page yet
903#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
904#    """ Page to display the student payments
905#    """
906#    grok.context(IStudentPaymentsContainer)
907#    grok.name('view')
908#    grok.require('waeup.viewStudent')
909#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
910#    grok.template('paymentspage')
911#    title = 'Payments'
912#    pnav = 4
913
914#    def formatDatetime(self,datetimeobj):
915#        if isinstance(datetimeobj, datetime):
916#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
917#        else:
918#            return None
919
920#    @property
921#    def label(self):
922#        return '%s: Payments' % self.context.__parent__.fullname
923
924#    def update(self):
925#        super(PaymentsDisplayFormPage, self).update()
926#        datatable.need()
927#        return
928
929# This manage form page is for both students and students officers.
930class PaymentsManageFormPage(WAeUPEditFormPage):
931    """ Page to manage the student payments
932    """
933    grok.context(IStudentPaymentsContainer)
934    grok.name('index')
935    grok.require('waeup.handleStudent')
936    form_fields = grok.AutoFields(IStudentPaymentsContainer)
937    grok.template('paymentsmanagepage')
938    title = 'Payments'
939    pnav = 4
940
941    def unremovable(self, ticket):
942        prm = get_principal_role_manager()
943        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
944        return ('waeup.Student' in roles and ticket.r_code)
945
946    def formatDatetime(self,datetimeobj):
947        if isinstance(datetimeobj, datetime):
948            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
949        else:
950            return None
951
952    @property
953    def label(self):
954        return '%s: Payments' % self.context.__parent__.fullname
955
956    def update(self):
957        super(PaymentsManageFormPage, self).update()
958        datatable.need()
959        return
960
961    @grok.action('Remove selected tickets')
962    def delPaymentTicket(self, **data):
963        form = self.request.form
964        if form.has_key('val_id'):
965            child_id = form['val_id']
966        else:
967            self.flash('No payment selected.')
968            self.redirect(self.url(self.context))
969            return
970        if not isinstance(child_id, list):
971            child_id = [child_id]
972        deleted = []
973        for id in child_id:
974            # Students are not allowed to remove used payment tickets
975            if not self.unremovable(self.context[id]):
976                try:
977                    del self.context[id]
978                    deleted.append(id)
979                except:
980                    self.flash('Could not delete %s: %s: %s' % (
981                            id, sys.exc_info()[0], sys.exc_info()[1]))
982        if len(deleted):
983            self.flash('Successfully removed: %s' % ', '.join(deleted))
984            write_log_message(self,'removed: % s' % ', '.join(deleted))
985        self.redirect(self.url(self.context))
986        return
987
988    @grok.action('Add online payment ticket')
989    def addPaymentTicket(self, **data):
990        self.redirect(self.url(self.context, '@@addop'))
991
992#class OnlinePaymentManageActionButton(ManageActionButton):
993#    grok.order(1)
994#    grok.context(IStudentPaymentsContainer)
995#    grok.view(PaymentsDisplayFormPage)
996#    grok.require('waeup.manageStudent')
997#    text = 'Manage payments'
998#    target = 'manage'
999
1000class OnlinePaymentAddFormPage(WAeUPAddFormPage):
1001    """ Page to add an online payment ticket
1002    """
1003    grok.context(IStudentPaymentsContainer)
1004    grok.name('addop')
1005    grok.require('waeup.handleStudent')
1006    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
1007        'p_category')
1008    #zzgrok.template('addpaymentpage')
1009    label = 'Add online payment'
1010    title = 'Payments'
1011    pnav = 4
1012   
1013    @grok.action('Create ticket')
1014    def createTicket(self, **data):
1015        p_category = data['p_category']
1016        student = self.context.__parent__
1017        if p_category == 'bed_allocation' and student[
1018            'studycourse'].current_session != grok.getSite()[
1019            'configuration'].accommodation_session:
1020                self.flash(
1021                    'Your current session does not match accommodation session.')
1022                self.redirect(self.url(self.context))
1023                return
1024        students_utils = getUtility(IStudentsUtils)
1025        pay_details  = students_utils.get_payment_details(
1026            p_category,student)
1027        if pay_details['error']:
1028            self.flash(pay_details['error'])
1029            self.redirect(self.url(self.context))
1030            return
1031        p_item = pay_details['p_item']
1032        p_session = pay_details['p_session']
1033        for key in self.context.keys():
1034            ticket = self.context[key]
1035            if ticket.p_category == p_category and \
1036               ticket.p_item == p_item and \
1037               ticket.p_session == p_session:
1038                  self.flash(
1039                      'This payment ticket already exists.')
1040                  self.redirect(self.url(self.context))
1041                  return
1042        payment = createObject(u'waeup.StudentOnlinePayment')
1043        self.applyData(payment, **data)
1044        timestamp = "%d" % int(time()*1000)
1045        #order_id = "%s%s" % (student_id[1:],timestamp)
1046        payment.p_id = "p%s" % timestamp
1047        payment.p_item = p_item
1048        payment.p_session = p_session
1049        payment.amount_auth = pay_details['amount']
1050        payment.surcharge_1 = pay_details['surcharge_1']
1051        payment.surcharge_2 = pay_details['surcharge_2']
1052        payment.surcharge_3 = pay_details['surcharge_3']
1053        self.context[payment.p_id] = payment
1054        self.flash('Payment ticket created.')
1055        self.redirect(self.url(self.context))
1056        return
1057
1058class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
1059    """ Page to view an online payment ticket
1060    """
1061    grok.context(IStudentOnlinePayment)
1062    grok.name('index')
1063    grok.require('waeup.viewStudent')
1064    form_fields = grok.AutoFields(IStudentOnlinePayment)
1065    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1066    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1067    pnav = 4
1068
1069    @property
1070    def title(self):
1071        return 'Online Payment Ticket %s' % self.context.p_id
1072
1073    @property
1074    def label(self):
1075        return '%s: Online Payment Ticket %s' % (
1076            self.context.getStudent().fullname,self.context.p_id)
1077
1078class PaymentReceiptActionButton(ManageActionButton):
1079    grok.order(1)
1080    grok.context(IStudentOnlinePayment)
1081    grok.view(OnlinePaymentDisplayFormPage)
1082    grok.require('waeup.viewStudent')
1083    icon = 'actionicon_pdf.png'
1084    text = 'Download payment receipt'
1085    target = 'payment_receipt.pdf'
1086
1087    @property
1088    def target_url(self):
1089        if self.context.p_state != 'paid':
1090            return ''
1091        return self.view.url(self.view.context, self.target)
1092
1093class RequestCallbackActionButton(ManageActionButton):
1094    grok.order(2)
1095    grok.context(IStudentOnlinePayment)
1096    grok.view(OnlinePaymentDisplayFormPage)
1097    grok.require('waeup.handleStudent')
1098    icon = 'actionicon_call.png'
1099    text = 'Request callback'
1100    target = 'callback'
1101
1102    @property
1103    def target_url(self):
1104        if self.context.p_state != 'unpaid':
1105            return ''
1106        return self.view.url(self.view.context, self.target)
1107
1108class OnlinePaymentCallbackPage(grok.View):
1109    """ Callback view
1110    """
1111    grok.context(IStudentOnlinePayment)
1112    grok.name('callback')
1113    grok.require('waeup.payStudent')
1114
1115    # This update method simulates a valid callback und must be
1116    # specified in the customization package. The parameters must be taken
1117    # from the incoming request.
1118    def update(self):
1119        if self.context.p_state == 'paid':
1120            self.flash('This ticket has already been paid.')
1121            return
1122        student = self.context.getStudent()
1123        write_log_message(self,'valid callback: %s' % self.context.p_id)
1124        self.context.r_amount_approved = self.context.amount_auth
1125        self.context.r_card_num = u'0000'
1126        self.context.r_code = u'00'
1127        self.context.p_state = 'paid'
1128        self.context.payment_date = datetime.now()
1129        if self.context.p_category == 'clearance':
1130            # Create CLR access code
1131            pin, error = create_accesscode('CLR',0,student.student_id)
1132            if error:
1133                self.flash('Valid callback received. ' + error)
1134                return
1135            self.context.ac = pin
1136        elif self.context.p_category == 'schoolfee':
1137            # Create SFE access code
1138            pin, error = create_accesscode('SFE',0,student.student_id)
1139            if error:
1140                self.flash('Valid callback received. ' + error)
1141                return
1142            self.context.ac = pin
1143        elif self.context.p_category == 'bed_allocation':
1144            # Create HOS access code
1145            pin, error = create_accesscode('HOS',0,student.student_id)
1146            if error:
1147                self.flash('Valid callback received. ' + error)
1148                return
1149            self.context.ac = pin
1150        self.flash('Valid callback received.')
1151        return
1152
1153    def render(self):
1154        self.redirect(self.url(self.context, '@@index'))
1155        return
1156
1157class ExportPDFPaymentSlipPage(grok.View):
1158    """Deliver a PDF slip of the context.
1159    """
1160    grok.context(IStudentOnlinePayment)
1161    grok.name('payment_receipt.pdf')
1162    grok.require('waeup.viewStudent')
1163    form_fields = grok.AutoFields(IStudentOnlinePayment)
1164    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1165    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1166    prefix = 'form'
1167
1168    @property
1169    def label(self):
1170        return 'Online Payment Receipt %s' % self.context.p_id
1171
1172    def render(self):
1173        if self.context.p_state != 'paid':
1174            self.flash('Ticket not yet paid.')
1175            self.redirect(self.url(self.context))
1176            return
1177        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1178            self.request)
1179        students_utils = getUtility(IStudentsUtils)
1180        return students_utils.render_pdf(self,'Payment', 'payment_receipt.pdf',
1181            self.context.getStudent, studentview)
1182
1183# We don't need the display form page yet
1184#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1185#    """ Page to display the student accommodation data
1186#    """
1187#    grok.context(IStudentAccommodation)
1188#    grok.name('xxx')
1189#    grok.require('waeup.viewStudent')
1190#    form_fields = grok.AutoFields(IStudentAccommodation)
1191#    #grok.template('accommodationpage')
1192#    title = 'Accommodation'
1193#    pnav = 4
1194
1195#    @property
1196#    def label(self):
1197#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1198
1199# This manage form page is for both students and students officers.
1200class AccommodationManageFormPage(WAeUPEditFormPage):
1201    """ Page to manage bed tickets.
1202    """
1203    grok.context(IStudentAccommodation)
1204    grok.name('index')
1205    grok.require('waeup.handleStudent')
1206    form_fields = grok.AutoFields(IStudentAccommodation)
1207    grok.template('accommodationmanagepage')
1208    title = 'Accommodation'
1209    pnav = 4
1210    officers_only_actions = ['Remove selected']
1211
1212    def formatDatetime(self,datetimeobj):
1213        if isinstance(datetimeobj, datetime):
1214            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1215        else:
1216            return None
1217
1218    @property
1219    def label(self):
1220        return '%s: Accommodation' % self.context.__parent__.fullname
1221
1222    def update(self):
1223        super(AccommodationManageFormPage, self).update()
1224        datatable.need()
1225        return
1226
1227    @property
1228    def is_student(self):
1229        prm = get_principal_role_manager()
1230        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1231        return 'waeup.Student' in roles
1232
1233    @grok.action('Remove selected')
1234    def delBedTickets(self, **data):
1235        if self.is_student:
1236            self.flash('You are not allowed to remove bed tickets.')
1237            self.redirect(self.url(self.context))
1238            return
1239        form = self.request.form
1240        if form.has_key('val_id'):
1241            child_id = form['val_id']
1242        else:
1243            self.flash('No bed ticket selected.')
1244            self.redirect(self.url(self.context))
1245            return
1246        if not isinstance(child_id, list):
1247            child_id = [child_id]
1248        deleted = []
1249        for id in child_id:
1250            del self.context[id]
1251            deleted.append(id)
1252        if len(deleted):
1253            self.flash('Successfully removed: %s' % ', '.join(deleted))
1254            write_log_message(self,'removed: % s' % ', '.join(deleted))
1255        self.redirect(self.url(self.context))
1256        return
1257
1258    @property
1259    def selected_actions(self):
1260        sa = self.actions
1261        if self.is_student:
1262            sa = [action for action in self.actions
1263                  if not action.label in self.officers_only_actions]
1264        return sa
1265
1266class AddBedTicketActionButton(ManageActionButton):
1267    grok.order(1)
1268    grok.context(IStudentAccommodation)
1269    grok.view(AccommodationManageFormPage)
1270    grok.require('waeup.handleStudent')
1271    icon = 'actionicon_home.png'
1272    text = 'Book accommodation'
1273    target = 'add'
1274
1275class BedTicketAddPage(WAeUPPage):
1276    """ Page to add an online payment ticket
1277    """
1278    grok.context(IStudentAccommodation)
1279    grok.name('add')
1280    grok.require('waeup.handleStudent')
1281    grok.template('enterpin')
1282    ac_prefix = 'HOS'
1283    label = 'Add bed ticket'
1284    title = 'Add bed ticket'
1285    pnav = 4
1286    buttonname = 'Create bed ticket'
1287    notice = ''
1288
1289    def update(self, SUBMIT=None):
1290        student = self.context.getStudent()
1291        students_utils = getUtility(IStudentsUtils)
1292        acc_details  = students_utils.get_accommodation_details(student)
1293        if not student.state in acc_details['allowed_states']:
1294            self.flash("You are in the wrong registration state.")
1295            self.redirect(self.url(self.context))
1296            return
1297        if student['studycourse'].current_session != acc_details['booking_session']:
1298            self.flash(
1299                'Your current session does not match accommodation session.')
1300            self.redirect(self.url(self.context))
1301            return
1302        if str(acc_details['booking_session']) in self.context.keys():
1303            self.flash('You already booked a bed space in current accommodation session.')
1304            self.redirect(self.url(self.context))
1305            return
1306        self.ac_series = self.request.form.get('ac_series', None)
1307        self.ac_number = self.request.form.get('ac_number', None)
1308        if SUBMIT is None:
1309            return
1310        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1311        code = get_access_code(pin)
1312        if not code:
1313            self.flash('Activation code is invalid.')
1314            return
1315        # Search and book bed
1316        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1317        entries = cat.searchResults(
1318            owner=(student.student_id,student.student_id))
1319        if len(entries):
1320            # If bed space has bee manually allocated use this bed
1321            bed = [entry for entry in entries][0]
1322        else:
1323            # else search for other available beds
1324            entries = cat.searchResults(
1325                bed_type=(acc_details['bt'],acc_details['bt']))
1326            available_beds = [
1327                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1328            if available_beds:
1329                students_utils = getUtility(IStudentsUtils)
1330                bed = students_utils.select_bed(available_beds)
1331                bed.bookBed(student.student_id)
1332            else:
1333                self.flash('There is no free bed in your category %s.'
1334                            % acc_details['bt'])
1335                return
1336        # Mark pin as used (this also fires a pin related transition)
1337        if code.state == USED:
1338            self.flash('Activation code has already been used.')
1339            return
1340        else:
1341            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1342            # Here we know that the ac is in state initialized so we do not
1343            # expect an exception, but the owner might be different
1344            if not invalidate_accesscode(
1345                pin,comment,self.context.getStudent().student_id):
1346                self.flash('You are not the owner of this access code.')
1347                return
1348        # Create bed ticket
1349        bedticket = createObject(u'waeup.BedTicket')
1350        bedticket.booking_code = pin
1351        bedticket.booking_session = acc_details['booking_session']
1352        bedticket.bed_type = acc_details['bt']
1353        bedticket.bed = bed
1354        hall_title = bed.__parent__.hostel_name
1355        coordinates = bed.getBedCoordinates()[1:]
1356        block, room_nr, bed_nr = coordinates
1357        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1358            hall_title, block, room_nr, bed_nr, bed.bed_type)
1359        key = str(acc_details['booking_session'])
1360        self.context[key] = bedticket
1361        self.flash('Bed ticket created and bed booked: %s'
1362            % bedticket.bed_coordinates)
1363        self.redirect(self.url(self.context))
1364        return
1365
1366class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1367    """ Page to display bed tickets
1368    """
1369    grok.context(IBedTicket)
1370    grok.name('index')
1371    grok.require('waeup.viewStudent')
1372    form_fields = grok.AutoFields(IBedTicket)
1373    form_fields[
1374        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1375    pnav = 4
1376
1377    @property
1378    def label(self):
1379        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1380
1381    @property
1382    def title(self):
1383        return 'Bed Ticket %s' % self.context.getSessionString()
1384
1385class BedTicketSlipActionButton(ManageActionButton):
1386    grok.order(1)
1387    grok.context(IBedTicket)
1388    grok.view(BedTicketDisplayFormPage)
1389    grok.require('waeup.viewStudent')
1390    icon = 'actionicon_pdf.png'
1391    text = 'Download bed allocation slip'
1392    target = 'bed_allocation.pdf'
1393
1394class ExportPDFBedTicketSlipPage(grok.View):
1395    """Deliver a PDF slip of the context.
1396    """
1397    grok.context(IBedTicket)
1398    grok.name('bed_allocation.pdf')
1399    grok.require('waeup.viewStudent')
1400    form_fields = grok.AutoFields(IBedTicket)
1401    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1402    prefix = 'form'
1403
1404    @property
1405    def label(self):
1406        return 'Bed Allocation %s' % self.context.bed_coordinates
1407
1408    def render(self):
1409        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1410            self.request)
1411        students_utils = getUtility(IStudentsUtils)
1412        return students_utils.render_pdf(
1413            self,'Bed Allocation', 'bed_allocation.pdf',
1414            self.context.getStudent, studentview)
1415
1416class RelocateStudentActionButton(ManageActionButton):
1417    grok.order(2)
1418    grok.context(IBedTicket)
1419    grok.view(BedTicketDisplayFormPage)
1420    grok.require('waeup.manageHostels')
1421    icon = 'actionicon_reload.png'
1422    text = 'Relocate student'
1423    target = 'relocate'
1424
1425class BedTicketRelocationPage(grok.View):
1426    """ Callback view
1427    """
1428    grok.context(IBedTicket)
1429    grok.name('relocate')
1430    grok.require('waeup.manageHostels')
1431
1432    # Relocate student if student parameters have changed or the bed_type
1433    # of the bed has changed
1434    def update(self):
1435        student = self.context.getStudent()
1436        students_utils = getUtility(IStudentsUtils)
1437        acc_details  = students_utils.get_accommodation_details(student)
1438        if self.context.bed != None and \
1439              'reserved' in self.context.bed.bed_type:
1440            self.flash("Students in reserved beds can't be relocated.")
1441            self.redirect(self.url(self.context))
1442            return
1443        if acc_details['bt'] == self.context.bed_type and \
1444                self.context.bed != None and \
1445                self.context.bed.bed_type == self.context.bed_type:
1446            self.flash("Student can't be relocated.")
1447            self.redirect(self.url(self.context))
1448            return
1449        # Search a bed
1450        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1451        entries = cat.searchResults(
1452            owner=(student.student_id,student.student_id))
1453        if len(entries) and self.context.bed == None:
1454            # If booking has been cancelled but other bed space has been
1455            # manually allocated after cancellation use this bed
1456            new_bed = [entry for entry in entries][0]
1457        else:
1458            # Search for other available beds
1459            entries = cat.searchResults(
1460                bed_type=(acc_details['bt'],acc_details['bt']))
1461            available_beds = [
1462                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1463            if available_beds:
1464                students_utils = getUtility(IStudentsUtils)
1465                new_bed = students_utils.select_bed(available_beds)
1466                new_bed.bookBed(student.student_id)
1467            else:
1468                self.flash('There is no free bed in your category %s.'
1469                            % acc_details['bt'])
1470                self.redirect(self.url(self.context))
1471                return
1472        # Rlease old bed if exists
1473        if self.context.bed != None:
1474            self.context.bed.owner = NOT_OCCUPIED
1475            notify(grok.ObjectModifiedEvent(self.context.bed))
1476        # Alocate new bed
1477        self.context.bed_type = acc_details['bt']
1478        self.context.bed = new_bed
1479        hall_title = new_bed.__parent__.hostel_name
1480        coordinates = new_bed.getBedCoordinates()[1:]
1481        block, room_nr, bed_nr = coordinates
1482        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1483            hall_title, block, room_nr, bed_nr, new_bed.bed_type)
1484        self.flash('Student relocated: %s' % self.context.bed_coordinates)
1485        self.redirect(self.url(self.context))
1486        return
1487
1488    def render(self):
1489        #self.redirect(self.url(self.context, '@@index'))
1490        return
1491
1492class StudentHistoryPage(WAeUPPage):
1493    """ Page to display student clearance data
1494    """
1495    grok.context(IStudent)
1496    grok.name('history')
1497    grok.require('waeup.viewStudent')
1498    grok.template('studenthistory')
1499    title = 'History'
1500    pnav = 4
1501
1502    @property
1503    def label(self):
1504        return '%s: History' % self.context.fullname
1505
1506# Pages for students only
1507
1508class StudentBaseActionButton(ManageActionButton):
1509    grok.order(1)
1510    grok.context(IStudent)
1511    grok.view(StudentBaseDisplayFormPage)
1512    grok.require('waeup.handleStudent')
1513    text = 'Edit base data'
1514    target = 'edit_base'
1515
1516class StudentPasswordActionButton(ManageActionButton):
1517    grok.order(2)
1518    grok.context(IStudent)
1519    grok.view(StudentBaseDisplayFormPage)
1520    grok.require('waeup.handleStudent')
1521    icon = 'actionicon_key.png'
1522    text = 'Change password'
1523    target = 'change_password'
1524
1525class StudentPassportActionButton(ManageActionButton):
1526    grok.order(3)
1527    grok.context(IStudent)
1528    grok.view(StudentBaseDisplayFormPage)
1529    grok.require('waeup.handleStudent')
1530    icon = 'actionicon_portrait.png'
1531    text = 'Change portrait'
1532    target = 'change_portrait'
1533
1534    @property
1535    def target_url(self):
1536        if self.context.state != 'admitted':
1537            return ''
1538        return self.view.url(self.view.context, self.target)
1539
1540class StudentBaseEditFormPage(WAeUPEditFormPage):
1541    """ View to edit student base data
1542    """
1543    grok.context(IStudent)
1544    grok.name('edit_base')
1545    grok.require('waeup.handleStudent')
1546    form_fields = grok.AutoFields(IStudentBase).select(
1547        'email', 'phone')
1548    label = 'Edit base data'
1549    title = 'Base Data'
1550    pnav = 4
1551
1552    @grok.action('Save')
1553    def save(self, **data):
1554        msave(self, **data)
1555        return
1556
1557class StudentChangePasswordPage(WAeUPEditFormPage):
1558    """ View to manage student base data
1559    """
1560    grok.context(IStudent)
1561    grok.name('change_password')
1562    grok.require('waeup.handleStudent')
1563    grok.template('change_password')
1564    label = 'Change password'
1565    title = 'Base Data'
1566    pnav = 4
1567
1568    @grok.action('Save')
1569    def save(self, **data):
1570        form = self.request.form
1571        password = form.get('change_password', None)
1572        password_ctl = form.get('change_password_repeat', None)
1573        if password:
1574            validator = getUtility(IPasswordValidator)
1575            errors = validator.validate_password(password, password_ctl)
1576            if not errors:
1577                IUserAccount(self.context).setPassword(password)
1578                write_log_message(self, 'saved: password')
1579                self.flash('Password changed.')
1580            else:
1581                self.flash( ' '.join(errors))
1582        return
1583
1584class StudentFilesUploadPage(WAeUPPage):
1585    """ View to upload files by student
1586    """
1587    grok.context(IStudent)
1588    grok.name('change_portrait')
1589    grok.require('waeup.uploadStudentFile')
1590    grok.template('filesuploadpage')
1591    label = 'Upload portrait'
1592    title = 'Base Data'
1593    pnav = 4
1594
1595    def update(self):
1596        if self.context.getStudent().state != 'admitted':
1597            emit_lock_message(self)
1598            return
1599        super(StudentFilesUploadPage, self).update()
1600        return
1601
1602class StudentClearanceStartActionButton(ManageActionButton):
1603    grok.order(1)
1604    grok.context(IStudent)
1605    grok.view(StudentClearanceDisplayFormPage)
1606    grok.require('waeup.handleStudent')
1607    icon = 'actionicon_start.gif'
1608    text = 'Start clearance'
1609    target = 'start_clearance'
1610
1611    @property
1612    def target_url(self):
1613        if self.context.state != 'admitted':
1614            return ''
1615        return self.view.url(self.view.context, self.target)
1616
1617class StartClearancePage(WAeUPPage):
1618    grok.context(IStudent)
1619    grok.name('start_clearance')
1620    grok.require('waeup.handleStudent')
1621    grok.template('enterpin')
1622    title = 'Start clearance'
1623    label = 'Start clearance'
1624    ac_prefix = 'CLR'
1625    notice = ''
1626    pnav = 4
1627    buttonname = 'Start clearance now'
1628
1629    @property
1630    def all_required_fields_filled(self):
1631        if self.context.email and self.context.phone:
1632            return True
1633        return False
1634
1635    @property
1636    def portrait_uploaded(self):
1637        store = getUtility(IExtFileStore)
1638        if store.getFileByContext(self.context, attr=u'passport.jpg'):
1639            return True
1640        return False
1641
1642    def update(self, SUBMIT=None):
1643        if not self.context.state == 'admitted':
1644            self.flash("Wrong state.")
1645            self.redirect(self.url(self.context))
1646            return
1647        if not self.portrait_uploaded:
1648            self.flash("No portrait uploaded.")
1649            self.redirect(self.url(self.context, 'change_portrait'))
1650            return
1651        if not self.all_required_fields_filled:
1652            self.flash("Not all required fields filled.")
1653            self.redirect(self.url(self.context, 'edit_base'))
1654            return
1655        self.ac_series = self.request.form.get('ac_series', None)
1656        self.ac_number = self.request.form.get('ac_number', None)
1657
1658        if SUBMIT is None:
1659            return
1660        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1661        code = get_access_code(pin)
1662        if not code:
1663            self.flash('Activation code is invalid.')
1664            return
1665        # Mark pin as used (this also fires a pin related transition)
1666        # and fire transition start_clearance
1667        if code.state == USED:
1668            self.flash('Activation code has already been used.')
1669            return
1670        else:
1671            comment = u"AC invalidated for %s" % self.context.student_id
1672            # Here we know that the ac is in state initialized so we do not
1673            # expect an exception, but the owner might be different
1674            if not invalidate_accesscode(pin,comment,self.context.student_id):
1675                self.flash('You are not the owner of this access code.')
1676                return
1677            self.context.clr_code = pin
1678        IWorkflowInfo(self.context).fireTransition('start_clearance')
1679        self.flash('Clearance process has been started.')
1680        self.redirect(self.url(self.context,'cedit'))
1681        return
1682
1683class StudentClearanceEditActionButton(ManageActionButton):
1684    grok.order(1)
1685    grok.context(IStudent)
1686    grok.view(StudentClearanceDisplayFormPage)
1687    grok.require('waeup.handleStudent')
1688    text = 'Edit'
1689    target = 'cedit'
1690
1691    @property
1692    def target_url(self):
1693        if self.context.clearance_locked:
1694            return ''
1695        return self.view.url(self.view.context, self.target)
1696
1697class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1698    """ View to edit student clearance data by student
1699    """
1700    grok.context(IStudent)
1701    grok.name('cedit')
1702    grok.require('waeup.handleStudent')
1703    form_fields = grok.AutoFields(
1704        IStudentClearanceEdit).omit('clearance_locked')
1705    label = 'Edit clearance data'
1706    title = 'Clearance Data'
1707    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1708
1709    def update(self):
1710        if self.context.clearance_locked:
1711            emit_lock_message(self)
1712            return
1713        return super(StudentClearanceEditFormPage, self).update()
1714
1715    @grok.action('Save')
1716    def save(self, **data):
1717        self.applyData(self.context, **data)
1718        self.flash('Clearance form has been saved.')
1719        return
1720
1721    @grok.action('Save and request clearance')
1722    def requestclearance(self, **data):
1723        self.applyData(self.context, **data)
1724        self.context._p_changed = True
1725        #if self.dataNotComplete():
1726        #    self.flash(self.dataNotComplete())
1727        #    return
1728        self.flash('Clearance form has been saved.')
1729        self.redirect(self.url(self.context,'request_clearance'))
1730        return
1731
1732class RequestClearancePage(WAeUPPage):
1733    grok.context(IStudent)
1734    grok.name('request_clearance')
1735    grok.require('waeup.handleStudent')
1736    grok.template('enterpin')
1737    title = 'Request clearance'
1738    label = 'Request clearance'
1739    notice = 'Enter the CLR access code used for starting clearance.'
1740    ac_prefix = 'CLR'
1741    pnav = 4
1742    buttonname = 'Request clearance now'
1743
1744    def update(self, SUBMIT=None):
1745        self.ac_series = self.request.form.get('ac_series', None)
1746        self.ac_number = self.request.form.get('ac_number', None)
1747        if SUBMIT is None:
1748            return
1749        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1750        if self.context.clr_code != pin:
1751            self.flash("This isn't your CLR access code.")
1752            return
1753        state = IWorkflowState(self.context).getState()
1754        # This shouldn't happen, but the application officer
1755        # might have forgotten to lock the form after changing the state
1756        if state != CLEARANCE:
1757            self.flash('This form cannot be submitted. Wrong state!')
1758            return
1759        IWorkflowInfo(self.context).fireTransition('request_clearance')
1760        self.flash('Clearance has been requested.')
1761        self.redirect(self.url(self.context))
1762        return
1763
1764class CourseRegistrationStartActionButton(ManageActionButton):
1765    grok.order(1)
1766    grok.context(IStudentStudyCourse)
1767    grok.view(StudyCourseDisplayFormPage)
1768    grok.require('waeup.handleStudent')
1769    icon = 'actionicon_start.gif'
1770    text = 'Start course registration'
1771    target = 'start_course_registration'
1772
1773    @property
1774    def target_url(self):
1775        if not self.context.getStudent().state in (CLEARED,RETURNING):
1776            return ''
1777        return self.view.url(self.view.context, self.target)
1778
1779class StartCourseRegistrationPage(WAeUPPage):
1780    grok.context(IStudentStudyCourse)
1781    grok.name('start_course_registration')
1782    grok.require('waeup.handleStudent')
1783    grok.template('enterpin')
1784    title = 'Start course registration'
1785    label = 'Start course registration'
1786    ac_prefix = 'SFE'
1787    notice = ''
1788    pnav = 4
1789    buttonname = 'Start course registration now'
1790
1791    def update(self, SUBMIT=None):
1792        if not self.context.getStudent().state in (CLEARED,RETURNING):
1793            self.flash("Wrong state.")
1794            self.redirect(self.url(self.context))
1795            return
1796        self.ac_series = self.request.form.get('ac_series', None)
1797        self.ac_number = self.request.form.get('ac_number', None)
1798
1799        if SUBMIT is None:
1800            return
1801        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1802        code = get_access_code(pin)
1803        if not code:
1804            self.flash('Activation code is invalid.')
1805            return
1806        # Mark pin as used (this also fires a pin related transition)
1807        # and fire transition start_clearance
1808        if code.state == USED:
1809            self.flash('Activation code has already been used.')
1810            return
1811        else:
1812            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1813            # Here we know that the ac is in state initialized so we do not
1814            # expect an exception, but the owner might be different
1815            if not invalidate_accesscode(
1816                pin,comment,self.context.getStudent().student_id):
1817                self.flash('You are not the owner of this access code.')
1818                return
1819        if self.context.getStudent().state == CLEARED:
1820            IWorkflowInfo(self.context.getStudent()).fireTransition(
1821                'pay_first_school_fee')
1822        elif self.context.getStudent().state == RETURNING:
1823            IWorkflowInfo(self.context.getStudent()).fireTransition(
1824                'pay_school_fee')
1825        self.flash('Course registration has been started.')
1826        self.redirect(self.url(self.context))
1827        return
1828
1829
1830class AddStudyLevelActionButton(AddActionButton):
1831    grok.order(1)
1832    grok.context(IStudentStudyCourse)
1833    grok.view(StudyCourseDisplayFormPage)
1834    grok.require('waeup.handleStudent')
1835    text = 'Add course list'
1836    target = 'add'
1837
1838    @property
1839    def target_url(self):
1840        student = self.view.context.getStudent()
1841        condition1 = student.state != 'school fee paid'
1842        condition2 = str(student['studycourse'].current_level) in \
1843            self.view.context.keys()
1844        if condition1 or condition2:
1845            return ''
1846        return self.view.url(self.view.context, self.target)
1847
1848class AddStudyLevelFormPage(WAeUPEditFormPage):
1849    """ Page for students to add current study levels
1850    """
1851    grok.context(IStudentStudyCourse)
1852    grok.name('add')
1853    grok.require('waeup.handleStudent')
1854    grok.template('studyleveladdpage')
1855    form_fields = grok.AutoFields(IStudentStudyCourse)
1856    title = 'Study Course'
1857    pnav = 4
1858
1859    @property
1860    def label(self):
1861        studylevelsource = StudyLevelSource().factory
1862        code = self.context.current_level
1863        title = studylevelsource.getTitle(self.context, code)
1864        return 'Add current level %s' % title
1865
1866    def update(self):
1867        if self.context.getStudent().state != 'school fee paid':
1868            emit_lock_message(self)
1869            return
1870        super(AddStudyLevelFormPage, self).update()
1871        return
1872
1873    @grok.action('Create course list now')
1874    def addStudyLevel(self, **data):
1875        studylevel = StudentStudyLevel()
1876        studylevel.level = self.context.current_level
1877        studylevel.level_session = self.context.current_session
1878        try:
1879            self.context.addStudentStudyLevel(
1880                self.context.certificate,studylevel)
1881        except KeyError:
1882            self.flash('This level exists.')
1883        self.redirect(self.url(self.context))
1884        return
1885
1886class StudyLevelEditActionButton(ManageActionButton):
1887    grok.order(1)
1888    grok.context(IStudentStudyLevel)
1889    grok.view(StudyLevelDisplayFormPage)
1890    grok.require('waeup.handleStudent')
1891    text = 'Add and remove courses'
1892    target = 'edit'
1893
1894    @property
1895    def target_url(self):
1896        student = self.view.context.getStudent()
1897        condition1 = student.state != 'school fee paid'
1898        condition2 = student[
1899            'studycourse'].current_level != self.view.context.level
1900        if condition1 or condition2:
1901            return ''
1902        return self.view.url(self.view.context, self.target)
1903
1904class StudyLevelEditFormPage(WAeUPEditFormPage):
1905    """ Page to edit the student study level data by students
1906    """
1907    grok.context(IStudentStudyLevel)
1908    grok.name('edit')
1909    grok.require('waeup.handleStudent')
1910    grok.template('studyleveleditpage')
1911    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1912        'level_session', 'level_verdict')
1913    pnav = 4
1914
1915    def update(self):
1916        super(StudyLevelEditFormPage, self).update()
1917    #    tabs.need()
1918        datatable.need()
1919        return
1920
1921    @property
1922    def title(self):
1923        return 'Study Level %s' % self.context.level_title
1924
1925    @property
1926    def label(self):
1927        return 'Add and remove course tickets of study level %s' % self.context.level_title
1928
1929    @property
1930    def total_credits(self):
1931        total_credits = 0
1932        for key, val in self.context.items():
1933            total_credits += val.credits
1934        return total_credits
1935
1936    @grok.action('Add course ticket')
1937    def addCourseTicket(self, **data):
1938        self.redirect(self.url(self.context, 'ctadd'))
1939
1940    @grok.action('Remove selected tickets')
1941    def delCourseTicket(self, **data):
1942        form = self.request.form
1943        if form.has_key('val_id'):
1944            child_id = form['val_id']
1945        else:
1946            self.flash('No ticket selected.')
1947            self.redirect(self.url(self.context, '@@edit'))
1948            return
1949        if not isinstance(child_id, list):
1950            child_id = [child_id]
1951        deleted = []
1952        for id in child_id:
1953            # Students are not allowed to remove core tickets
1954            if not self.context[id].core_or_elective:
1955                try:
1956                    del self.context[id]
1957                    deleted.append(id)
1958                except:
1959                    self.flash('Could not delete %s: %s: %s' % (
1960                            id, sys.exc_info()[0], sys.exc_info()[1]))
1961        if len(deleted):
1962            self.flash('Successfully removed: %s' % ', '.join(deleted))
1963        self.redirect(self.url(self.context, u'@@edit'))
1964        return
1965
1966    @grok.action('Register course list')
1967    def register_courses(self, **data):
1968        state = IWorkflowState(self.context.getStudent()).getState()
1969        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1970        self.flash('Course list has been registered.')
1971        self.redirect(self.url(self.context))
1972        return
1973
1974class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1975    """Add a course ticket by student.
1976    """
1977    grok.name('ctadd')
1978    grok.require('waeup.handleStudent')
1979    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1980        'grade', 'score', 'core_or_elective', 'automatic')
1981
1982    @grok.action('Add course ticket')
1983    def addCourseTicket(self, **data):
1984        ticket = CourseTicket()
1985        course = data['course']
1986        ticket.automatic = False
1987        ticket.code = course.code
1988        ticket.title = course.title
1989        ticket.faculty = course.__parent__.__parent__.__parent__.title
1990        ticket.department = course.__parent__.__parent__.title
1991        ticket.credits = course.credits
1992        ticket.passmark = course.passmark
1993        ticket.semester = course.semester
1994        try:
1995            self.context.addCourseTicket(ticket)
1996        except KeyError:
1997            self.flash('The ticket exists.')
1998            return
1999        self.flash('Successfully added %s.' % ticket.code)
2000        self.redirect(self.url(self.context, u'@@edit'))
2001        return
Note: See TracBrowser for help on using the repository browser.