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

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

Rename purchase_accesscode create_accesscode.

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