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

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

Add some basic hostel UI stuff.

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