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

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

StudentsOfficers? are not allowed to view the accommodation and payments containers of students. We therefore use the dedicated permissions waeup.handleAccommodation and waeup.payStudent.

Add role waeup.ACManager with permission waeup.manageACBatches which has already been uses in accesscodes.

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