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

Last change on this file since 7008 was 7006, checked in by Henrik Bettermann, 14 years ago

Add missing handler.

  • Property svn:keywords set to Id
File size: 56.5 KB
RevLine 
[6621]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"""
[7006]18import sys
[6621]19import grok
[6869]20from time import time
21from datetime import date, datetime
[6996]22from zope.catalog.interfaces import ICatalog
23from zope.component import queryUtility
[6621]24from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6760]25from zope.component import createObject
[6936]26from waeup.sirp.accesscodes import (
[6937]27    invalidate_accesscode, get_access_code, create_accesscode)
[6621]28from waeup.sirp.accesscodes.workflow import USED
29from waeup.sirp.browser import (
30    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
31from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6775]32from waeup.sirp.browser.resources import datepicker, datatable, tabs
[6621]33from waeup.sirp.browser.viewlets import (
[6760]34    ManageActionButton, PrimaryNavTab, AddActionButton)
35from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
[6621]36from waeup.sirp.widgets.datewidget import (
[6869]37    FriendlyDateWidget, FriendlyDateDisplayWidget,
38    FriendlyDatetimeDisplayWidget)
[6912]39from waeup.sirp.university.vocabularies import study_modes
[6621]40from waeup.sirp.students.interfaces import (
[6756]41    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
[6859]42    IStudentPersonal, IStudentBase, IStudentStudyCourse,
[6774]43    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
[6877]44    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
[6992]45    IStudentOnlinePayment, IBedTicket
[6621]46    )
[6626]47from waeup.sirp.students.catalog import search
[6992]48from waeup.sirp.students.workflow import (
49    CLEARANCE, RETURNING, CLEARED, REGISTERED, VALIDATED)
[6795]50from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
[6775]51from waeup.sirp.students.vocabularies import StudyLevelSource
[6996]52from waeup.sirp.students.utils import (
[7004]53    getPaymentDetails, getAccommodationDetails, selectBed)
[6820]54from waeup.sirp.browser.resources import toggleall
[6940]55from waeup.sirp.authentication import get_principal_role_manager
[6997]56from waeup.sirp.hostels.hostel import NOT_OCCUPIED
[6621]57
[6943]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
[6762]63# Save function used for save methods in manager pages
64def msave(view, **data):
65    form = view.request.form
66    changed_fields = view.applyData(view.context, **data)
[6771]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)
[6762]71    view.context._p_changed = True
72    view.flash('Form has been saved.')
73    if fields_string:
[6943]74        write_log_message(view, 'saved: % s' % fields_string)
[6762]75    return
76
[6621]77class StudentsTab(PrimaryNavTab):
78    """Students tab in primary navigation.
79    """
80
81    grok.context(IWAeUPObject)
[6953]82    grok.order(4)
[6660]83    grok.require('waeup.viewStudent')
[6621]84    grok.template('primarynavtab')
85
86    pnav = 4
87    tab_title = u'Students'
88
89    @property
90    def link_target(self):
91        return self.view.application_url('students')
92
[6629]93class StudentsBreadcrumb(Breadcrumb):
94    """A breadcrumb for the students container.
95    """
96    grok.context(IStudentsContainer)
97    title = u'Students'
98
[6818]99class StudentBreadcrumb(Breadcrumb):
100    """A breadcrumb for the student container.
101    """
102    grok.context(IStudent)
103
104    def title(self):
105        return self.context.fullname
106
[6635]107class SudyCourseBreadcrumb(Breadcrumb):
108    """A breadcrumb for the student study course.
109    """
110    grok.context(IStudentStudyCourse)
111    title = u'Study Course'
112
113class PaymentsBreadcrumb(Breadcrumb):
114    """A breadcrumb for the student payments folder.
115    """
[6859]116    grok.context(IStudentPaymentsContainer)
[6635]117    title = u'Payments'
118
[6870]119class OnlinePaymentBreadcrumb(Breadcrumb):
120    """A breadcrumb for course lists.
121    """
[6877]122    grok.context(IStudentOnlinePayment)
[6870]123
124    @property
125    def title(self):
126        return self.context.p_id
127
[6635]128class AccommodationBreadcrumb(Breadcrumb):
129    """A breadcrumb for the student accommodation folder.
130    """
131    grok.context(IStudentAccommodation)
132    title = u'Accommodation'
133
[6994]134class BedTicketBreadcrumb(Breadcrumb):
135    """A breadcrumb for bed tickets.
136    """
137    grok.context(IBedTicket)
138    @property
139
140    def title(self):
141        return 'Bed Ticket %s' % self.context.getSessionString()
142
[6776]143class StudyLevelBreadcrumb(Breadcrumb):
144    """A breadcrumb for course lists.
145    """
146    grok.context(IStudentStudyLevel)
147
148    @property
149    def title(self):
150        return self.context.level_title
151
[6626]152class StudentsContainerPage(WAeUPPage):
153    """The standard view for student containers.
[6621]154    """
155    grok.context(IStudentsContainer)
156    grok.name('index')
[6660]157    grok.require('waeup.viewStudent')
[6695]158    grok.template('containerpage')
[6654]159    label = 'Student Section'
160    title = 'Students'
[6642]161    pnav = 4
[6621]162
[6626]163    def update(self, *args, **kw):
164        datatable.need()
165        form = self.request.form
166        self.hitlist = []
167        if 'searchterm' in form and form['searchterm']:
168            self.searchterm = form['searchterm']
169            self.searchtype = form['searchtype']
170        elif 'old_searchterm' in form:
171            self.searchterm = form['old_searchterm']
172            self.searchtype = form['old_searchtype']
173        else:
174            if 'search' in form:
175                self.flash('Empty search string.')
176            return
177        self.hitlist = search(query=self.searchterm,
178            searchtype=self.searchtype, view=self)
179        if not self.hitlist:
180            self.flash('No student found.')
181        return
182
[6773]183class SetPasswordPage(WAeUPPage):
[6699]184    grok.context(IWAeUPObject)
185    grok.name('setpassword')
186    grok.require('waeup.Public')
[6774]187    grok.template('setpassword')
[6699]188    title = ''
189    label = 'Set password for first-time login'
[6758]190    ac_prefix = 'PWD'
[6715]191    pnav = 0
[6699]192
193    def update(self, SUBMIT=None):
[6758]194        self.reg_number = self.request.form.get('reg_number', None)
195        self.ac_series = self.request.form.get('ac_series', None)
196        self.ac_number = self.request.form.get('ac_number', None)
[6756]197
[6699]198        if SUBMIT is None:
199            return
200        hitlist = search(query=self.reg_number,
201            searchtype='reg_number', view=self)
202        if not hitlist:
203            self.flash('No student found.')
204            return
205        if len(hitlist) != 1:   # Cannot happen but anyway
206            self.flash('More than one student found.')
207            return
[6704]208        student = hitlist[0].context
209        self.student_id = student.student_id
210        student_pw = student.password
[6758]211        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
[6699]212        code = get_access_code(pin)
213        if not code:
214            self.flash('Access code is invalid.')
215            return
[6704]216        if student_pw and pin == student.adm_code:
217            self.flash('Password has already been set. Your Student Id is %s'
218                % self.student_id)
219            return
220        elif student_pw:
[6800]221            self.flash('Password has already been set. You are using the wrong Access Code.')
[6704]222            return
[6699]223        # Mark pin as used (this also fires a pin related transition)
224        # and set student password
225        if code.state == USED:
226            self.flash('Access code has already been used.')
227            return
228        else:
[6704]229            comment = u"AC invalidated for %s" % self.student_id
[6699]230            # Here we know that the ac is in state initialized so we do not
231            # expect an exception
232            #import pdb; pdb.set_trace()
233            invalidate_accesscode(pin,comment)
[6758]234            IUserAccount(student).setPassword(self.ac_number)
[6769]235            student.adm_code = pin
[6704]236        self.flash('Password has been set. Your Student Id is %s'
237            % self.student_id)
[6699]238        return
239
[6626]240class StudentsContainerManageActionButton(ManageActionButton):
[6622]241    grok.order(1)
242    grok.context(IStudentsContainer)
243    grok.view(StudentsContainerPage)
244    grok.require('waeup.manageStudents')
[6647]245    text = 'Manage student section'
[6622]246
[6626]247
248class StudentsContainerManagePage(WAeUPPage):
249    """The manage page for student containers.
[6622]250    """
251    grok.context(IStudentsContainer)
252    grok.name('manage')
253    grok.require('waeup.manageStudents')
[6695]254    grok.template('containermanagepage')
[6642]255    pnav = 4
[6647]256    title = 'Manage student section'
[6622]257
258    @property
259    def label(self):
260        return self.title
261
[6626]262    def update(self, *args, **kw):
263        datatable.need()
[6820]264        toggleall.need()
[6626]265        form = self.request.form
266        self.hitlist = []
267        if 'searchterm' in form and form['searchterm']:
268            self.searchterm = form['searchterm']
269            self.searchtype = form['searchtype']
270        elif 'old_searchterm' in form:
271            self.searchterm = form['old_searchterm']
272            self.searchtype = form['old_searchtype']
273        else:
274            if 'search' in form:
275                self.flash('Empty search string.')
276            return
277        if not 'entries' in form:
278            self.hitlist = search(query=self.searchterm,
279                searchtype=self.searchtype, view=self)
280            if not self.hitlist:
281                self.flash('No student found.')
282            return
283        entries = form['entries']
284        if isinstance(entries, basestring):
285            entries = [entries]
286        deleted = []
287        for entry in entries:
288            if 'remove' in form:
289                del self.context[entry]
290                deleted.append(entry)
291        self.hitlist = search(query=self.searchterm,
292            searchtype=self.searchtype, view=self)
293        if len(deleted):
294            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6622]295        return
296
[6626]297class StudentsContainerAddActionButton(AddActionButton):
298    grok.order(1)
299    grok.context(IStudentsContainer)
300    grok.view(StudentsContainerManagePage)
301    grok.require('waeup.manageStudents')
302    text = 'Add student'
303    target = 'addstudent'
304
[6622]305class StudentAddFormPage(WAeUPAddFormPage):
306    """Add-form to add a student.
307    """
308    grok.context(IStudentsContainer)
309    grok.require('waeup.manageStudents')
310    grok.name('addstudent')
311    grok.template('studentaddpage')
312    form_fields = grok.AutoFields(IStudent)
313    title = 'Students'
314    label = 'Add student'
[6642]315    pnav = 4
[6622]316
317    @grok.action('Create student record')
318    def addStudent(self, **data):
319        student = createObject(u'waeup.Student')
320        self.applyData(student, **data)
[6652]321        self.context.addStudent(student)
[6626]322        self.flash('Student record created.')
[6651]323        self.redirect(self.url(self.context[student.student_id], 'index'))
[6622]324        return
325
[6631]326class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
327    """ Page to display student base data
328    """
[6622]329    grok.context(IStudent)
330    grok.name('index')
[6660]331    grok.require('waeup.viewStudent')
[6695]332    grok.template('basepage')
[6756]333    form_fields = grok.AutoFields(IStudentBase).omit('password')
[6642]334    pnav = 4
335    title = 'Base Data'
[6622]336
337    @property
338    def label(self):
[6818]339        return '%s: Base Data' % self.context.fullname
[6631]340
[6699]341    @property
342    def hasPassword(self):
343        if self.context.password:
344            return 'set'
345        return 'unset'
346
[6631]347class StudentBaseManageActionButton(ManageActionButton):
348    grok.order(1)
349    grok.context(IStudent)
350    grok.view(StudentBaseDisplayFormPage)
351    grok.require('waeup.manageStudents')
[6695]352    text = 'Manage'
[6631]353    target = 'edit_base'
354
355class StudentBaseManageFormPage(WAeUPEditFormPage):
356    """ View to edit student base data
357    """
358    grok.context(IStudent)
359    grok.name('edit_base')
360    grok.require('waeup.manageStudents')
361    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
[6695]362    grok.template('basemanagepage')
363    label = 'Manage base data'
[6642]364    title = 'Base Data'
365    pnav = 4
[6631]366
[6638]367    def update(self):
368        datepicker.need() # Enable jQuery datepicker in date fields.
369        super(StudentBaseManageFormPage, self).update()
370        self.wf_info = IWorkflowInfo(self.context)
371        return
372
373    def getTransitions(self):
374        """Return a list of dicts of allowed transition ids and titles.
375
376        Each list entry provides keys ``name`` and ``title`` for
377        internal name and (human readable) title of a single
378        transition.
379        """
380        allowed_transitions = self.wf_info.getManualTransitions()
381        return [dict(name='', title='No transition')] +[
382            dict(name=x, title=y) for x, y in allowed_transitions]
383
384    @grok.action('Save')
385    def save(self, **data):
[6701]386        form = self.request.form
[6790]387        password = form.get('password', None)
388        password_ctl = form.get('control_password', None)
389        if password:
390            if (password != password_ctl):
[6701]391                self.flash('Passwords do not match.')
[6790]392            else:
393                # XXX: This is too early. PW should only be saved if there
394                #      are no (other) errors left in form.
395                IUserAccount(self.context).setPassword(password)
[6943]396                write_log_message(self, 'password changed')
[6790]397
[6789]398        #self.reg_number = form.get('form.reg_number', None)
399        #if self.reg_number:
400        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
401        #    if hitlist and hitlist[0].student_id != self.context.student_id:
402        #        self.flash('Registration number exists.')
403        #        return
404        #self.matric_number = form.get('form.matric_number', None)
405        #if self.matric_number:
406        #    hitlist = search(query=self.matric_number,
407        #        searchtype='matric_number', view=self)
408        #    if hitlist and hitlist[0].student_id != self.context.student_id:
409        #        self.flash('Matriculation number exists.')
410        #        return
411
[6771]412        # Turn list of lists into single list
[6638]413        changed_fields = self.applyData(self.context, **data)
[6771]414        if changed_fields:
415            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
416        fields_string = ' + '.join(changed_fields)
[6638]417        self.context._p_changed = True
418        if form.has_key('transition') and form['transition']:
419            transition_id = form['transition']
420            self.wf_info.fireTransition(transition_id)
421        self.flash('Form has been saved.')
[6644]422        if fields_string:
[6943]423            write_log_message(self, 'saved: % s' % fields_string)
[6638]424        return
425
[6631]426class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
427    """ Page to display student clearance data
428    """
429    grok.context(IStudent)
430    grok.name('view_clearance')
[6660]431    grok.require('waeup.viewStudent')
[6695]432    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
[6650]433    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[6642]434    title = 'Clearance Data'
435    pnav = 4
[6631]436
437    @property
438    def label(self):
[6818]439        return '%s: Clearance Data' % self.context.fullname
[6631]440
441class StudentClearanceManageActionButton(ManageActionButton):
442    grok.order(1)
443    grok.context(IStudent)
444    grok.view(StudentClearanceDisplayFormPage)
445    grok.require('waeup.manageStudents')
[6695]446    text = 'Manage'
[6631]447    target = 'edit_clearance'
448
449class StudentClearanceManageFormPage(WAeUPEditFormPage):
450    """ Page to edit student clearance data
451    """
452    grok.context(IStudent)
453    grok.name('edit_clearance')
[6649]454    grok.require('waeup.manageStudents')
[6631]455    form_fields = grok.AutoFields(IStudentClearance)
[6695]456    label = 'Manage clearance data'
[6642]457    title = 'Clearance Data'
458    pnav = 4
[6631]459
[6650]460    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
461
462    def update(self):
463        datepicker.need() # Enable jQuery datepicker in date fields.
464        return super(StudentClearanceManageFormPage, self).update()
465
[6695]466    @grok.action('Save')
467    def save(self, **data):
[6762]468        msave(self, **data)
[6695]469        return
470
[6631]471class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
472    """ Page to display student personal data
473    """
474    grok.context(IStudent)
475    grok.name('view_personal')
[6660]476    grok.require('waeup.viewStudent')
[6631]477    form_fields = grok.AutoFields(IStudentPersonal)
[6642]478    title = 'Personal Data'
479    pnav = 4
[6631]480
481    @property
482    def label(self):
[6818]483        return '%s: Personal Data' % self.context.fullname
[6631]484
485class StudentPersonalManageActionButton(ManageActionButton):
486    grok.order(1)
487    grok.context(IStudent)
488    grok.view(StudentPersonalDisplayFormPage)
489    grok.require('waeup.manageStudents')
[6695]490    text = 'Manage'
[6631]491    target = 'edit_personal'
492
493class StudentPersonalManageFormPage(WAeUPEditFormPage):
494    """ Page to edit student clearance data
495    """
496    grok.context(IStudent)
497    grok.name('edit_personal')
[6660]498    grok.require('waeup.viewStudent')
[6631]499    form_fields = grok.AutoFields(IStudentPersonal)
[6695]500    label = 'Manage personal data'
[6642]501    title = 'Personal Data'
502    pnav = 4
[6631]503
[6762]504    @grok.action('Save')
505    def save(self, **data):
506        msave(self, **data)
507        return
508
[6635]509class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
510    """ Page to display the student study course data
511    """
512    grok.context(IStudentStudyCourse)
513    grok.name('index')
[6660]514    grok.require('waeup.viewStudent')
[6635]515    form_fields = grok.AutoFields(IStudentStudyCourse)
[6775]516    grok.template('studycoursepage')
[6642]517    title = 'Study Course'
518    pnav = 4
[6635]519
520    @property
521    def label(self):
[6818]522        return '%s: Study Course' % self.context.__parent__.fullname
[6635]523
[6912]524    @property
525    def current_mode(self):
526        return study_modes.getTermByToken(
527            self.context.certificate.study_mode).title
528
[6649]529class StudyCourseManageActionButton(ManageActionButton):
530    grok.order(1)
531    grok.context(IStudentStudyCourse)
532    grok.view(StudyCourseDisplayFormPage)
533    grok.require('waeup.manageStudents')
[6695]534    text = 'Manage'
[6775]535    target = 'manage'
[6649]536
537class StudyCourseManageFormPage(WAeUPEditFormPage):
538    """ Page to edit the student study course data
539    """
540    grok.context(IStudentStudyCourse)
[6775]541    grok.name('manage')
[6649]542    grok.require('waeup.manageStudents')
[6775]543    grok.template('studycoursemanagepage')
[6649]544    form_fields = grok.AutoFields(IStudentStudyCourse)
545    title = 'Study Course'
[6695]546    label = 'Manage study course'
[6649]547    pnav = 4
[6775]548    taboneactions = ['Save','Cancel']
549    tabtwoactions = ['Remove selected levels','Cancel']
550    tabthreeactions = ['Add study level']
[6649]551
[6775]552    def update(self):
553        super(StudyCourseManageFormPage, self).update()
554        tabs.need()
555        datatable.need()
556        return
557
[6761]558    @grok.action('Save')
559    def save(self, **data):
[6762]560        msave(self, **data)
[6761]561        return
562
[6775]563    @property
564    def level_dict(self):
565        studylevelsource = StudyLevelSource().factory
566        for code in studylevelsource.getValues(self.context):
567            title = studylevelsource.getTitle(self.context, code)
568            yield(dict(code=code, title=title))
569
570    @grok.action('Add study level')
[6774]571    def addStudyLevel(self, **data):
[6775]572        level_code = self.request.form.get('addlevel', None)
[6774]573        studylevel = StudentStudyLevel()
[6775]574        studylevel.level = int(level_code)
575        try:
[6782]576            self.context.addStudentStudyLevel(
577                self.context.certificate,studylevel)
[6775]578        except KeyError:
579            self.flash('This level exists.')
580        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
[6774]581        return
582
[6775]583    @grok.action('Remove selected levels')
584    def delStudyLevels(self, **data):
585        form = self.request.form
586        if form.has_key('val_id'):
587            child_id = form['val_id']
588        else:
589            self.flash('No study level selected.')
590            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
591            return
592        if not isinstance(child_id, list):
593            child_id = [child_id]
594        deleted = []
595        for id in child_id:
596            try:
597                del self.context[id]
598                deleted.append(id)
599            except:
600                self.flash('Could not delete %s: %s: %s' % (
601                        id, sys.exc_info()[0], sys.exc_info()[1]))
602        if len(deleted):
603            self.flash('Successfully removed: %s' % ', '.join(deleted))
604        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
605        return
[6774]606
607class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
608    """ Page to display student study levels
609    """
610    grok.context(IStudentStudyLevel)
611    grok.name('index')
612    grok.require('waeup.viewStudent')
[6775]613    form_fields = grok.AutoFields(IStudentStudyLevel)
[6783]614    grok.template('studylevelpage')
[6774]615    pnav = 4
616
617    @property
[6792]618    def title(self):
619        return 'Study Level %s' % self.context.level_title
620
621    @property
[6774]622    def label(self):
[6776]623        return '%s: Study Level %s' % (
[6818]624            self.context.getStudent().fullname,self.context.level_title)
[6774]625
[6803]626    @property
627    def total_credits(self):
628        total_credits = 0
629        for key, val in self.context.items():
630            total_credits += val.credits
631        return total_credits
632
[6792]633class StudyLevelManageActionButton(ManageActionButton):
634    grok.order(1)
635    grok.context(IStudentStudyLevel)
636    grok.view(StudyLevelDisplayFormPage)
637    grok.require('waeup.manageStudents')
638    text = 'Manage'
639    target = 'manage'
640
641class StudyLevelManageFormPage(WAeUPEditFormPage):
642    """ Page to edit the student study level data
643    """
644    grok.context(IStudentStudyLevel)
645    grok.name('manage')
646    grok.require('waeup.manageStudents')
647    grok.template('studylevelmanagepage')
648    form_fields = grok.AutoFields(IStudentStudyLevel)
649    pnav = 4
650    taboneactions = ['Save','Cancel']
[6795]651    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
[6792]652
653    def update(self):
654        super(StudyLevelManageFormPage, self).update()
655        tabs.need()
656        datatable.need()
657        return
658
659    @property
660    def title(self):
661        return 'Study Level %s' % self.context.level_title
662
663    @property
664    def label(self):
665        return 'Manage study level %s' % self.context.level_title
666
667    @grok.action('Save')
668    def save(self, **data):
669        msave(self, **data)
670        return
671
672    @grok.action('Add course ticket')
[6795]673    def addCourseTicket(self, **data):
674        self.redirect(self.url(self.context, '@@add'))
[6792]675
676    @grok.action('Remove selected tickets')
677    def delCourseTicket(self, **data):
678        form = self.request.form
679        if form.has_key('val_id'):
680            child_id = form['val_id']
681        else:
682            self.flash('No ticket selected.')
683            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
684            return
685        if not isinstance(child_id, list):
686            child_id = [child_id]
687        deleted = []
688        for id in child_id:
689            try:
690                del self.context[id]
691                deleted.append(id)
692            except:
693                self.flash('Could not delete %s: %s: %s' % (
694                        id, sys.exc_info()[0], sys.exc_info()[1]))
695        if len(deleted):
696            self.flash('Successfully removed: %s' % ', '.join(deleted))
697        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
698        return
699
[6795]700class CourseTicketAddFormPage(WAeUPAddFormPage):
[6808]701    """Add a course ticket.
[6795]702    """
703    grok.context(IStudentStudyLevel)
704    grok.name('add')
705    grok.require('waeup.manageStudents')
706    label = 'Add course ticket'
[6808]707    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
708        'grade', 'score', 'automatic')
[6795]709    pnav = 4
710
711    @property
712    def title(self):
713        return 'Study Level %s' % self.context.level_title
714
715    @grok.action('Add course ticket')
716    def addCourseTicket(self, **data):
717        ticket = CourseTicket()
718        course = data['course']
719        ticket.core_or_elective = data['core_or_elective']
[6802]720        ticket.automatic = False
[6795]721        ticket.code = course.code
722        ticket.title = course.title
723        ticket.faculty = course.__parent__.__parent__.__parent__.title
724        ticket.department = course.__parent__.__parent__.title
725        ticket.credits = course.credits
726        ticket.passmark = course.passmark
727        ticket.semester = course.semester
728        try:
729            self.context.addCourseTicket(ticket)
730        except KeyError:
731            self.flash('The ticket exists.')
732            return
[6799]733        self.flash('Successfully added %s.' % ticket.code)
[6795]734        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
735        return
736
737    @grok.action('Cancel')
738    def cancel(self, **data):
739        self.redirect(self.url(self.context))
740
[6796]741class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
742    """ Page to display course tickets
743    """
744    grok.context(ICourseTicket)
745    grok.name('index')
746    grok.require('waeup.viewStudent')
747    form_fields = grok.AutoFields(ICourseTicket)
748    grok.template('courseticketpage')
749    pnav = 4
750
751    @property
752    def title(self):
753        return 'Course Ticket %s' % self.context.code
754
755    @property
756    def label(self):
757        return '%s: Course Ticket %s' % (
[6818]758            self.context.getStudent().fullname,self.context.code)
[6796]759
760class CourseTicketManageActionButton(ManageActionButton):
761    grok.order(1)
762    grok.context(ICourseTicket)
763    grok.view(CourseTicketDisplayFormPage)
764    grok.require('waeup.manageStudents')
765    text = 'Manage'
766    target = 'manage'
767
768class CourseTicketManageFormPage(WAeUPEditFormPage):
769    """ Page to manage course tickets
770    """
771    grok.context(ICourseTicket)
772    grok.name('manage')
773    grok.require('waeup.manageStudents')
774    form_fields = grok.AutoFields(ICourseTicket)
775    grok.template('courseticketmanagepage')
776    pnav = 4
777
778    @property
779    def title(self):
780        return 'Course Ticket %s' % self.context.code
781
782    @property
783    def label(self):
784        return 'Manage course ticket %s' % self.context.code
785
786    @grok.action('Save')
787    def save(self, **data):
788        msave(self, **data)
789        return
790
[6940]791# We don't need the display form page yet
[6943]792#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
793#    """ Page to display the student payments
794#    """
795#    grok.context(IStudentPaymentsContainer)
796#    grok.name('view')
797#    grok.require('waeup.viewStudent')
798#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
799#    grok.template('paymentspage')
800#    title = 'Payments'
801#    pnav = 4
[6635]802
[6943]803#    def formatDatetime(self,datetimeobj):
804#        if isinstance(datetimeobj, datetime):
805#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
806#        else:
807#            return None
[6869]808
[6943]809#    @property
810#    def label(self):
811#        return '%s: Payments' % self.context.__parent__.fullname
[6635]812
[6943]813#    def update(self):
814#        super(PaymentsDisplayFormPage, self).update()
815#        datatable.need()
816#        return
[6869]817
[6940]818# This manage form page is for both students and students officers.
[6869]819class PaymentsManageFormPage(WAeUPEditFormPage):
820    """ Page to manage the student payments
821    """
822    grok.context(IStudentPaymentsContainer)
[6940]823    grok.name('index')
824    grok.require('waeup.handleStudent')
[6869]825    form_fields = grok.AutoFields(IStudentPaymentsContainer)
826    grok.template('paymentsmanagepage')
827    title = 'Payments'
828    pnav = 4
829
[6940]830    def unremovable(self, ticket):
831        prm = get_principal_role_manager()
832        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
833        return ('waeup.Student' in roles and ticket.r_code)
834
[6869]835    def formatDatetime(self,datetimeobj):
836        if isinstance(datetimeobj, datetime):
837            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
838        else:
839            return None
840
841    @property
842    def label(self):
843        return '%s: Payments' % self.context.__parent__.fullname
844
845    def update(self):
846        super(PaymentsManageFormPage, self).update()
847        datatable.need()
848        return
849
850    @grok.action('Remove selected tickets')
851    def delPaymentTicket(self, **data):
852        form = self.request.form
853        if form.has_key('val_id'):
854            child_id = form['val_id']
855        else:
856            self.flash('No payment selected.')
[6940]857            self.redirect(self.url(self.context))
[6869]858            return
859        if not isinstance(child_id, list):
860            child_id = [child_id]
861        deleted = []
862        for id in child_id:
[6992]863            # Students are not allowed to remove used payment tickets
[6940]864            if not self.unremovable(self.context[id]):
865                try:
866                    del self.context[id]
867                    deleted.append(id)
868                except:
869                    self.flash('Could not delete %s: %s: %s' % (
870                            id, sys.exc_info()[0], sys.exc_info()[1]))
[6869]871        if len(deleted):
872            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6943]873            write_log_message(self,'removed: % s' % ', '.join(deleted))
[6940]874        self.redirect(self.url(self.context))
[6869]875        return
876
877    @grok.action('Add online payment ticket')
878    def addPaymentTicket(self, **data):
879        self.redirect(self.url(self.context, '@@addop'))
880
[6940]881#class OnlinePaymentManageActionButton(ManageActionButton):
882#    grok.order(1)
883#    grok.context(IStudentPaymentsContainer)
884#    grok.view(PaymentsDisplayFormPage)
885#    grok.require('waeup.manageStudents')
886#    text = 'Manage payments'
887#    target = 'manage'
[6869]888
889class OnlinePaymentAddFormPage(WAeUPAddFormPage):
890    """ Page to add an online payment ticket
891    """
892    grok.context(IStudentPaymentsContainer)
893    grok.name('addop')
894    grok.require('waeup.handleStudent')
[6877]895    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
[6869]896        'p_category')
[6906]897    #zzgrok.template('addpaymentpage')
[6869]898    label = 'Add online payment'
899    title = 'Payments'
900    pnav = 4
[6947]901   
902    # To be sepezified in customization packages
903    def getPaymentDetails(self, category, student):
904        return getPaymentDetails(category, student)
[6869]905
906    @grok.action('Create ticket')
907    def createTicket(self, **data):
[6876]908        payment = createObject(u'waeup.StudentOnlinePayment')
[6869]909        self.applyData(payment, **data)
910        timestamp = "%d" % int(time()*1000)
911        #order_id = "%s%s" % (student_id[1:],timestamp)
912        payment.p_id = "p%s" % timestamp
[6994]913        pay_details  = self.getPaymentDetails(
[6876]914            data['p_category'],self.context.__parent__)
[6994]915        if pay_details['error']:
916            self.flash(pay_details['error'])
[6940]917            self.redirect(self.url(self.context))
[6920]918            return
[6994]919        payment.amount_auth = pay_details['amount']
920        payment.p_item = pay_details['p_item']
921        payment.p_session = pay_details['p_session']
922        payment.surcharge_1 = pay_details['surcharge_1']
923        payment.surcharge_2 = pay_details['surcharge_2']
924        payment.surcharge_3 = pay_details['surcharge_3']
[6869]925        self.context[payment.p_id] = payment
926        self.flash('Payment ticket created.')
[6940]927        self.redirect(self.url(self.context))
[6869]928        return
929
930class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
931    """ Page to view an online payment ticket
932    """
[6877]933    grok.context(IStudentOnlinePayment)
[6869]934    grok.name('index')
935    grok.require('waeup.viewStudent')
[6877]936    form_fields = grok.AutoFields(IStudentOnlinePayment)
[6869]937    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
938    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
939    pnav = 4
940
941    @property
942    def title(self):
943        return 'Online Payment Ticket %s' % self.context.p_id
944
945    @property
946    def label(self):
947        return '%s: Online Payment Ticket %s' % (
948            self.context.__parent__.__parent__.fullname,self.context.p_id)
949
[6930]950class OnlinePaymentCallbackPage(grok.View):
951    """ Callback view
952    """
953    grok.context(IStudentOnlinePayment)
954    grok.name('callback')
955    grok.require('waeup.payStudent')
956
957    # This update method simulates a valid callback und must be
958    # specified in the customization package. The parameters must be taken
959    # from the incoming request.
960    def update(self):
[6936]961        student = self.context.getStudent()
[6943]962        write_log_message(self,'valid callback: %s' % self.context.p_id)
[6930]963        self.context.r_amount_approved = self.context.amount_auth
964        self.context.r_card_num = u'0000'
965        self.context.r_code = u'00'
966        self.context.p_state = 'paid'
967        self.context.payment_date = datetime.now()
968        if self.context.p_category == 'clearance':
[6936]969            # Create CLR access code
[6937]970            pin, error = create_accesscode('CLR',0,student.student_id)
[6936]971            if error:
[6940]972                self.flash('Valid callback received. ' + error)
[6936]973                return
974            self.context.ac = pin
[6930]975        elif self.context.p_category == 'schoolfee':
[6936]976            # Create SFE access code
[6940]977            pin, error = create_accesscode('SFE',0,student.student_id)
[6936]978            if error:
[6940]979                self.flash('Valid callback received. ' + error)
[6936]980                return
981            self.context.ac = pin
[6994]982        elif self.context.p_category == 'bed_allocation':
983            # Create HOS access code
984            pin, error = create_accesscode('HOS',0,student.student_id)
985            if error:
986                self.flash('Valid callback received. ' + error)
987                return
988            self.context.ac = pin
[6940]989        self.flash('Valid callback received.')
990        return
[6930]991
992    def render(self):
[6940]993        self.redirect(self.url(self.context, '@@index'))
[6930]994        return
995
[6992]996# We don't need the display form page yet
997#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
998#    """ Page to display the student accommodation data
999#    """
1000#    grok.context(IStudentAccommodation)
1001#    grok.name('xxx')
1002#    grok.require('waeup.viewStudent')
1003#    form_fields = grok.AutoFields(IStudentAccommodation)
1004#    #grok.template('accommodationpage')
1005#    title = 'Accommodation'
1006#    pnav = 4
1007
1008#    @property
1009#    def label(self):
1010#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1011
1012# This manage form page is for both students and students officers.
1013class AccommodationManageFormPage(WAeUPEditFormPage):
1014    """ Page to manage the bed tickets
[6635]1015    """
1016    grok.context(IStudentAccommodation)
1017    grok.name('index')
[6992]1018    grok.require('waeup.handleStudent')
[6635]1019    form_fields = grok.AutoFields(IStudentAccommodation)
[6992]1020    grok.template('accommodationmanagepage')
[6642]1021    title = 'Accommodation'
1022    pnav = 4
[6635]1023
[6992]1024    def unremovable(self):
1025        prm = get_principal_role_manager()
1026        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1027        return ('waeup.Student' in roles)
1028
1029    def formatDatetime(self,datetimeobj):
1030        if isinstance(datetimeobj, datetime):
1031            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1032        else:
1033            return None
1034
[6635]1035    @property
1036    def label(self):
[6992]1037        return '%s: Accommodation' % self.context.__parent__.fullname
[6637]1038
[6992]1039    def update(self):
1040        super(AccommodationManageFormPage, self).update()
1041        datatable.need()
1042        return
1043
1044    # We need an event handler which releases the bed space too.
1045    @grok.action('Remove selected tickets')
1046    def delBedTicket(self, **data):
1047        form = self.request.form
1048        if form.has_key('val_id'):
1049            child_id = form['val_id']
1050        else:
1051            self.flash('No bed ticket selected.')
1052            self.redirect(self.url(self.context))
1053            return
1054        if not isinstance(child_id, list):
1055            child_id = [child_id]
1056        deleted = []
1057        for id in child_id:
1058            # Students are not allowed to remove bed tickets
1059            if not self.unremovable():
1060                try:
1061                    del self.context[id]
1062                    deleted.append(id)
1063                except:
1064                    self.flash('Could not delete %s: %s: %s' % (
1065                            id, sys.exc_info()[0], sys.exc_info()[1]))
1066        if len(deleted):
1067            self.flash('Successfully removed: %s' % ', '.join(deleted))
1068            write_log_message(self,'removed: % s' % ', '.join(deleted))
1069        self.redirect(self.url(self.context))
1070        return
1071
1072    @grok.action('Add bed ticket')
1073    def addBedTicket(self, **data):
1074        self.redirect(self.url(self.context, '@@add'))
1075
1076class BedTicketAddPage(WAeUPPage):
1077    """ Page to add an online payment ticket
1078    """
1079    grok.context(IStudentAccommodation)
1080    grok.name('add')
1081    grok.require('waeup.handleStudent')
1082    grok.template('enterpin')
[6993]1083    ac_prefix = 'HOS'
[6992]1084    label = 'Add bed ticket'
1085    title = 'Add bed ticket'
1086    pnav = 4
1087    buttonname = 'Create bed ticket'
[6993]1088    notice = ''
[6992]1089
1090    # To be sepezified in customization packages
1091    def getAccommodationDetails(self, student):
1092        return getAccommodationDetails(student)
1093
[7004]1094    # To be sepezified in customization packages
1095    def selectBed(self, student):
1096        return getAccommodationDetails(student)
1097
[6992]1098    def update(self, SUBMIT=None):
[6996]1099        student = self.context.getStudent()
1100        acc_details  = self.getAccommodationDetails(student)
1101        if not student.state in acc_details['allowed_states']:
[6992]1102            self.flash("Wrong state.")
1103            self.redirect(self.url(self.context))
1104            return
[7004]1105        if str(student['studycourse'].entry_session) in self.context.keys():
1106            self.flash('Ticket for %d already created.'
1107                % student['studycourse'].entry_session)
1108            self.redirect(self.url(self.context))
1109            return
[6992]1110        self.ac_series = self.request.form.get('ac_series', None)
1111        self.ac_number = self.request.form.get('ac_number', None)
1112        if SUBMIT is None:
1113            return
1114        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1115        code = get_access_code(pin)
1116        if not code:
1117            self.flash('Activation code is invalid.')
1118            return
[7003]1119        # Search a bed and exit if no bed is found or if the student
1120        # has already booked a bed
[6997]1121        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1122        entries = cat.searchResults(
1123            bed_type=(acc_details['bt'],acc_details['bt']))
1124        available_beds = [
1125            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1126        if not available_beds:
1127            self.flash('There is no free bed in your category %s.'
1128                        % acc_details['bt'])
1129            return
[7003]1130        entries = cat.searchResults(
1131            owner=(student.student_id,student.student_id))
[7004]1132        # Cannot happen but anyway ...
[7003]1133        if len(entries):
1134            bed = [entry for entry in entries][0]
1135            self.flash('You already booked bed %s.'
1136                        % bed.bed_id)
1137            return
[6992]1138        # Mark pin as used (this also fires a pin related transition)
1139        if code.state == USED:
1140            self.flash('Activation code has already been used.')
1141            return
1142        else:
1143            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1144            # Here we know that the ac is in state initialized so we do not
1145            # expect an exception, but the owner might be different
1146            if not invalidate_accesscode(
1147                pin,comment,self.context.getStudent().student_id):
1148                self.flash('You are not the owner of this access code.')
1149                return
[6997]1150        # Create bed ticket and book bed
[6992]1151        bedticket = createObject(u'waeup.BedTicket')
1152        bedticket.booking_code = pin
[6994]1153        bedticket.booking_session = acc_details['booking_session']
[6996]1154        bedticket.bed_type = acc_details['bt']
[7004]1155        bed = selectBed(available_beds)
[6996]1156        bed.bookBed(student.student_id)
[7006]1157        bedticket.bed = bed
[6996]1158        hall_title = bed.__parent__.hostel_name
1159        coordinates = bed.getBedCoordinates()[1:]
1160        block, room_nr, bed_nr = coordinates
1161        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1162            hall_title, block, room_nr, bed_nr)
1163        key = str(acc_details['booking_session'])
1164        self.context[key] = bedticket
1165        self.flash('Bed ticket created and bed booked: %s'
1166            % bedticket.bed_coordinates)
[6992]1167        self.redirect(self.url(self.context))
1168        return
1169
[6994]1170class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1171    """ Page to display bed tickets
1172    """
1173    grok.context(IBedTicket)
1174    grok.name('index')
1175    grok.require('waeup.viewStudent')
1176    form_fields = grok.AutoFields(IBedTicket)
1177    form_fields[
1178        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1179    pnav = 4
1180
1181    @property
1182    def label(self):
1183        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1184
1185    @property
1186    def title(self):
1187        return 'Bed Ticket %s' % self.context.getSessionString()
1188
[6637]1189class StudentHistoryPage(WAeUPPage):
1190    """ Page to display student clearance data
1191    """
1192    grok.context(IStudent)
1193    grok.name('history')
[6660]1194    grok.require('waeup.viewStudent')
[6637]1195    grok.template('studenthistory')
[6642]1196    title = 'History'
1197    pnav = 4
[6637]1198
1199    @property
1200    def label(self):
[6818]1201        return '%s: History' % self.context.fullname
[6694]1202
1203# Pages for students only
1204
1205class StudentBaseEditActionButton(ManageActionButton):
1206    grok.order(1)
1207    grok.context(IStudent)
1208    grok.view(StudentBaseDisplayFormPage)
1209    grok.require('waeup.handleStudent')
1210    text = 'Change password'
1211    target = 'bedit'
1212
[6756]1213class StudentPasswordSetting(grok.Adapter):
1214    """Adapt IStudent to data needed for password settings.
1215
1216    We provide password getters/setters for the attached context (an
1217    IStudent object) that cooperate seamless with the usual
1218    formlib/form techniques.
1219    """
1220    grok.context(IStudent)
1221    grok.provides(IStudentPasswordSetting)
1222
1223    def __init__(self, context):
[6818]1224        self.name = context.fullname
[6756]1225        self.password_repeat = context.password
1226        self.context = context
1227        return
1228
1229    def getPassword(self):
1230        return self.context.password
1231
1232    def setPassword(self, password):
1233        IUserAccount(self.context).setPassword(password)
1234        return
1235
1236    password = property(getPassword, setPassword)
1237
[6694]1238class StudentBaseEditFormPage(WAeUPEditFormPage):
1239    """ View to edit student base data by student
1240    """
1241    grok.context(IStudent)
1242    grok.name('bedit')
1243    grok.require('waeup.handleStudent')
[6756]1244    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1245    #    'student_id', 'reg_number', 'matric_number')
1246    form_fields = grok.AutoFields(IStudentPasswordSetting)
[6695]1247    grok.template('baseeditpage')
[6694]1248    label = 'Change password'
1249    title = 'Base Data'
1250    pnav = 4
1251
1252    def update(self):
1253        super(StudentBaseEditFormPage, self).update()
1254        self.wf_info = IWorkflowInfo(self.context)
1255        return
1256
[6756]1257    def onFailure(self, action, data, errors):
1258        new_status = []
1259        other_errors = False
1260        for error in errors:
1261            msg = getattr(error, 'message', '')
1262            if isinstance(msg, basestring) and msg != '':
1263                new_status.append(msg)
1264            else:
1265                other_errors = True
1266        if other_errors:
1267            if new_status:
1268                new_status.append('see below for further errors')
1269            else:
1270                new_status.append('See below for details.')
1271        if new_status:
1272            self.status = u'There were errors: %s' % ', '.join(new_status)
1273        return
1274
1275    @grok.action('Save', failure=onFailure)
[6694]1276    def save(self, **data):
[6771]1277        self.applyData(self.context, **data)
1278        self.flash('Form has been saved.')
[6694]1279        return
[6695]1280
[6719]1281class StudentClearanceStartActionButton(ManageActionButton):
1282    grok.order(1)
1283    grok.context(IStudent)
1284    grok.view(StudentClearanceDisplayFormPage)
1285    grok.require('waeup.handleStudent')
1286    icon = 'actionicon_start.png'
1287    text = 'Start clearance'
1288    target = 'start_clearance'
1289
1290    @property
1291    def target_url(self):
1292        if self.context.state != 'admitted':
1293            return ''
1294        return self.view.url(self.view.context, self.target)
1295
[6773]1296class StartClearancePage(WAeUPPage):
[6770]1297    grok.context(IStudent)
1298    grok.name('start_clearance')
1299    grok.require('waeup.handleStudent')
1300    grok.template('enterpin')
1301    title = 'Start clearance'
1302    label = 'Start clearance'
1303    ac_prefix = 'CLR'
1304    notice = ''
1305    pnav = 4
1306    buttonname = 'Start clearance now'
1307
1308    def update(self, SUBMIT=None):
[6936]1309        if not self.context.state == 'admitted':
1310            self.flash("Wrong state.")
1311            self.redirect(self.url(self.context))
1312            return
[6770]1313        self.ac_series = self.request.form.get('ac_series', None)
1314        self.ac_number = self.request.form.get('ac_number', None)
1315
1316        if SUBMIT is None:
1317            return
1318        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1319        code = get_access_code(pin)
1320        if not code:
[6936]1321            self.flash('Activation code is invalid.')
[6770]1322            return
1323        # Mark pin as used (this also fires a pin related transition)
1324        # and fire transition start_clearance
1325        if code.state == USED:
[6936]1326            self.flash('Activation code has already been used.')
[6770]1327            return
1328        else:
1329            comment = u"AC invalidated for %s" % self.context.student_id
1330            # Here we know that the ac is in state initialized so we do not
[6927]1331            # expect an exception, but the owner might be different
1332            if not invalidate_accesscode(pin,comment,self.context.student_id):
1333                self.flash('You are not the owner of this access code.')
1334                return
[6770]1335            self.context.clr_code = pin
1336        IWorkflowInfo(self.context).fireTransition('start_clearance')
[6771]1337        self.flash('Clearance process has been started.')
[6770]1338        self.redirect(self.url(self.context,'cedit'))
1339        return
1340
[6695]1341class StudentClearanceEditActionButton(ManageActionButton):
1342    grok.order(1)
1343    grok.context(IStudent)
1344    grok.view(StudentClearanceDisplayFormPage)
1345    grok.require('waeup.handleStudent')
[6722]1346    text = 'Edit'
[6695]1347    target = 'cedit'
1348
[6717]1349    @property
1350    def target_url(self):
1351        if self.context.clearance_locked:
1352            return ''
1353        return self.view.url(self.view.context, self.target)
1354
[6695]1355class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1356    """ View to edit student clearance data by student
1357    """
1358    grok.context(IStudent)
1359    grok.name('cedit')
1360    grok.require('waeup.handleStudent')
[6756]1361    form_fields = grok.AutoFields(
1362        IStudentClearanceEdit).omit('clearance_locked')
[6722]1363    label = 'Edit clearance data'
[6695]1364    title = 'Clearance Data'
1365    pnav = 4
1366    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6718]1367
1368    def emitLockMessage(self):
1369        self.flash('The requested form is locked (read-only).')
1370        self.redirect(self.url(self.context))
1371        return
1372
1373    def update(self):
1374        if self.context.clearance_locked:
1375            self.emitLockMessage()
1376            return
1377        datepicker.need()
1378        return super(StudentClearanceEditFormPage, self).update()
[6719]1379
[6722]1380    @grok.action('Save')
1381    def save(self, **data):
1382        self.applyData(self.context, **data)
[6771]1383        self.flash('Clearance form has been saved.')
[6722]1384        return
1385
1386    @grok.action('Save and request clearance')
1387    def requestclearance(self, **data):
1388        self.applyData(self.context, **data)
1389        self.context._p_changed = True
1390        #if self.dataNotComplete():
1391        #    self.flash(self.dataNotComplete())
1392        #    return
[6771]1393        self.flash('Clearance form has been saved.')
[6769]1394        self.redirect(self.url(self.context,'request_clearance'))
[6722]1395        return
1396
[6773]1397class RequestClearancePage(WAeUPPage):
[6769]1398    grok.context(IStudent)
1399    grok.name('request_clearance')
1400    grok.require('waeup.handleStudent')
1401    grok.template('enterpin')
1402    title = 'Request clearance'
1403    label = 'Request clearance'
1404    notice = 'Enter the CLR access code used for starting clearance.'
1405    ac_prefix = 'CLR'
1406    pnav = 4
1407    buttonname = 'Request clearance now'
1408
1409    def update(self, SUBMIT=None):
1410        self.ac_series = self.request.form.get('ac_series', None)
1411        self.ac_number = self.request.form.get('ac_number', None)
1412        if SUBMIT is None:
1413            return
1414        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1415        if self.context.clr_code != pin:
1416            self.flash("This isn't your CLR access code.")
1417            return
1418        state = IWorkflowState(self.context).getState()
1419        # This shouldn't happen, but the application officer
1420        # might have forgotten to lock the form after changing the state
1421        if state != CLEARANCE:
1422            self.flash('This form cannot be submitted. Wrong state!')
1423            return
1424        IWorkflowInfo(self.context).fireTransition('request_clearance')
1425        self.flash('Clearance has been requested.')
1426        self.redirect(self.url(self.context))
[6789]1427        return
[6806]1428
[6944]1429class CourseRegistrationStartActionButton(ManageActionButton):
1430    grok.order(1)
1431    grok.context(IStudentStudyCourse)
1432    grok.view(StudyCourseDisplayFormPage)
1433    grok.require('waeup.handleStudent')
1434    icon = 'actionicon_start.png'
1435    text = 'Start course registration'
1436    target = 'start_course_registration'
1437
1438    @property
1439    def target_url(self):
1440        if not self.context.getStudent().state in (CLEARED,RETURNING):
1441            return ''
1442        return self.view.url(self.view.context, self.target)
1443
1444class StartCourseRegistrationPage(WAeUPPage):
1445    grok.context(IStudentStudyCourse)
1446    grok.name('start_course_registration')
1447    grok.require('waeup.handleStudent')
1448    grok.template('enterpin')
1449    title = 'Start course registration'
1450    label = 'Start course registration'
1451    ac_prefix = 'SFE'
1452    notice = ''
1453    pnav = 4
1454    buttonname = 'Start course registration now'
1455
1456    def update(self, SUBMIT=None):
1457        if not self.context.getStudent().state in (CLEARED,RETURNING):
1458            self.flash("Wrong state.")
1459            self.redirect(self.url(self.context))
1460            return
1461        self.ac_series = self.request.form.get('ac_series', None)
1462        self.ac_number = self.request.form.get('ac_number', None)
1463
1464        if SUBMIT is None:
1465            return
1466        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1467        code = get_access_code(pin)
1468        if not code:
1469            self.flash('Activation code is invalid.')
1470            return
1471        # Mark pin as used (this also fires a pin related transition)
1472        # and fire transition start_clearance
1473        if code.state == USED:
1474            self.flash('Activation code has already been used.')
1475            return
1476        else:
1477            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1478            # Here we know that the ac is in state initialized so we do not
1479            # expect an exception, but the owner might be different
1480            if not invalidate_accesscode(
1481                pin,comment,self.context.getStudent().student_id):
1482                self.flash('You are not the owner of this access code.')
1483                return
1484        if self.context.getStudent().state == CLEARED:
1485            IWorkflowInfo(self.context.getStudent()).fireTransition(
1486                'pay_first_school_fee')
1487        elif self.context.getStudent().state == RETURNING:
1488            IWorkflowInfo(self.context.getStudent()).fireTransition(
1489                'pay_school_fee')
1490        self.flash('Course registration has been started.')
1491        self.redirect(self.url(self.context))
1492        return
1493
1494
[6808]1495class AddStudyLevelActionButton(AddActionButton):
1496    grok.order(1)
1497    grok.context(IStudentStudyCourse)
1498    grok.view(StudyCourseDisplayFormPage)
1499    grok.require('waeup.handleStudent')
1500    text = 'Add course list'
1501    target = 'add'
1502
1503    @property
1504    def target_url(self):
1505        student = self.view.context.getStudent()
1506        condition1 = student.state != 'school fee paid'
1507        condition2 = str(student['studycourse'].current_level) in \
1508            self.view.context.keys()
1509        if condition1 or condition2:
1510            return ''
1511        return self.view.url(self.view.context, self.target)
1512
[6806]1513class AddStudyLevelFormPage(WAeUPEditFormPage):
1514    """ Page for students to add current study levels
1515    """
1516    grok.context(IStudentStudyCourse)
1517    grok.name('add')
1518    grok.require('waeup.handleStudent')
1519    grok.template('studyleveladdpage')
1520    form_fields = grok.AutoFields(IStudentStudyCourse)
1521    title = 'Study Course'
1522    pnav = 4
1523
1524    @property
1525    def label(self):
1526        studylevelsource = StudyLevelSource().factory
1527        code = self.context.current_level
1528        title = studylevelsource.getTitle(self.context, code)
1529        return 'Add current level %s' % title
1530
1531    def emitLockMessage(self):
1532        self.flash('The requested form is locked (read-only).')
1533        self.redirect(self.url(self.context))
1534        return
1535
1536    def update(self):
1537        if self.context.getStudent().state != 'school fee paid':
1538            self.emitLockMessage()
1539            return
1540        super(AddStudyLevelFormPage, self).update()
1541        return
1542
1543    @grok.action('Create course list now')
1544    def addStudyLevel(self, **data):
1545        studylevel = StudentStudyLevel()
1546        studylevel.level = self.context.current_level
1547        studylevel.level_session = self.context.current_session
1548        try:
1549            self.context.addStudentStudyLevel(
1550                self.context.certificate,studylevel)
1551        except KeyError:
1552            self.flash('This level exists.')
1553        self.redirect(self.url(self.context))
1554        return
[6808]1555
1556class StudyLevelEditActionButton(ManageActionButton):
1557    grok.order(1)
1558    grok.context(IStudentStudyLevel)
1559    grok.view(StudyLevelDisplayFormPage)
1560    grok.require('waeup.handleStudent')
1561    text = 'Add and remove courses'
1562    target = 'edit'
1563
1564    @property
1565    def target_url(self):
1566        student = self.view.context.getStudent()
1567        condition1 = student.state != 'school fee paid'
1568        condition2 = student[
1569            'studycourse'].current_level != self.view.context.level
1570        if condition1 or condition2:
1571            return ''
1572        return self.view.url(self.view.context, self.target)
1573
1574class StudyLevelEditFormPage(WAeUPEditFormPage):
1575    """ Page to edit the student study level data by students
1576    """
1577    grok.context(IStudentStudyLevel)
1578    grok.name('edit')
1579    grok.require('waeup.handleStudent')
1580    grok.template('studyleveleditpage')
1581    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1582        'level_session', 'level_verdict')
1583    pnav = 4
1584
1585    def update(self):
1586        super(StudyLevelEditFormPage, self).update()
1587    #    tabs.need()
1588        datatable.need()
1589        return
1590
1591    @property
1592    def title(self):
1593        return 'Study Level %s' % self.context.level_title
1594
1595    @property
1596    def label(self):
1597        return 'Add and remove course tickets of study level %s' % self.context.level_title
1598
1599    @property
1600    def total_credits(self):
1601        total_credits = 0
1602        for key, val in self.context.items():
1603            total_credits += val.credits
1604        return total_credits
1605
1606    @grok.action('Add course ticket')
1607    def addCourseTicket(self, **data):
1608        self.redirect(self.url(self.context, 'ctadd'))
1609
1610    @grok.action('Remove selected tickets')
1611    def delCourseTicket(self, **data):
1612        form = self.request.form
1613        if form.has_key('val_id'):
1614            child_id = form['val_id']
1615        else:
1616            self.flash('No ticket selected.')
1617            self.redirect(self.url(self.context, '@@edit'))
1618            return
1619        if not isinstance(child_id, list):
1620            child_id = [child_id]
1621        deleted = []
1622        for id in child_id:
[6940]1623            # Students are not allowed to remove core tickets
[6808]1624            if not self.context[id].core_or_elective:
1625                try:
1626                    del self.context[id]
1627                    deleted.append(id)
1628                except:
1629                    self.flash('Could not delete %s: %s: %s' % (
1630                            id, sys.exc_info()[0], sys.exc_info()[1]))
1631        if len(deleted):
1632            self.flash('Successfully removed: %s' % ', '.join(deleted))
1633        self.redirect(self.url(self.context, u'@@edit'))
1634        return
1635
[6810]1636    @grok.action('Register course list')
1637    def register_courses(self, **data):
1638        state = IWorkflowState(self.context.getStudent()).getState()
1639        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1640        self.flash('Course list has been registered.')
1641        self.redirect(self.url(self.context))
1642        return
1643
[6808]1644class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1645    """Add a course ticket by student.
1646    """
1647    grok.name('ctadd')
1648    grok.require('waeup.handleStudent')
1649    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1650        'grade', 'score', 'core_or_elective', 'automatic')
1651
1652    @grok.action('Add course ticket')
1653    def addCourseTicket(self, **data):
1654        ticket = CourseTicket()
1655        course = data['course']
1656        ticket.automatic = False
1657        ticket.code = course.code
1658        ticket.title = course.title
1659        ticket.faculty = course.__parent__.__parent__.__parent__.title
1660        ticket.department = course.__parent__.__parent__.title
1661        ticket.credits = course.credits
1662        ticket.passmark = course.passmark
1663        ticket.semester = course.semester
1664        try:
1665            self.context.addCourseTicket(ticket)
1666        except KeyError:
1667            self.flash('The ticket exists.')
1668            return
1669        self.flash('Successfully added %s.' % ticket.code)
1670        self.redirect(self.url(self.context, u'@@edit'))
1671        return
Note: See TracBrowser for help on using the repository browser.