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

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

Rename functions according to the WAeUP style guide:

functions and methods with property decorator with underscore

methods with CamelCase

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