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

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

Prepare selection of bed for customization.

  • Property svn:keywords set to Id
File size: 56.6 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"""
18import grok
[6869]19from time import time
20from datetime import date, datetime
[6996]21from zope.catalog.interfaces import ICatalog
22from zope.component import queryUtility
[6621]23from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6760]24from zope.component import createObject
[6936]25from waeup.sirp.accesscodes import (
[6937]26    invalidate_accesscode, get_access_code, create_accesscode)
[6621]27from waeup.sirp.accesscodes.workflow import USED
28from waeup.sirp.browser import (
29    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
30from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6775]31from waeup.sirp.browser.resources import datepicker, datatable, tabs
[6621]32from waeup.sirp.browser.viewlets import (
[6760]33    ManageActionButton, PrimaryNavTab, AddActionButton)
34from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
[6621]35from waeup.sirp.widgets.datewidget import (
[6869]36    FriendlyDateWidget, FriendlyDateDisplayWidget,
37    FriendlyDatetimeDisplayWidget)
[6912]38from waeup.sirp.university.vocabularies import study_modes
[6621]39from waeup.sirp.students.interfaces import (
[6756]40    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
[6859]41    IStudentPersonal, IStudentBase, IStudentStudyCourse,
[6774]42    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
[6877]43    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
[6992]44    IStudentOnlinePayment, IBedTicket
[6621]45    )
[6626]46from waeup.sirp.students.catalog import search
[6992]47from waeup.sirp.students.workflow import (
48    CLEARANCE, RETURNING, CLEARED, REGISTERED, VALIDATED)
[6795]49from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
[6775]50from waeup.sirp.students.vocabularies import StudyLevelSource
[6996]51from waeup.sirp.students.utils import (
[7004]52    getPaymentDetails, getAccommodationDetails, selectBed)
[6820]53from waeup.sirp.browser.resources import toggleall
[6940]54from waeup.sirp.authentication import get_principal_role_manager
[6997]55from waeup.sirp.hostels.hostel import NOT_OCCUPIED
[6621]56
[6943]57def write_log_message(view, message):
58    ob_class = view.__implemented__.__name__.replace('waeup.sirp.','')
59    view.context.getStudent().loggerInfo(ob_class, message)
60    return
61
[6762]62# Save function used for save methods in manager pages
63def msave(view, **data):
64    form = view.request.form
65    changed_fields = view.applyData(view.context, **data)
[6771]66    # Turn list of lists into single list
67    if changed_fields:
68        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
69    fields_string = ' + '.join(changed_fields)
[6762]70    view.context._p_changed = True
71    view.flash('Form has been saved.')
72    if fields_string:
[6943]73        write_log_message(view, 'saved: % s' % fields_string)
[6762]74    return
75
[6621]76class StudentsTab(PrimaryNavTab):
77    """Students tab in primary navigation.
78    """
79
80    grok.context(IWAeUPObject)
[6953]81    grok.order(4)
[6660]82    grok.require('waeup.viewStudent')
[6621]83    grok.template('primarynavtab')
84
85    pnav = 4
86    tab_title = u'Students'
87
88    @property
89    def link_target(self):
90        return self.view.application_url('students')
91
[6629]92class StudentsBreadcrumb(Breadcrumb):
93    """A breadcrumb for the students container.
94    """
95    grok.context(IStudentsContainer)
96    title = u'Students'
97
[6818]98class StudentBreadcrumb(Breadcrumb):
99    """A breadcrumb for the student container.
100    """
101    grok.context(IStudent)
102
103    def title(self):
104        return self.context.fullname
105
[6635]106class SudyCourseBreadcrumb(Breadcrumb):
107    """A breadcrumb for the student study course.
108    """
109    grok.context(IStudentStudyCourse)
110    title = u'Study Course'
111
112class PaymentsBreadcrumb(Breadcrumb):
113    """A breadcrumb for the student payments folder.
114    """
[6859]115    grok.context(IStudentPaymentsContainer)
[6635]116    title = u'Payments'
117
[6870]118class OnlinePaymentBreadcrumb(Breadcrumb):
119    """A breadcrumb for course lists.
120    """
[6877]121    grok.context(IStudentOnlinePayment)
[6870]122
123    @property
124    def title(self):
125        return self.context.p_id
126
[6635]127class AccommodationBreadcrumb(Breadcrumb):
128    """A breadcrumb for the student accommodation folder.
129    """
130    grok.context(IStudentAccommodation)
131    title = u'Accommodation'
132
[6994]133class BedTicketBreadcrumb(Breadcrumb):
134    """A breadcrumb for bed tickets.
135    """
136    grok.context(IBedTicket)
137    @property
138
139    def title(self):
140        return 'Bed Ticket %s' % self.context.getSessionString()
141
[6776]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
[6626]151class StudentsContainerPage(WAeUPPage):
152    """The standard view for student containers.
[6621]153    """
154    grok.context(IStudentsContainer)
155    grok.name('index')
[6660]156    grok.require('waeup.viewStudent')
[6695]157    grok.template('containerpage')
[6654]158    label = 'Student Section'
159    title = 'Students'
[6642]160    pnav = 4
[6621]161
[6626]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        self.hitlist = search(query=self.searchterm,
177            searchtype=self.searchtype, view=self)
178        if not self.hitlist:
179            self.flash('No student found.')
180        return
181
[6773]182class SetPasswordPage(WAeUPPage):
[6699]183    grok.context(IWAeUPObject)
184    grok.name('setpassword')
185    grok.require('waeup.Public')
[6774]186    grok.template('setpassword')
[6699]187    title = ''
188    label = 'Set password for first-time login'
[6758]189    ac_prefix = 'PWD'
[6715]190    pnav = 0
[6699]191
192    def update(self, SUBMIT=None):
[6758]193        self.reg_number = self.request.form.get('reg_number', None)
194        self.ac_series = self.request.form.get('ac_series', None)
195        self.ac_number = self.request.form.get('ac_number', None)
[6756]196
[6699]197        if SUBMIT is None:
198            return
199        hitlist = search(query=self.reg_number,
200            searchtype='reg_number', view=self)
201        if not hitlist:
202            self.flash('No student found.')
203            return
204        if len(hitlist) != 1:   # Cannot happen but anyway
205            self.flash('More than one student found.')
206            return
[6704]207        student = hitlist[0].context
208        self.student_id = student.student_id
209        student_pw = student.password
[6758]210        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
[6699]211        code = get_access_code(pin)
212        if not code:
213            self.flash('Access code is invalid.')
214            return
[6704]215        if student_pw and pin == student.adm_code:
216            self.flash('Password has already been set. Your Student Id is %s'
217                % self.student_id)
218            return
219        elif student_pw:
[6800]220            self.flash('Password has already been set. You are using the wrong Access Code.')
[6704]221            return
[6699]222        # Mark pin as used (this also fires a pin related transition)
223        # and set student password
224        if code.state == USED:
225            self.flash('Access code has already been used.')
226            return
227        else:
[6704]228            comment = u"AC invalidated for %s" % self.student_id
[6699]229            # Here we know that the ac is in state initialized so we do not
230            # expect an exception
231            #import pdb; pdb.set_trace()
232            invalidate_accesscode(pin,comment)
[6758]233            IUserAccount(student).setPassword(self.ac_number)
[6769]234            student.adm_code = pin
[6704]235        self.flash('Password has been set. Your Student Id is %s'
236            % self.student_id)
[6699]237        return
238
[6626]239class StudentsContainerManageActionButton(ManageActionButton):
[6622]240    grok.order(1)
241    grok.context(IStudentsContainer)
242    grok.view(StudentsContainerPage)
243    grok.require('waeup.manageStudents')
[6647]244    text = 'Manage student section'
[6622]245
[6626]246
247class StudentsContainerManagePage(WAeUPPage):
248    """The manage page for student containers.
[6622]249    """
250    grok.context(IStudentsContainer)
251    grok.name('manage')
252    grok.require('waeup.manageStudents')
[6695]253    grok.template('containermanagepage')
[6642]254    pnav = 4
[6647]255    title = 'Manage student section'
[6622]256
257    @property
258    def label(self):
259        return self.title
260
[6626]261    def update(self, *args, **kw):
262        datatable.need()
[6820]263        toggleall.need()
[6626]264        form = self.request.form
265        self.hitlist = []
266        if 'searchterm' in form and form['searchterm']:
267            self.searchterm = form['searchterm']
268            self.searchtype = form['searchtype']
269        elif 'old_searchterm' in form:
270            self.searchterm = form['old_searchterm']
271            self.searchtype = form['old_searchtype']
272        else:
273            if 'search' in form:
274                self.flash('Empty search string.')
275            return
276        if not 'entries' in form:
277            self.hitlist = search(query=self.searchterm,
278                searchtype=self.searchtype, view=self)
279            if not self.hitlist:
280                self.flash('No student found.')
281            return
282        entries = form['entries']
283        if isinstance(entries, basestring):
284            entries = [entries]
285        deleted = []
286        for entry in entries:
287            if 'remove' in form:
288                del self.context[entry]
289                deleted.append(entry)
290        self.hitlist = search(query=self.searchterm,
291            searchtype=self.searchtype, view=self)
292        if len(deleted):
293            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6622]294        return
295
[6626]296class StudentsContainerAddActionButton(AddActionButton):
297    grok.order(1)
298    grok.context(IStudentsContainer)
299    grok.view(StudentsContainerManagePage)
300    grok.require('waeup.manageStudents')
301    text = 'Add student'
302    target = 'addstudent'
303
[6622]304class StudentAddFormPage(WAeUPAddFormPage):
305    """Add-form to add a student.
306    """
307    grok.context(IStudentsContainer)
308    grok.require('waeup.manageStudents')
309    grok.name('addstudent')
310    grok.template('studentaddpage')
311    form_fields = grok.AutoFields(IStudent)
312    title = 'Students'
313    label = 'Add student'
[6642]314    pnav = 4
[6622]315
316    @grok.action('Create student record')
317    def addStudent(self, **data):
318        student = createObject(u'waeup.Student')
319        self.applyData(student, **data)
[6652]320        self.context.addStudent(student)
[6626]321        self.flash('Student record created.')
[6651]322        self.redirect(self.url(self.context[student.student_id], 'index'))
[6622]323        return
324
[6631]325class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
326    """ Page to display student base data
327    """
[6622]328    grok.context(IStudent)
329    grok.name('index')
[6660]330    grok.require('waeup.viewStudent')
[6695]331    grok.template('basepage')
[6756]332    form_fields = grok.AutoFields(IStudentBase).omit('password')
[6642]333    pnav = 4
334    title = 'Base Data'
[6622]335
336    @property
337    def label(self):
[6818]338        return '%s: Base Data' % self.context.fullname
[6631]339
[6699]340    @property
341    def hasPassword(self):
342        if self.context.password:
343            return 'set'
344        return 'unset'
345
[6631]346class StudentBaseManageActionButton(ManageActionButton):
347    grok.order(1)
348    grok.context(IStudent)
349    grok.view(StudentBaseDisplayFormPage)
350    grok.require('waeup.manageStudents')
[6695]351    text = 'Manage'
[6631]352    target = 'edit_base'
353
354class StudentBaseManageFormPage(WAeUPEditFormPage):
355    """ View to edit student base data
356    """
357    grok.context(IStudent)
358    grok.name('edit_base')
359    grok.require('waeup.manageStudents')
360    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
[6695]361    grok.template('basemanagepage')
362    label = 'Manage base data'
[6642]363    title = 'Base Data'
364    pnav = 4
[6631]365
[6638]366    def update(self):
367        datepicker.need() # Enable jQuery datepicker in date fields.
368        super(StudentBaseManageFormPage, self).update()
369        self.wf_info = IWorkflowInfo(self.context)
370        return
371
372    def getTransitions(self):
373        """Return a list of dicts of allowed transition ids and titles.
374
375        Each list entry provides keys ``name`` and ``title`` for
376        internal name and (human readable) title of a single
377        transition.
378        """
379        allowed_transitions = self.wf_info.getManualTransitions()
380        return [dict(name='', title='No transition')] +[
381            dict(name=x, title=y) for x, y in allowed_transitions]
382
383    @grok.action('Save')
384    def save(self, **data):
[6701]385        form = self.request.form
[6790]386        password = form.get('password', None)
387        password_ctl = form.get('control_password', None)
388        if password:
389            if (password != password_ctl):
[6701]390                self.flash('Passwords do not match.')
[6790]391            else:
392                # XXX: This is too early. PW should only be saved if there
393                #      are no (other) errors left in form.
394                IUserAccount(self.context).setPassword(password)
[6943]395                write_log_message(self, 'password changed')
[6790]396
[6789]397        #self.reg_number = form.get('form.reg_number', None)
398        #if self.reg_number:
399        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
400        #    if hitlist and hitlist[0].student_id != self.context.student_id:
401        #        self.flash('Registration number exists.')
402        #        return
403        #self.matric_number = form.get('form.matric_number', None)
404        #if self.matric_number:
405        #    hitlist = search(query=self.matric_number,
406        #        searchtype='matric_number', view=self)
407        #    if hitlist and hitlist[0].student_id != self.context.student_id:
408        #        self.flash('Matriculation number exists.')
409        #        return
410
[6771]411        # Turn list of lists into single list
[6638]412        changed_fields = self.applyData(self.context, **data)
[6771]413        if changed_fields:
414            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
415        fields_string = ' + '.join(changed_fields)
[6638]416        self.context._p_changed = True
417        if form.has_key('transition') and form['transition']:
418            transition_id = form['transition']
419            self.wf_info.fireTransition(transition_id)
420        self.flash('Form has been saved.')
[6644]421        if fields_string:
[6943]422            write_log_message(self, 'saved: % s' % fields_string)
[6638]423        return
424
[6631]425class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
426    """ Page to display student clearance data
427    """
428    grok.context(IStudent)
429    grok.name('view_clearance')
[6660]430    grok.require('waeup.viewStudent')
[6695]431    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
[6650]432    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[6642]433    title = 'Clearance Data'
434    pnav = 4
[6631]435
436    @property
437    def label(self):
[6818]438        return '%s: Clearance Data' % self.context.fullname
[6631]439
440class StudentClearanceManageActionButton(ManageActionButton):
441    grok.order(1)
442    grok.context(IStudent)
443    grok.view(StudentClearanceDisplayFormPage)
444    grok.require('waeup.manageStudents')
[6695]445    text = 'Manage'
[6631]446    target = 'edit_clearance'
447
448class StudentClearanceManageFormPage(WAeUPEditFormPage):
449    """ Page to edit student clearance data
450    """
451    grok.context(IStudent)
452    grok.name('edit_clearance')
[6649]453    grok.require('waeup.manageStudents')
[6631]454    form_fields = grok.AutoFields(IStudentClearance)
[6695]455    label = 'Manage clearance data'
[6642]456    title = 'Clearance Data'
457    pnav = 4
[6631]458
[6650]459    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
460
461    def update(self):
462        datepicker.need() # Enable jQuery datepicker in date fields.
463        return super(StudentClearanceManageFormPage, self).update()
464
[6695]465    @grok.action('Save')
466    def save(self, **data):
[6762]467        msave(self, **data)
[6695]468        return
469
[6631]470class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
471    """ Page to display student personal data
472    """
473    grok.context(IStudent)
474    grok.name('view_personal')
[6660]475    grok.require('waeup.viewStudent')
[6631]476    form_fields = grok.AutoFields(IStudentPersonal)
[6642]477    title = 'Personal Data'
478    pnav = 4
[6631]479
480    @property
481    def label(self):
[6818]482        return '%s: Personal Data' % self.context.fullname
[6631]483
484class StudentPersonalManageActionButton(ManageActionButton):
485    grok.order(1)
486    grok.context(IStudent)
487    grok.view(StudentPersonalDisplayFormPage)
488    grok.require('waeup.manageStudents')
[6695]489    text = 'Manage'
[6631]490    target = 'edit_personal'
491
492class StudentPersonalManageFormPage(WAeUPEditFormPage):
493    """ Page to edit student clearance data
494    """
495    grok.context(IStudent)
496    grok.name('edit_personal')
[6660]497    grok.require('waeup.viewStudent')
[6631]498    form_fields = grok.AutoFields(IStudentPersonal)
[6695]499    label = 'Manage personal data'
[6642]500    title = 'Personal Data'
501    pnav = 4
[6631]502
[6762]503    @grok.action('Save')
504    def save(self, **data):
505        msave(self, **data)
506        return
507
[6635]508class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
509    """ Page to display the student study course data
510    """
511    grok.context(IStudentStudyCourse)
512    grok.name('index')
[6660]513    grok.require('waeup.viewStudent')
[6635]514    form_fields = grok.AutoFields(IStudentStudyCourse)
[6775]515    grok.template('studycoursepage')
[6642]516    title = 'Study Course'
517    pnav = 4
[6635]518
519    @property
520    def label(self):
[6818]521        return '%s: Study Course' % self.context.__parent__.fullname
[6635]522
[6912]523    @property
524    def current_mode(self):
525        return study_modes.getTermByToken(
526            self.context.certificate.study_mode).title
527
[6649]528class StudyCourseManageActionButton(ManageActionButton):
529    grok.order(1)
530    grok.context(IStudentStudyCourse)
531    grok.view(StudyCourseDisplayFormPage)
532    grok.require('waeup.manageStudents')
[6695]533    text = 'Manage'
[6775]534    target = 'manage'
[6649]535
536class StudyCourseManageFormPage(WAeUPEditFormPage):
537    """ Page to edit the student study course data
538    """
539    grok.context(IStudentStudyCourse)
[6775]540    grok.name('manage')
[6649]541    grok.require('waeup.manageStudents')
[6775]542    grok.template('studycoursemanagepage')
[6649]543    form_fields = grok.AutoFields(IStudentStudyCourse)
544    title = 'Study Course'
[6695]545    label = 'Manage study course'
[6649]546    pnav = 4
[6775]547    taboneactions = ['Save','Cancel']
548    tabtwoactions = ['Remove selected levels','Cancel']
549    tabthreeactions = ['Add study level']
[6649]550
[6775]551    def update(self):
552        super(StudyCourseManageFormPage, self).update()
553        tabs.need()
554        datatable.need()
555        return
556
[6761]557    @grok.action('Save')
558    def save(self, **data):
[6762]559        msave(self, **data)
[6761]560        return
561
[6775]562    @property
563    def level_dict(self):
564        studylevelsource = StudyLevelSource().factory
565        for code in studylevelsource.getValues(self.context):
566            title = studylevelsource.getTitle(self.context, code)
567            yield(dict(code=code, title=title))
568
569    @grok.action('Add study level')
[6774]570    def addStudyLevel(self, **data):
[6775]571        level_code = self.request.form.get('addlevel', None)
[6774]572        studylevel = StudentStudyLevel()
[6775]573        studylevel.level = int(level_code)
574        try:
[6782]575            self.context.addStudentStudyLevel(
576                self.context.certificate,studylevel)
[6775]577        except KeyError:
578            self.flash('This level exists.')
579        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
[6774]580        return
581
[6775]582    @grok.action('Remove selected levels')
583    def delStudyLevels(self, **data):
584        form = self.request.form
585        if form.has_key('val_id'):
586            child_id = form['val_id']
587        else:
588            self.flash('No study level selected.')
589            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
590            return
591        if not isinstance(child_id, list):
592            child_id = [child_id]
593        deleted = []
594        for id in child_id:
595            try:
596                del self.context[id]
597                deleted.append(id)
598            except:
599                self.flash('Could not delete %s: %s: %s' % (
600                        id, sys.exc_info()[0], sys.exc_info()[1]))
601        if len(deleted):
602            self.flash('Successfully removed: %s' % ', '.join(deleted))
603        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
604        return
[6774]605
606class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
607    """ Page to display student study levels
608    """
609    grok.context(IStudentStudyLevel)
610    grok.name('index')
611    grok.require('waeup.viewStudent')
[6775]612    form_fields = grok.AutoFields(IStudentStudyLevel)
[6783]613    grok.template('studylevelpage')
[6774]614    pnav = 4
615
616    @property
[6792]617    def title(self):
618        return 'Study Level %s' % self.context.level_title
619
620    @property
[6774]621    def label(self):
[6776]622        return '%s: Study Level %s' % (
[6818]623            self.context.getStudent().fullname,self.context.level_title)
[6774]624
[6803]625    @property
626    def total_credits(self):
627        total_credits = 0
628        for key, val in self.context.items():
629            total_credits += val.credits
630        return total_credits
631
[6792]632class StudyLevelManageActionButton(ManageActionButton):
633    grok.order(1)
634    grok.context(IStudentStudyLevel)
635    grok.view(StudyLevelDisplayFormPage)
636    grok.require('waeup.manageStudents')
637    text = 'Manage'
638    target = 'manage'
639
640class StudyLevelManageFormPage(WAeUPEditFormPage):
641    """ Page to edit the student study level data
642    """
643    grok.context(IStudentStudyLevel)
644    grok.name('manage')
645    grok.require('waeup.manageStudents')
646    grok.template('studylevelmanagepage')
647    form_fields = grok.AutoFields(IStudentStudyLevel)
648    pnav = 4
649    taboneactions = ['Save','Cancel']
[6795]650    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
[6792]651
652    def update(self):
653        super(StudyLevelManageFormPage, self).update()
654        tabs.need()
655        datatable.need()
656        return
657
658    @property
659    def title(self):
660        return 'Study Level %s' % self.context.level_title
661
662    @property
663    def label(self):
664        return 'Manage study level %s' % self.context.level_title
665
666    @grok.action('Save')
667    def save(self, **data):
668        msave(self, **data)
669        return
670
671    @grok.action('Add course ticket')
[6795]672    def addCourseTicket(self, **data):
673        self.redirect(self.url(self.context, '@@add'))
[6792]674
675    @grok.action('Remove selected tickets')
676    def delCourseTicket(self, **data):
677        form = self.request.form
678        if form.has_key('val_id'):
679            child_id = form['val_id']
680        else:
681            self.flash('No ticket selected.')
682            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
683            return
684        if not isinstance(child_id, list):
685            child_id = [child_id]
686        deleted = []
687        for id in child_id:
688            try:
689                del self.context[id]
690                deleted.append(id)
691            except:
692                self.flash('Could not delete %s: %s: %s' % (
693                        id, sys.exc_info()[0], sys.exc_info()[1]))
694        if len(deleted):
695            self.flash('Successfully removed: %s' % ', '.join(deleted))
696        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
697        return
698
[6795]699class CourseTicketAddFormPage(WAeUPAddFormPage):
[6808]700    """Add a course ticket.
[6795]701    """
702    grok.context(IStudentStudyLevel)
703    grok.name('add')
704    grok.require('waeup.manageStudents')
705    label = 'Add course ticket'
[6808]706    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
707        'grade', 'score', 'automatic')
[6795]708    pnav = 4
709
710    @property
711    def title(self):
712        return 'Study Level %s' % self.context.level_title
713
714    @grok.action('Add course ticket')
715    def addCourseTicket(self, **data):
716        ticket = CourseTicket()
717        course = data['course']
718        ticket.core_or_elective = data['core_or_elective']
[6802]719        ticket.automatic = False
[6795]720        ticket.code = course.code
721        ticket.title = course.title
722        ticket.faculty = course.__parent__.__parent__.__parent__.title
723        ticket.department = course.__parent__.__parent__.title
724        ticket.credits = course.credits
725        ticket.passmark = course.passmark
726        ticket.semester = course.semester
727        try:
728            self.context.addCourseTicket(ticket)
729        except KeyError:
730            self.flash('The ticket exists.')
731            return
[6799]732        self.flash('Successfully added %s.' % ticket.code)
[6795]733        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
734        return
735
736    @grok.action('Cancel')
737    def cancel(self, **data):
738        self.redirect(self.url(self.context))
739
[6796]740class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
741    """ Page to display course tickets
742    """
743    grok.context(ICourseTicket)
744    grok.name('index')
745    grok.require('waeup.viewStudent')
746    form_fields = grok.AutoFields(ICourseTicket)
747    grok.template('courseticketpage')
748    pnav = 4
749
750    @property
751    def title(self):
752        return 'Course Ticket %s' % self.context.code
753
754    @property
755    def label(self):
756        return '%s: Course Ticket %s' % (
[6818]757            self.context.getStudent().fullname,self.context.code)
[6796]758
759class CourseTicketManageActionButton(ManageActionButton):
760    grok.order(1)
761    grok.context(ICourseTicket)
762    grok.view(CourseTicketDisplayFormPage)
763    grok.require('waeup.manageStudents')
764    text = 'Manage'
765    target = 'manage'
766
767class CourseTicketManageFormPage(WAeUPEditFormPage):
768    """ Page to manage course tickets
769    """
770    grok.context(ICourseTicket)
771    grok.name('manage')
772    grok.require('waeup.manageStudents')
773    form_fields = grok.AutoFields(ICourseTicket)
774    grok.template('courseticketmanagepage')
775    pnav = 4
776
777    @property
778    def title(self):
779        return 'Course Ticket %s' % self.context.code
780
781    @property
782    def label(self):
783        return 'Manage course ticket %s' % self.context.code
784
785    @grok.action('Save')
786    def save(self, **data):
787        msave(self, **data)
788        return
789
[6940]790# We don't need the display form page yet
[6943]791#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
792#    """ Page to display the student payments
793#    """
794#    grok.context(IStudentPaymentsContainer)
795#    grok.name('view')
796#    grok.require('waeup.viewStudent')
797#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
798#    grok.template('paymentspage')
799#    title = 'Payments'
800#    pnav = 4
[6635]801
[6943]802#    def formatDatetime(self,datetimeobj):
803#        if isinstance(datetimeobj, datetime):
804#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
805#        else:
806#            return None
[6869]807
[6943]808#    @property
809#    def label(self):
810#        return '%s: Payments' % self.context.__parent__.fullname
[6635]811
[6943]812#    def update(self):
813#        super(PaymentsDisplayFormPage, self).update()
814#        datatable.need()
815#        return
[6869]816
[6940]817# This manage form page is for both students and students officers.
[6869]818class PaymentsManageFormPage(WAeUPEditFormPage):
819    """ Page to manage the student payments
820    """
821    grok.context(IStudentPaymentsContainer)
[6940]822    grok.name('index')
823    grok.require('waeup.handleStudent')
[6869]824    form_fields = grok.AutoFields(IStudentPaymentsContainer)
825    grok.template('paymentsmanagepage')
826    title = 'Payments'
827    pnav = 4
828
[6940]829    def unremovable(self, ticket):
830        prm = get_principal_role_manager()
831        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
832        return ('waeup.Student' in roles and ticket.r_code)
833
[6869]834    def formatDatetime(self,datetimeobj):
835        if isinstance(datetimeobj, datetime):
836            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
837        else:
838            return None
839
840    @property
841    def label(self):
842        return '%s: Payments' % self.context.__parent__.fullname
843
844    def update(self):
845        super(PaymentsManageFormPage, self).update()
846        datatable.need()
847        return
848
849    @grok.action('Remove selected tickets')
850    def delPaymentTicket(self, **data):
851        form = self.request.form
852        if form.has_key('val_id'):
853            child_id = form['val_id']
854        else:
855            self.flash('No payment selected.')
[6940]856            self.redirect(self.url(self.context))
[6869]857            return
858        if not isinstance(child_id, list):
859            child_id = [child_id]
860        deleted = []
861        for id in child_id:
[6992]862            # Students are not allowed to remove used payment tickets
[6940]863            if not self.unremovable(self.context[id]):
864                try:
865                    del self.context[id]
866                    deleted.append(id)
867                except:
868                    self.flash('Could not delete %s: %s: %s' % (
869                            id, sys.exc_info()[0], sys.exc_info()[1]))
[6869]870        if len(deleted):
871            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6943]872            write_log_message(self,'removed: % s' % ', '.join(deleted))
[6940]873        self.redirect(self.url(self.context))
[6869]874        return
875
876    @grok.action('Add online payment ticket')
877    def addPaymentTicket(self, **data):
878        self.redirect(self.url(self.context, '@@addop'))
879
[6940]880#class OnlinePaymentManageActionButton(ManageActionButton):
881#    grok.order(1)
882#    grok.context(IStudentPaymentsContainer)
883#    grok.view(PaymentsDisplayFormPage)
884#    grok.require('waeup.manageStudents')
885#    text = 'Manage payments'
886#    target = 'manage'
[6869]887
888class OnlinePaymentAddFormPage(WAeUPAddFormPage):
889    """ Page to add an online payment ticket
890    """
891    grok.context(IStudentPaymentsContainer)
892    grok.name('addop')
893    grok.require('waeup.handleStudent')
[6877]894    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
[6869]895        'p_category')
[6906]896    #zzgrok.template('addpaymentpage')
[6869]897    label = 'Add online payment'
898    title = 'Payments'
899    pnav = 4
[6947]900   
901    # To be sepezified in customization packages
902    def getPaymentDetails(self, category, student):
903        return getPaymentDetails(category, student)
[6869]904
905    @grok.action('Create ticket')
906    def createTicket(self, **data):
[6876]907        payment = createObject(u'waeup.StudentOnlinePayment')
[6869]908        self.applyData(payment, **data)
909        timestamp = "%d" % int(time()*1000)
910        #order_id = "%s%s" % (student_id[1:],timestamp)
911        payment.p_id = "p%s" % timestamp
[6994]912        pay_details  = self.getPaymentDetails(
[6876]913            data['p_category'],self.context.__parent__)
[6994]914        if pay_details['error']:
915            self.flash(pay_details['error'])
[6940]916            self.redirect(self.url(self.context))
[6920]917            return
[6994]918        payment.amount_auth = pay_details['amount']
919        payment.p_item = pay_details['p_item']
920        payment.p_session = pay_details['p_session']
921        payment.surcharge_1 = pay_details['surcharge_1']
922        payment.surcharge_2 = pay_details['surcharge_2']
923        payment.surcharge_3 = pay_details['surcharge_3']
[6869]924        self.context[payment.p_id] = payment
925        self.flash('Payment ticket created.')
[6940]926        self.redirect(self.url(self.context))
[6869]927        return
928
929class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
930    """ Page to view an online payment ticket
931    """
[6877]932    grok.context(IStudentOnlinePayment)
[6869]933    grok.name('index')
934    grok.require('waeup.viewStudent')
[6877]935    form_fields = grok.AutoFields(IStudentOnlinePayment)
[6869]936    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
937    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
938    pnav = 4
939
940    @property
941    def title(self):
942        return 'Online Payment Ticket %s' % self.context.p_id
943
944    @property
945    def label(self):
946        return '%s: Online Payment Ticket %s' % (
947            self.context.__parent__.__parent__.fullname,self.context.p_id)
948
[6930]949class OnlinePaymentCallbackPage(grok.View):
950    """ Callback view
951    """
952    grok.context(IStudentOnlinePayment)
953    grok.name('callback')
954    grok.require('waeup.payStudent')
955
956    # This update method simulates a valid callback und must be
957    # specified in the customization package. The parameters must be taken
958    # from the incoming request.
959    def update(self):
[6936]960        student = self.context.getStudent()
[6943]961        write_log_message(self,'valid callback: %s' % self.context.p_id)
[6930]962        self.context.r_amount_approved = self.context.amount_auth
963        self.context.r_card_num = u'0000'
964        self.context.r_code = u'00'
965        self.context.p_state = 'paid'
966        self.context.payment_date = datetime.now()
967        if self.context.p_category == 'clearance':
[6936]968            # Create CLR access code
[6937]969            pin, error = create_accesscode('CLR',0,student.student_id)
[6936]970            if error:
[6940]971                self.flash('Valid callback received. ' + error)
[6936]972                return
973            self.context.ac = pin
[6930]974        elif self.context.p_category == 'schoolfee':
[6936]975            # Create SFE access code
[6940]976            pin, error = create_accesscode('SFE',0,student.student_id)
[6936]977            if error:
[6940]978                self.flash('Valid callback received. ' + error)
[6936]979                return
980            self.context.ac = pin
[6994]981        elif self.context.p_category == 'bed_allocation':
982            # Create HOS access code
983            pin, error = create_accesscode('HOS',0,student.student_id)
984            if error:
985                self.flash('Valid callback received. ' + error)
986                return
987            self.context.ac = pin
[6940]988        self.flash('Valid callback received.')
989        return
[6930]990
991    def render(self):
[6940]992        self.redirect(self.url(self.context, '@@index'))
[6930]993        return
994
[6992]995# We don't need the display form page yet
996#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
997#    """ Page to display the student accommodation data
998#    """
999#    grok.context(IStudentAccommodation)
1000#    grok.name('xxx')
1001#    grok.require('waeup.viewStudent')
1002#    form_fields = grok.AutoFields(IStudentAccommodation)
1003#    #grok.template('accommodationpage')
1004#    title = 'Accommodation'
1005#    pnav = 4
1006
1007#    @property
1008#    def label(self):
1009#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1010
1011# This manage form page is for both students and students officers.
1012class AccommodationManageFormPage(WAeUPEditFormPage):
1013    """ Page to manage the bed tickets
[6635]1014    """
1015    grok.context(IStudentAccommodation)
1016    grok.name('index')
[6992]1017    grok.require('waeup.handleStudent')
[6635]1018    form_fields = grok.AutoFields(IStudentAccommodation)
[6992]1019    grok.template('accommodationmanagepage')
[6642]1020    title = 'Accommodation'
1021    pnav = 4
[6635]1022
[6992]1023    def unremovable(self):
1024        prm = get_principal_role_manager()
1025        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1026        return ('waeup.Student' in roles)
1027
1028    def formatDatetime(self,datetimeobj):
1029        if isinstance(datetimeobj, datetime):
1030            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1031        else:
1032            return None
1033
[6635]1034    @property
1035    def label(self):
[6992]1036        return '%s: Accommodation' % self.context.__parent__.fullname
[6637]1037
[6992]1038    def update(self):
1039        super(AccommodationManageFormPage, self).update()
1040        datatable.need()
1041        return
1042
1043    # We need an event handler which releases the bed space too.
1044    @grok.action('Remove selected tickets')
1045    def delBedTicket(self, **data):
1046        form = self.request.form
1047        if form.has_key('val_id'):
1048            child_id = form['val_id']
1049        else:
1050            self.flash('No bed ticket selected.')
1051            self.redirect(self.url(self.context))
1052            return
1053        if not isinstance(child_id, list):
1054            child_id = [child_id]
1055        deleted = []
1056        for id in child_id:
1057            # Students are not allowed to remove bed tickets
1058            if not self.unremovable():
1059                try:
1060                    del self.context[id]
1061                    deleted.append(id)
1062                except:
1063                    self.flash('Could not delete %s: %s: %s' % (
1064                            id, sys.exc_info()[0], sys.exc_info()[1]))
1065        if len(deleted):
1066            self.flash('Successfully removed: %s' % ', '.join(deleted))
1067            write_log_message(self,'removed: % s' % ', '.join(deleted))
1068        self.redirect(self.url(self.context))
1069        return
1070
1071    @grok.action('Add bed ticket')
1072    def addBedTicket(self, **data):
1073        self.redirect(self.url(self.context, '@@add'))
1074
1075class BedTicketAddPage(WAeUPPage):
1076    """ Page to add an online payment ticket
1077    """
1078    grok.context(IStudentAccommodation)
1079    grok.name('add')
1080    grok.require('waeup.handleStudent')
1081    grok.template('enterpin')
[6993]1082    ac_prefix = 'HOS'
[6992]1083    label = 'Add bed ticket'
1084    title = 'Add bed ticket'
1085    pnav = 4
1086    buttonname = 'Create bed ticket'
[6993]1087    notice = ''
[6992]1088
1089    # To be sepezified in customization packages
1090    def getAccommodationDetails(self, student):
1091        return getAccommodationDetails(student)
1092
[7004]1093    # To be sepezified in customization packages
1094    def selectBed(self, student):
1095        return getAccommodationDetails(student)
1096
[6992]1097    def update(self, SUBMIT=None):
[6996]1098        student = self.context.getStudent()
1099        acc_details  = self.getAccommodationDetails(student)
1100        if not student.state in acc_details['allowed_states']:
[6992]1101            self.flash("Wrong state.")
1102            self.redirect(self.url(self.context))
1103            return
[7004]1104        if str(student['studycourse'].entry_session) in self.context.keys():
1105            self.flash('Ticket for %d already created.'
1106                % student['studycourse'].entry_session)
1107            self.redirect(self.url(self.context))
1108            return
[6992]1109        self.ac_series = self.request.form.get('ac_series', None)
1110        self.ac_number = self.request.form.get('ac_number', None)
1111        if SUBMIT is None:
1112            return
1113        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1114        code = get_access_code(pin)
1115        if not code:
1116            self.flash('Activation code is invalid.')
1117            return
[7003]1118        # Search a bed and exit if no bed is found or if the student
1119        # has already booked a bed
[6997]1120        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1121        entries = cat.searchResults(
1122            bed_type=(acc_details['bt'],acc_details['bt']))
1123        available_beds = [
1124            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1125        if not available_beds:
1126            self.flash('There is no free bed in your category %s.'
1127                        % acc_details['bt'])
1128            return
[7003]1129        entries = cat.searchResults(
1130            owner=(student.student_id,student.student_id))
[7004]1131        # Cannot happen but anyway ...
[7003]1132        if len(entries):
1133            bed = [entry for entry in entries][0]
1134            self.flash('You already booked bed %s.'
1135                        % bed.bed_id)
1136            return
[6992]1137        # Mark pin as used (this also fires a pin related transition)
1138        if code.state == USED:
1139            self.flash('Activation code has already been used.')
1140            return
1141        else:
1142            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1143            # Here we know that the ac is in state initialized so we do not
1144            # expect an exception, but the owner might be different
1145            if not invalidate_accesscode(
1146                pin,comment,self.context.getStudent().student_id):
1147                self.flash('You are not the owner of this access code.')
1148                return
[6997]1149        # Create bed ticket and book bed
[6992]1150        bedticket = createObject(u'waeup.BedTicket')
1151        bedticket.booking_code = pin
[6994]1152        bedticket.booking_session = acc_details['booking_session']
[6996]1153        bedticket.bed_type = acc_details['bt']
[7004]1154        bed = selectBed(available_beds)
[6996]1155        bed.bookBed(student.student_id)
[7004]1156        bedticket.bed = bed # maybe wo don't need the bed object itself and can
1157                            # remove that later
[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.