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

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

academics: Show students in departments.

students: Search for students in department.

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