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

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

Implement StartCourseRegistrationPage? (tests will follow).

  • Property svn:keywords set to Id
File size: 48.8 KB
Line 
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
19from time import time
20from datetime import date, datetime
21from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
22from zope.component import createObject
23from waeup.sirp.accesscodes import (
24    invalidate_accesscode, get_access_code, create_accesscode)
25from waeup.sirp.accesscodes.workflow import USED
26from waeup.sirp.browser import (
27    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
28from waeup.sirp.browser.breadcrumbs import Breadcrumb
29from waeup.sirp.browser.resources import datepicker, datatable, tabs
30from waeup.sirp.browser.viewlets import (
31    ManageActionButton, PrimaryNavTab, AddActionButton)
32from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
33from waeup.sirp.widgets.datewidget import (
34    FriendlyDateWidget, FriendlyDateDisplayWidget,
35    FriendlyDatetimeDisplayWidget)
36from waeup.sirp.university.vocabularies import study_modes
37from waeup.sirp.students.interfaces import (
38    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
39    IStudentPersonal, IStudentBase, IStudentStudyCourse,
40    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
41    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
42    IStudentOnlinePayment
43    )
44from waeup.sirp.students.catalog import search
45from waeup.sirp.students.workflow import CLEARANCE, RETURNING, CLEARED
46from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
47from waeup.sirp.students.vocabularies import StudyLevelSource
48from waeup.sirp.students.utils import getPaymentDetails
49from waeup.sirp.browser.resources import toggleall
50from waeup.sirp.authentication import get_principal_role_manager
51
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
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)
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)
65    view.context._p_changed = True
66    view.flash('Form has been saved.')
67    if fields_string:
68        write_log_message(view, 'saved: % s' % fields_string)
69    return
70
71class StudentsTab(PrimaryNavTab):
72    """Students tab in primary navigation.
73    """
74
75    grok.context(IWAeUPObject)
76    grok.order(3)
77    grok.require('waeup.viewStudent')
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
87class StudentsBreadcrumb(Breadcrumb):
88    """A breadcrumb for the students container.
89    """
90    grok.context(IStudentsContainer)
91    title = u'Students'
92
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
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    """
110    grok.context(IStudentPaymentsContainer)
111    title = u'Payments'
112
113class OnlinePaymentBreadcrumb(Breadcrumb):
114    """A breadcrumb for course lists.
115    """
116    grok.context(IStudentOnlinePayment)
117
118    @property
119    def title(self):
120        return self.context.p_id
121
122class AccommodationBreadcrumb(Breadcrumb):
123    """A breadcrumb for the student accommodation folder.
124    """
125    grok.context(IStudentAccommodation)
126    title = u'Accommodation'
127
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
137class StudentsContainerPage(WAeUPPage):
138    """The standard view for student containers.
139    """
140    grok.context(IStudentsContainer)
141    grok.name('index')
142    grok.require('waeup.viewStudent')
143    grok.template('containerpage')
144    label = 'Student Section'
145    title = 'Students'
146    pnav = 4
147
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
168class SetPasswordPage(WAeUPPage):
169    grok.context(IWAeUPObject)
170    grok.name('setpassword')
171    grok.require('waeup.Public')
172    grok.template('setpassword')
173    title = ''
174    label = 'Set password for first-time login'
175    ac_prefix = 'PWD'
176    pnav = 0
177
178    def update(self, SUBMIT=None):
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)
182
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
193        student = hitlist[0].context
194        self.student_id = student.student_id
195        student_pw = student.password
196        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
197        code = get_access_code(pin)
198        if not code:
199            self.flash('Access code is invalid.')
200            return
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:
206            self.flash('Password has already been set. You are using the wrong Access Code.')
207            return
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:
214            comment = u"AC invalidated for %s" % self.student_id
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)
219            IUserAccount(student).setPassword(self.ac_number)
220            student.adm_code = pin
221        self.flash('Password has been set. Your Student Id is %s'
222            % self.student_id)
223        return
224
225class StudentsContainerManageActionButton(ManageActionButton):
226    grok.order(1)
227    grok.context(IStudentsContainer)
228    grok.view(StudentsContainerPage)
229    grok.require('waeup.manageStudents')
230    text = 'Manage student section'
231
232
233class StudentsContainerManagePage(WAeUPPage):
234    """The manage page for student containers.
235    """
236    grok.context(IStudentsContainer)
237    grok.name('manage')
238    grok.require('waeup.manageStudents')
239    grok.template('containermanagepage')
240    pnav = 4
241    title = 'Manage student section'
242
243    @property
244    def label(self):
245        return self.title
246
247    def update(self, *args, **kw):
248        datatable.need()
249        toggleall.need()
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))
280        return
281
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
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'
300    pnav = 4
301
302    @grok.action('Create student record')
303    def addStudent(self, **data):
304        student = createObject(u'waeup.Student')
305        self.applyData(student, **data)
306        self.context.addStudent(student)
307        self.flash('Student record created.')
308        self.redirect(self.url(self.context[student.student_id], 'index'))
309        return
310
311class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
312    """ Page to display student base data
313    """
314    grok.context(IStudent)
315    grok.name('index')
316    grok.require('waeup.viewStudent')
317    grok.template('basepage')
318    form_fields = grok.AutoFields(IStudentBase).omit('password')
319    pnav = 4
320    title = 'Base Data'
321
322    @property
323    def label(self):
324        return '%s: Base Data' % self.context.fullname
325
326    @property
327    def hasPassword(self):
328        if self.context.password:
329            return 'set'
330        return 'unset'
331
332class StudentBaseManageActionButton(ManageActionButton):
333    grok.order(1)
334    grok.context(IStudent)
335    grok.view(StudentBaseDisplayFormPage)
336    grok.require('waeup.manageStudents')
337    text = 'Manage'
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')
347    grok.template('basemanagepage')
348    label = 'Manage base data'
349    title = 'Base Data'
350    pnav = 4
351
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):
371        form = self.request.form
372        password = form.get('password', None)
373        password_ctl = form.get('control_password', None)
374        if password:
375            if (password != password_ctl):
376                self.flash('Passwords do not match.')
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)
381                write_log_message(self, 'password changed')
382
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
397        # Turn list of lists into single list
398        changed_fields = self.applyData(self.context, **data)
399        if changed_fields:
400            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
401        fields_string = ' + '.join(changed_fields)
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.')
407        if fields_string:
408            write_log_message(self, 'saved: % s' % fields_string)
409        return
410
411class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
412    """ Page to display student clearance data
413    """
414    grok.context(IStudent)
415    grok.name('view_clearance')
416    grok.require('waeup.viewStudent')
417    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
418    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
419    title = 'Clearance Data'
420    pnav = 4
421
422    @property
423    def label(self):
424        return '%s: Clearance Data' % self.context.fullname
425
426class StudentClearanceManageActionButton(ManageActionButton):
427    grok.order(1)
428    grok.context(IStudent)
429    grok.view(StudentClearanceDisplayFormPage)
430    grok.require('waeup.manageStudents')
431    text = 'Manage'
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')
439    grok.require('waeup.manageStudents')
440    form_fields = grok.AutoFields(IStudentClearance)
441    label = 'Manage clearance data'
442    title = 'Clearance Data'
443    pnav = 4
444
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
451    @grok.action('Save')
452    def save(self, **data):
453        msave(self, **data)
454        return
455
456class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
457    """ Page to display student personal data
458    """
459    grok.context(IStudent)
460    grok.name('view_personal')
461    grok.require('waeup.viewStudent')
462    form_fields = grok.AutoFields(IStudentPersonal)
463    title = 'Personal Data'
464    pnav = 4
465
466    @property
467    def label(self):
468        return '%s: Personal Data' % self.context.fullname
469
470class StudentPersonalManageActionButton(ManageActionButton):
471    grok.order(1)
472    grok.context(IStudent)
473    grok.view(StudentPersonalDisplayFormPage)
474    grok.require('waeup.manageStudents')
475    text = 'Manage'
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')
483    grok.require('waeup.viewStudent')
484    form_fields = grok.AutoFields(IStudentPersonal)
485    label = 'Manage personal data'
486    title = 'Personal Data'
487    pnav = 4
488
489    @grok.action('Save')
490    def save(self, **data):
491        msave(self, **data)
492        return
493
494class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
495    """ Page to display the student study course data
496    """
497    grok.context(IStudentStudyCourse)
498    grok.name('index')
499    grok.require('waeup.viewStudent')
500    form_fields = grok.AutoFields(IStudentStudyCourse)
501    grok.template('studycoursepage')
502    title = 'Study Course'
503    pnav = 4
504
505    @property
506    def label(self):
507        return '%s: Study Course' % self.context.__parent__.fullname
508
509    @property
510    def current_mode(self):
511        return study_modes.getTermByToken(
512            self.context.certificate.study_mode).title
513
514class StudyCourseManageActionButton(ManageActionButton):
515    grok.order(1)
516    grok.context(IStudentStudyCourse)
517    grok.view(StudyCourseDisplayFormPage)
518    grok.require('waeup.manageStudents')
519    text = 'Manage'
520    target = 'manage'
521
522class StudyCourseManageFormPage(WAeUPEditFormPage):
523    """ Page to edit the student study course data
524    """
525    grok.context(IStudentStudyCourse)
526    grok.name('manage')
527    grok.require('waeup.manageStudents')
528    grok.template('studycoursemanagepage')
529    form_fields = grok.AutoFields(IStudentStudyCourse)
530    title = 'Study Course'
531    label = 'Manage study course'
532    pnav = 4
533    taboneactions = ['Save','Cancel']
534    tabtwoactions = ['Remove selected levels','Cancel']
535    tabthreeactions = ['Add study level']
536
537    def update(self):
538        super(StudyCourseManageFormPage, self).update()
539        tabs.need()
540        datatable.need()
541        return
542
543    @grok.action('Save')
544    def save(self, **data):
545        msave(self, **data)
546        return
547
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')
556    def addStudyLevel(self, **data):
557        level_code = self.request.form.get('addlevel', None)
558        studylevel = StudentStudyLevel()
559        studylevel.level = int(level_code)
560        try:
561            self.context.addStudentStudyLevel(
562                self.context.certificate,studylevel)
563        except KeyError:
564            self.flash('This level exists.')
565        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
566        return
567
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
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')
598    form_fields = grok.AutoFields(IStudentStudyLevel)
599    grok.template('studylevelpage')
600    pnav = 4
601
602    @property
603    def title(self):
604        return 'Study Level %s' % self.context.level_title
605
606    @property
607    def label(self):
608        return '%s: Study Level %s' % (
609            self.context.getStudent().fullname,self.context.level_title)
610
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
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']
636    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
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')
658    def addCourseTicket(self, **data):
659        self.redirect(self.url(self.context, '@@add'))
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
685class CourseTicketAddFormPage(WAeUPAddFormPage):
686    """Add a course ticket.
687    """
688    grok.context(IStudentStudyLevel)
689    grok.name('add')
690    grok.require('waeup.manageStudents')
691    label = 'Add course ticket'
692    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
693        'grade', 'score', 'automatic')
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']
705        ticket.automatic = False
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
718        self.flash('Successfully added %s.' % ticket.code)
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
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' % (
743            self.context.getStudent().fullname,self.context.code)
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
776# We don't need the display form page yet
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
787
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
793
794#    @property
795#    def label(self):
796#        return '%s: Payments' % self.context.__parent__.fullname
797
798#    def update(self):
799#        super(PaymentsDisplayFormPage, self).update()
800#        datatable.need()
801#        return
802
803# This manage form page is for both students and students officers.
804class PaymentsManageFormPage(WAeUPEditFormPage):
805    """ Page to manage the student payments
806    """
807    grok.context(IStudentPaymentsContainer)
808    grok.name('index')
809    grok.require('waeup.handleStudent')
810    form_fields = grok.AutoFields(IStudentPaymentsContainer)
811    grok.template('paymentsmanagepage')
812    title = 'Payments'
813    pnav = 4
814
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
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.')
842            self.redirect(self.url(self.context))
843            return
844        if not isinstance(child_id, list):
845            child_id = [child_id]
846        deleted = []
847        for id in child_id:
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]))
856        if len(deleted):
857            self.flash('Successfully removed: %s' % ', '.join(deleted))
858            write_log_message(self,'removed: % s' % ', '.join(deleted))
859        self.redirect(self.url(self.context))
860        return
861
862    @grok.action('Add online payment ticket')
863    def addPaymentTicket(self, **data):
864        self.redirect(self.url(self.context, '@@addop'))
865
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'
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')
880    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
881        'p_category')
882    #zzgrok.template('addpaymentpage')
883    label = 'Add online payment'
884    title = 'Payments'
885    pnav = 4
886
887    @grok.action('Create ticket')
888    def createTicket(self, **data):
889        payment = createObject(u'waeup.StudentOnlinePayment')
890        self.applyData(payment, **data)
891        timestamp = "%d" % int(time()*1000)
892        #order_id = "%s%s" % (student_id[1:],timestamp)
893        payment.p_id = "p%s" % timestamp
894        (payment.amount_auth,
895            payment.p_item, payment.p_session,
896            payment.surcharge_1,
897            payment.surcharge_2,
898            payment.surcharge_3,
899            error)  = getPaymentDetails(
900            data['p_category'],self.context.__parent__)
901        if error:
902            self.flash(error)
903            self.redirect(self.url(self.context))
904            return
905        self.context[payment.p_id] = payment
906        self.flash('Payment ticket created.')
907        self.redirect(self.url(self.context))
908        return
909
910class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
911    """ Page to view an online payment ticket
912    """
913    grok.context(IStudentOnlinePayment)
914    grok.name('index')
915    grok.require('waeup.viewStudent')
916    form_fields = grok.AutoFields(IStudentOnlinePayment)
917    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
918    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
919    pnav = 4
920
921    @property
922    def title(self):
923        return 'Online Payment Ticket %s' % self.context.p_id
924
925    @property
926    def label(self):
927        return '%s: Online Payment Ticket %s' % (
928            self.context.__parent__.__parent__.fullname,self.context.p_id)
929
930class OnlinePaymentCallbackPage(grok.View):
931    """ Callback view
932    """
933    grok.context(IStudentOnlinePayment)
934    grok.name('callback')
935    grok.require('waeup.payStudent')
936
937    # This update method simulates a valid callback und must be
938    # specified in the customization package. The parameters must be taken
939    # from the incoming request.
940    def update(self):
941        student = self.context.getStudent()
942        write_log_message(self,'valid callback: %s' % self.context.p_id)
943        self.context.r_amount_approved = self.context.amount_auth
944        self.context.r_card_num = u'0000'
945        self.context.r_code = u'00'
946        self.context.p_state = 'paid'
947        self.context.payment_date = datetime.now()
948        if self.context.p_category == 'clearance':
949            # Create CLR access code
950            pin, error = create_accesscode('CLR',0,student.student_id)
951            if error:
952                self.flash('Valid callback received. ' + error)
953                return
954            self.context.ac = pin
955        elif self.context.p_category == 'schoolfee':
956            # Create SFE access code
957            pin, error = create_accesscode('SFE',0,student.student_id)
958            if error:
959                self.flash('Valid callback received. ' + error)
960                return
961            self.context.ac = pin
962        self.flash('Valid callback received.')
963        return
964
965    def render(self):
966        self.redirect(self.url(self.context, '@@index'))
967        return
968
969class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
970    """ Page to display the student accommodation data
971    """
972    grok.context(IStudentAccommodation)
973    grok.name('index')
974    grok.require('waeup.viewStudent')
975    form_fields = grok.AutoFields(IStudentAccommodation)
976    #grok.template('accommodationpage')
977    title = 'Accommodation'
978    pnav = 4
979
980    @property
981    def label(self):
982        return '%s: Accommodation Data' % self.context.__parent__.fullname
983
984class StudentHistoryPage(WAeUPPage):
985    """ Page to display student clearance data
986    """
987    grok.context(IStudent)
988    grok.name('history')
989    grok.require('waeup.viewStudent')
990    grok.template('studenthistory')
991    title = 'History'
992    pnav = 4
993
994    @property
995    def label(self):
996        return '%s: History' % self.context.fullname
997
998# Pages for students only
999
1000class StudentBaseEditActionButton(ManageActionButton):
1001    grok.order(1)
1002    grok.context(IStudent)
1003    grok.view(StudentBaseDisplayFormPage)
1004    grok.require('waeup.handleStudent')
1005    text = 'Change password'
1006    target = 'bedit'
1007
1008class StudentPasswordSetting(grok.Adapter):
1009    """Adapt IStudent to data needed for password settings.
1010
1011    We provide password getters/setters for the attached context (an
1012    IStudent object) that cooperate seamless with the usual
1013    formlib/form techniques.
1014    """
1015    grok.context(IStudent)
1016    grok.provides(IStudentPasswordSetting)
1017
1018    def __init__(self, context):
1019        self.name = context.fullname
1020        self.password_repeat = context.password
1021        self.context = context
1022        return
1023
1024    def getPassword(self):
1025        return self.context.password
1026
1027    def setPassword(self, password):
1028        IUserAccount(self.context).setPassword(password)
1029        return
1030
1031    password = property(getPassword, setPassword)
1032
1033class StudentBaseEditFormPage(WAeUPEditFormPage):
1034    """ View to edit student base data by student
1035    """
1036    grok.context(IStudent)
1037    grok.name('bedit')
1038    grok.require('waeup.handleStudent')
1039    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1040    #    'student_id', 'reg_number', 'matric_number')
1041    form_fields = grok.AutoFields(IStudentPasswordSetting)
1042    grok.template('baseeditpage')
1043    label = 'Change password'
1044    title = 'Base Data'
1045    pnav = 4
1046
1047    def update(self):
1048        super(StudentBaseEditFormPage, self).update()
1049        self.wf_info = IWorkflowInfo(self.context)
1050        return
1051
1052    def onFailure(self, action, data, errors):
1053        new_status = []
1054        other_errors = False
1055        for error in errors:
1056            msg = getattr(error, 'message', '')
1057            if isinstance(msg, basestring) and msg != '':
1058                new_status.append(msg)
1059            else:
1060                other_errors = True
1061        if other_errors:
1062            if new_status:
1063                new_status.append('see below for further errors')
1064            else:
1065                new_status.append('See below for details.')
1066        if new_status:
1067            self.status = u'There were errors: %s' % ', '.join(new_status)
1068        return
1069
1070    @grok.action('Save', failure=onFailure)
1071    def save(self, **data):
1072        self.applyData(self.context, **data)
1073        self.flash('Form has been saved.')
1074        return
1075
1076class StudentClearanceStartActionButton(ManageActionButton):
1077    grok.order(1)
1078    grok.context(IStudent)
1079    grok.view(StudentClearanceDisplayFormPage)
1080    grok.require('waeup.handleStudent')
1081    icon = 'actionicon_start.png'
1082    text = 'Start clearance'
1083    target = 'start_clearance'
1084
1085    @property
1086    def target_url(self):
1087        if self.context.state != 'admitted':
1088            return ''
1089        return self.view.url(self.view.context, self.target)
1090
1091class StartClearancePage(WAeUPPage):
1092    grok.context(IStudent)
1093    grok.name('start_clearance')
1094    grok.require('waeup.handleStudent')
1095    grok.template('enterpin')
1096    title = 'Start clearance'
1097    label = 'Start clearance'
1098    ac_prefix = 'CLR'
1099    notice = ''
1100    pnav = 4
1101    buttonname = 'Start clearance now'
1102
1103    def update(self, SUBMIT=None):
1104        if not self.context.state == 'admitted':
1105            self.flash("Wrong state.")
1106            self.redirect(self.url(self.context))
1107            return
1108        self.ac_series = self.request.form.get('ac_series', None)
1109        self.ac_number = self.request.form.get('ac_number', None)
1110
1111        if SUBMIT is None:
1112            return
1113        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1114        code = get_access_code(pin)
1115        if not code:
1116            self.flash('Activation code is invalid.')
1117            return
1118        # Mark pin as used (this also fires a pin related transition)
1119        # and fire transition start_clearance
1120        if code.state == USED:
1121            self.flash('Activation code has already been used.')
1122            return
1123        else:
1124            comment = u"AC invalidated for %s" % self.context.student_id
1125            # Here we know that the ac is in state initialized so we do not
1126            # expect an exception, but the owner might be different
1127            if not invalidate_accesscode(pin,comment,self.context.student_id):
1128                self.flash('You are not the owner of this access code.')
1129                return
1130            self.context.clr_code = pin
1131        IWorkflowInfo(self.context).fireTransition('start_clearance')
1132        self.flash('Clearance process has been started.')
1133        self.redirect(self.url(self.context,'cedit'))
1134        return
1135
1136class StudentClearanceEditActionButton(ManageActionButton):
1137    grok.order(1)
1138    grok.context(IStudent)
1139    grok.view(StudentClearanceDisplayFormPage)
1140    grok.require('waeup.handleStudent')
1141    text = 'Edit'
1142    target = 'cedit'
1143
1144    @property
1145    def target_url(self):
1146        if self.context.clearance_locked:
1147            return ''
1148        return self.view.url(self.view.context, self.target)
1149
1150class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1151    """ View to edit student clearance data by student
1152    """
1153    grok.context(IStudent)
1154    grok.name('cedit')
1155    grok.require('waeup.handleStudent')
1156    form_fields = grok.AutoFields(
1157        IStudentClearanceEdit).omit('clearance_locked')
1158    label = 'Edit clearance data'
1159    title = 'Clearance Data'
1160    pnav = 4
1161    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1162
1163    def emitLockMessage(self):
1164        self.flash('The requested form is locked (read-only).')
1165        self.redirect(self.url(self.context))
1166        return
1167
1168    def update(self):
1169        if self.context.clearance_locked:
1170            self.emitLockMessage()
1171            return
1172        datepicker.need()
1173        return super(StudentClearanceEditFormPage, self).update()
1174
1175    @grok.action('Save')
1176    def save(self, **data):
1177        self.applyData(self.context, **data)
1178        self.flash('Clearance form has been saved.')
1179        return
1180
1181    @grok.action('Save and request clearance')
1182    def requestclearance(self, **data):
1183        self.applyData(self.context, **data)
1184        self.context._p_changed = True
1185        #if self.dataNotComplete():
1186        #    self.flash(self.dataNotComplete())
1187        #    return
1188        self.flash('Clearance form has been saved.')
1189        self.redirect(self.url(self.context,'request_clearance'))
1190        return
1191
1192class RequestClearancePage(WAeUPPage):
1193    grok.context(IStudent)
1194    grok.name('request_clearance')
1195    grok.require('waeup.handleStudent')
1196    grok.template('enterpin')
1197    title = 'Request clearance'
1198    label = 'Request clearance'
1199    notice = 'Enter the CLR access code used for starting clearance.'
1200    ac_prefix = 'CLR'
1201    pnav = 4
1202    buttonname = 'Request clearance now'
1203
1204    def update(self, SUBMIT=None):
1205        self.ac_series = self.request.form.get('ac_series', None)
1206        self.ac_number = self.request.form.get('ac_number', None)
1207        if SUBMIT is None:
1208            return
1209        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1210        if self.context.clr_code != pin:
1211            self.flash("This isn't your CLR access code.")
1212            return
1213        state = IWorkflowState(self.context).getState()
1214        # This shouldn't happen, but the application officer
1215        # might have forgotten to lock the form after changing the state
1216        if state != CLEARANCE:
1217            self.flash('This form cannot be submitted. Wrong state!')
1218            return
1219        IWorkflowInfo(self.context).fireTransition('request_clearance')
1220        self.flash('Clearance has been requested.')
1221        self.redirect(self.url(self.context))
1222        return
1223
1224class CourseRegistrationStartActionButton(ManageActionButton):
1225    grok.order(1)
1226    grok.context(IStudentStudyCourse)
1227    grok.view(StudyCourseDisplayFormPage)
1228    grok.require('waeup.handleStudent')
1229    icon = 'actionicon_start.png'
1230    text = 'Start course registration'
1231    target = 'start_course_registration'
1232
1233    @property
1234    def target_url(self):
1235        if not self.context.getStudent().state in (CLEARED,RETURNING):
1236            return ''
1237        return self.view.url(self.view.context, self.target)
1238
1239class StartCourseRegistrationPage(WAeUPPage):
1240    grok.context(IStudentStudyCourse)
1241    grok.name('start_course_registration')
1242    grok.require('waeup.handleStudent')
1243    grok.template('enterpin')
1244    title = 'Start course registration'
1245    label = 'Start course registration'
1246    ac_prefix = 'SFE'
1247    notice = ''
1248    pnav = 4
1249    buttonname = 'Start course registration now'
1250
1251    def update(self, SUBMIT=None):
1252        if not self.context.getStudent().state in (CLEARED,RETURNING):
1253            self.flash("Wrong state.")
1254            self.redirect(self.url(self.context))
1255            return
1256        self.ac_series = self.request.form.get('ac_series', None)
1257        self.ac_number = self.request.form.get('ac_number', None)
1258
1259        if SUBMIT is None:
1260            return
1261        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1262        code = get_access_code(pin)
1263        if not code:
1264            self.flash('Activation code is invalid.')
1265            return
1266        # Mark pin as used (this also fires a pin related transition)
1267        # and fire transition start_clearance
1268        if code.state == USED:
1269            self.flash('Activation code has already been used.')
1270            return
1271        else:
1272            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1273            # Here we know that the ac is in state initialized so we do not
1274            # expect an exception, but the owner might be different
1275            if not invalidate_accesscode(
1276                pin,comment,self.context.getStudent().student_id):
1277                self.flash('You are not the owner of this access code.')
1278                return
1279        if self.context.getStudent().state == CLEARED:
1280            IWorkflowInfo(self.context.getStudent()).fireTransition(
1281                'pay_first_school_fee')
1282        elif self.context.getStudent().state == RETURNING:
1283            IWorkflowInfo(self.context.getStudent()).fireTransition(
1284                'pay_school_fee')
1285        self.flash('Course registration has been started.')
1286        self.redirect(self.url(self.context))
1287        return
1288
1289
1290class AddStudyLevelActionButton(AddActionButton):
1291    grok.order(1)
1292    grok.context(IStudentStudyCourse)
1293    grok.view(StudyCourseDisplayFormPage)
1294    grok.require('waeup.handleStudent')
1295    text = 'Add course list'
1296    target = 'add'
1297
1298    @property
1299    def target_url(self):
1300        student = self.view.context.getStudent()
1301        condition1 = student.state != 'school fee paid'
1302        condition2 = str(student['studycourse'].current_level) in \
1303            self.view.context.keys()
1304        if condition1 or condition2:
1305            return ''
1306        return self.view.url(self.view.context, self.target)
1307
1308class AddStudyLevelFormPage(WAeUPEditFormPage):
1309    """ Page for students to add current study levels
1310    """
1311    grok.context(IStudentStudyCourse)
1312    grok.name('add')
1313    grok.require('waeup.handleStudent')
1314    grok.template('studyleveladdpage')
1315    form_fields = grok.AutoFields(IStudentStudyCourse)
1316    title = 'Study Course'
1317    pnav = 4
1318
1319    @property
1320    def label(self):
1321        studylevelsource = StudyLevelSource().factory
1322        code = self.context.current_level
1323        title = studylevelsource.getTitle(self.context, code)
1324        return 'Add current level %s' % title
1325
1326    def emitLockMessage(self):
1327        self.flash('The requested form is locked (read-only).')
1328        self.redirect(self.url(self.context))
1329        return
1330
1331    def update(self):
1332        if self.context.getStudent().state != 'school fee paid':
1333            self.emitLockMessage()
1334            return
1335        super(AddStudyLevelFormPage, self).update()
1336        return
1337
1338    @grok.action('Create course list now')
1339    def addStudyLevel(self, **data):
1340        studylevel = StudentStudyLevel()
1341        studylevel.level = self.context.current_level
1342        studylevel.level_session = self.context.current_session
1343        try:
1344            self.context.addStudentStudyLevel(
1345                self.context.certificate,studylevel)
1346        except KeyError:
1347            self.flash('This level exists.')
1348        self.redirect(self.url(self.context))
1349        return
1350
1351class StudyLevelEditActionButton(ManageActionButton):
1352    grok.order(1)
1353    grok.context(IStudentStudyLevel)
1354    grok.view(StudyLevelDisplayFormPage)
1355    grok.require('waeup.handleStudent')
1356    text = 'Add and remove courses'
1357    target = 'edit'
1358
1359    @property
1360    def target_url(self):
1361        student = self.view.context.getStudent()
1362        condition1 = student.state != 'school fee paid'
1363        condition2 = student[
1364            'studycourse'].current_level != self.view.context.level
1365        if condition1 or condition2:
1366            return ''
1367        return self.view.url(self.view.context, self.target)
1368
1369class StudyLevelEditFormPage(WAeUPEditFormPage):
1370    """ Page to edit the student study level data by students
1371    """
1372    grok.context(IStudentStudyLevel)
1373    grok.name('edit')
1374    grok.require('waeup.handleStudent')
1375    grok.template('studyleveleditpage')
1376    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1377        'level_session', 'level_verdict')
1378    pnav = 4
1379
1380    def update(self):
1381        super(StudyLevelEditFormPage, self).update()
1382    #    tabs.need()
1383        datatable.need()
1384        return
1385
1386    @property
1387    def title(self):
1388        return 'Study Level %s' % self.context.level_title
1389
1390    @property
1391    def label(self):
1392        return 'Add and remove course tickets of study level %s' % self.context.level_title
1393
1394    @property
1395    def total_credits(self):
1396        total_credits = 0
1397        for key, val in self.context.items():
1398            total_credits += val.credits
1399        return total_credits
1400
1401    @grok.action('Add course ticket')
1402    def addCourseTicket(self, **data):
1403        self.redirect(self.url(self.context, 'ctadd'))
1404
1405    @grok.action('Remove selected tickets')
1406    def delCourseTicket(self, **data):
1407        form = self.request.form
1408        if form.has_key('val_id'):
1409            child_id = form['val_id']
1410        else:
1411            self.flash('No ticket selected.')
1412            self.redirect(self.url(self.context, '@@edit'))
1413            return
1414        if not isinstance(child_id, list):
1415            child_id = [child_id]
1416        deleted = []
1417        for id in child_id:
1418            # Students are not allowed to remove core tickets
1419            if not self.context[id].core_or_elective:
1420                try:
1421                    del self.context[id]
1422                    deleted.append(id)
1423                except:
1424                    self.flash('Could not delete %s: %s: %s' % (
1425                            id, sys.exc_info()[0], sys.exc_info()[1]))
1426        if len(deleted):
1427            self.flash('Successfully removed: %s' % ', '.join(deleted))
1428        self.redirect(self.url(self.context, u'@@edit'))
1429        return
1430
1431    @grok.action('Register course list')
1432    def register_courses(self, **data):
1433        state = IWorkflowState(self.context.getStudent()).getState()
1434        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1435        self.flash('Course list has been registered.')
1436        self.redirect(self.url(self.context))
1437        return
1438
1439class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1440    """Add a course ticket by student.
1441    """
1442    grok.name('ctadd')
1443    grok.require('waeup.handleStudent')
1444    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1445        'grade', 'score', 'core_or_elective', 'automatic')
1446
1447    @grok.action('Add course ticket')
1448    def addCourseTicket(self, **data):
1449        ticket = CourseTicket()
1450        course = data['course']
1451        ticket.automatic = False
1452        ticket.code = course.code
1453        ticket.title = course.title
1454        ticket.faculty = course.__parent__.__parent__.__parent__.title
1455        ticket.department = course.__parent__.__parent__.title
1456        ticket.credits = course.credits
1457        ticket.passmark = course.passmark
1458        ticket.semester = course.semester
1459        try:
1460            self.context.addCourseTicket(ticket)
1461        except KeyError:
1462            self.flash('The ticket exists.')
1463            return
1464        self.flash('Successfully added %s.' % ticket.code)
1465        self.redirect(self.url(self.context, u'@@edit'))
1466        return
Note: See TracBrowser for help on using the repository browser.