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

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

Filter actions if students are using the AccommodationManageFormPage?.

I first tried to use two different pages, one for officers and one for students, and use different targets in breadcrumbs. But this was much more complicated.

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