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

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

Add some basic hostel UI stuff.

  • Property svn:keywords set to Id
File size: 49.0 KB
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(4)
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    # To be sepezified in customization packages
888    def getPaymentDetails(self, category, student):
889        return getPaymentDetails(category, student)
890
891    @grok.action('Create ticket')
892    def createTicket(self, **data):
893        payment = createObject(u'waeup.StudentOnlinePayment')
894        self.applyData(payment, **data)
895        timestamp = "%d" % int(time()*1000)
896        #order_id = "%s%s" % (student_id[1:],timestamp)
897        payment.p_id = "p%s" % timestamp
898        (payment.amount_auth,
899            payment.p_item, payment.p_session,
900            payment.surcharge_1,
901            payment.surcharge_2,
902            payment.surcharge_3,
903            error)  = self.getPaymentDetails(
904            data['p_category'],self.context.__parent__)
905        if error:
906            self.flash(error)
907            self.redirect(self.url(self.context))
908            return
909        self.context[payment.p_id] = payment
910        self.flash('Payment ticket created.')
911        self.redirect(self.url(self.context))
912        return
913
914class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
915    """ Page to view an online payment ticket
916    """
917    grok.context(IStudentOnlinePayment)
918    grok.name('index')
919    grok.require('waeup.viewStudent')
920    form_fields = grok.AutoFields(IStudentOnlinePayment)
921    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
922    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
923    pnav = 4
924
925    @property
926    def title(self):
927        return 'Online Payment Ticket %s' % self.context.p_id
928
929    @property
930    def label(self):
931        return '%s: Online Payment Ticket %s' % (
932            self.context.__parent__.__parent__.fullname,self.context.p_id)
933
934class OnlinePaymentCallbackPage(grok.View):
935    """ Callback view
936    """
937    grok.context(IStudentOnlinePayment)
938    grok.name('callback')
939    grok.require('waeup.payStudent')
940
941    # This update method simulates a valid callback und must be
942    # specified in the customization package. The parameters must be taken
943    # from the incoming request.
944    def update(self):
945        student = self.context.getStudent()
946        write_log_message(self,'valid callback: %s' % self.context.p_id)
947        self.context.r_amount_approved = self.context.amount_auth
948        self.context.r_card_num = u'0000'
949        self.context.r_code = u'00'
950        self.context.p_state = 'paid'
951        self.context.payment_date = datetime.now()
952        if self.context.p_category == 'clearance':
953            # Create CLR access code
954            pin, error = create_accesscode('CLR',0,student.student_id)
955            if error:
956                self.flash('Valid callback received. ' + error)
957                return
958            self.context.ac = pin
959        elif self.context.p_category == 'schoolfee':
960            # Create SFE access code
961            pin, error = create_accesscode('SFE',0,student.student_id)
962            if error:
963                self.flash('Valid callback received. ' + error)
964                return
965            self.context.ac = pin
966        self.flash('Valid callback received.')
967        return
968
969    def render(self):
970        self.redirect(self.url(self.context, '@@index'))
971        return
972
973class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
974    """ Page to display the student accommodation data
975    """
976    grok.context(IStudentAccommodation)
977    grok.name('index')
978    grok.require('waeup.viewStudent')
979    form_fields = grok.AutoFields(IStudentAccommodation)
980    #grok.template('accommodationpage')
981    title = 'Accommodation'
982    pnav = 4
983
984    @property
985    def label(self):
986        return '%s: Accommodation Data' % self.context.__parent__.fullname
987
988class StudentHistoryPage(WAeUPPage):
989    """ Page to display student clearance data
990    """
991    grok.context(IStudent)
992    grok.name('history')
993    grok.require('waeup.viewStudent')
994    grok.template('studenthistory')
995    title = 'History'
996    pnav = 4
997
998    @property
999    def label(self):
1000        return '%s: History' % self.context.fullname
1001
1002# Pages for students only
1003
1004class StudentBaseEditActionButton(ManageActionButton):
1005    grok.order(1)
1006    grok.context(IStudent)
1007    grok.view(StudentBaseDisplayFormPage)
1008    grok.require('waeup.handleStudent')
1009    text = 'Change password'
1010    target = 'bedit'
1011
1012class StudentPasswordSetting(grok.Adapter):
1013    """Adapt IStudent to data needed for password settings.
1014
1015    We provide password getters/setters for the attached context (an
1016    IStudent object) that cooperate seamless with the usual
1017    formlib/form techniques.
1018    """
1019    grok.context(IStudent)
1020    grok.provides(IStudentPasswordSetting)
1021
1022    def __init__(self, context):
1023        self.name = context.fullname
1024        self.password_repeat = context.password
1025        self.context = context
1026        return
1027
1028    def getPassword(self):
1029        return self.context.password
1030
1031    def setPassword(self, password):
1032        IUserAccount(self.context).setPassword(password)
1033        return
1034
1035    password = property(getPassword, setPassword)
1036
1037class StudentBaseEditFormPage(WAeUPEditFormPage):
1038    """ View to edit student base data by student
1039    """
1040    grok.context(IStudent)
1041    grok.name('bedit')
1042    grok.require('waeup.handleStudent')
1043    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1044    #    'student_id', 'reg_number', 'matric_number')
1045    form_fields = grok.AutoFields(IStudentPasswordSetting)
1046    grok.template('baseeditpage')
1047    label = 'Change password'
1048    title = 'Base Data'
1049    pnav = 4
1050
1051    def update(self):
1052        super(StudentBaseEditFormPage, self).update()
1053        self.wf_info = IWorkflowInfo(self.context)
1054        return
1055
1056    def onFailure(self, action, data, errors):
1057        new_status = []
1058        other_errors = False
1059        for error in errors:
1060            msg = getattr(error, 'message', '')
1061            if isinstance(msg, basestring) and msg != '':
1062                new_status.append(msg)
1063            else:
1064                other_errors = True
1065        if other_errors:
1066            if new_status:
1067                new_status.append('see below for further errors')
1068            else:
1069                new_status.append('See below for details.')
1070        if new_status:
1071            self.status = u'There were errors: %s' % ', '.join(new_status)
1072        return
1073
1074    @grok.action('Save', failure=onFailure)
1075    def save(self, **data):
1076        self.applyData(self.context, **data)
1077        self.flash('Form has been saved.')
1078        return
1079
1080class StudentClearanceStartActionButton(ManageActionButton):
1081    grok.order(1)
1082    grok.context(IStudent)
1083    grok.view(StudentClearanceDisplayFormPage)
1084    grok.require('waeup.handleStudent')
1085    icon = 'actionicon_start.png'
1086    text = 'Start clearance'
1087    target = 'start_clearance'
1088
1089    @property
1090    def target_url(self):
1091        if self.context.state != 'admitted':
1092            return ''
1093        return self.view.url(self.view.context, self.target)
1094
1095class StartClearancePage(WAeUPPage):
1096    grok.context(IStudent)
1097    grok.name('start_clearance')
1098    grok.require('waeup.handleStudent')
1099    grok.template('enterpin')
1100    title = 'Start clearance'
1101    label = 'Start clearance'
1102    ac_prefix = 'CLR'
1103    notice = ''
1104    pnav = 4
1105    buttonname = 'Start clearance now'
1106
1107    def update(self, SUBMIT=None):
1108        if not self.context.state == 'admitted':
1109            self.flash("Wrong state.")
1110            self.redirect(self.url(self.context))
1111            return
1112        self.ac_series = self.request.form.get('ac_series', None)
1113        self.ac_number = self.request.form.get('ac_number', None)
1114
1115        if SUBMIT is None:
1116            return
1117        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1118        code = get_access_code(pin)
1119        if not code:
1120            self.flash('Activation code is invalid.')
1121            return
1122        # Mark pin as used (this also fires a pin related transition)
1123        # and fire transition start_clearance
1124        if code.state == USED:
1125            self.flash('Activation code has already been used.')
1126            return
1127        else:
1128            comment = u"AC invalidated for %s" % self.context.student_id
1129            # Here we know that the ac is in state initialized so we do not
1130            # expect an exception, but the owner might be different
1131            if not invalidate_accesscode(pin,comment,self.context.student_id):
1132                self.flash('You are not the owner of this access code.')
1133                return
1134            self.context.clr_code = pin
1135        IWorkflowInfo(self.context).fireTransition('start_clearance')
1136        self.flash('Clearance process has been started.')
1137        self.redirect(self.url(self.context,'cedit'))
1138        return
1139
1140class StudentClearanceEditActionButton(ManageActionButton):
1141    grok.order(1)
1142    grok.context(IStudent)
1143    grok.view(StudentClearanceDisplayFormPage)
1144    grok.require('waeup.handleStudent')
1145    text = 'Edit'
1146    target = 'cedit'
1147
1148    @property
1149    def target_url(self):
1150        if self.context.clearance_locked:
1151            return ''
1152        return self.view.url(self.view.context, self.target)
1153
1154class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1155    """ View to edit student clearance data by student
1156    """
1157    grok.context(IStudent)
1158    grok.name('cedit')
1159    grok.require('waeup.handleStudent')
1160    form_fields = grok.AutoFields(
1161        IStudentClearanceEdit).omit('clearance_locked')
1162    label = 'Edit clearance data'
1163    title = 'Clearance Data'
1164    pnav = 4
1165    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1166
1167    def emitLockMessage(self):
1168        self.flash('The requested form is locked (read-only).')
1169        self.redirect(self.url(self.context))
1170        return
1171
1172    def update(self):
1173        if self.context.clearance_locked:
1174            self.emitLockMessage()
1175            return
1176        datepicker.need()
1177        return super(StudentClearanceEditFormPage, self).update()
1178
1179    @grok.action('Save')
1180    def save(self, **data):
1181        self.applyData(self.context, **data)
1182        self.flash('Clearance form has been saved.')
1183        return
1184
1185    @grok.action('Save and request clearance')
1186    def requestclearance(self, **data):
1187        self.applyData(self.context, **data)
1188        self.context._p_changed = True
1189        #if self.dataNotComplete():
1190        #    self.flash(self.dataNotComplete())
1191        #    return
1192        self.flash('Clearance form has been saved.')
1193        self.redirect(self.url(self.context,'request_clearance'))
1194        return
1195
1196class RequestClearancePage(WAeUPPage):
1197    grok.context(IStudent)
1198    grok.name('request_clearance')
1199    grok.require('waeup.handleStudent')
1200    grok.template('enterpin')
1201    title = 'Request clearance'
1202    label = 'Request clearance'
1203    notice = 'Enter the CLR access code used for starting clearance.'
1204    ac_prefix = 'CLR'
1205    pnav = 4
1206    buttonname = 'Request clearance now'
1207
1208    def update(self, SUBMIT=None):
1209        self.ac_series = self.request.form.get('ac_series', None)
1210        self.ac_number = self.request.form.get('ac_number', None)
1211        if SUBMIT is None:
1212            return
1213        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1214        if self.context.clr_code != pin:
1215            self.flash("This isn't your CLR access code.")
1216            return
1217        state = IWorkflowState(self.context).getState()
1218        # This shouldn't happen, but the application officer
1219        # might have forgotten to lock the form after changing the state
1220        if state != CLEARANCE:
1221            self.flash('This form cannot be submitted. Wrong state!')
1222            return
1223        IWorkflowInfo(self.context).fireTransition('request_clearance')
1224        self.flash('Clearance has been requested.')
1225        self.redirect(self.url(self.context))
1226        return
1227
1228class CourseRegistrationStartActionButton(ManageActionButton):
1229    grok.order(1)
1230    grok.context(IStudentStudyCourse)
1231    grok.view(StudyCourseDisplayFormPage)
1232    grok.require('waeup.handleStudent')
1233    icon = 'actionicon_start.png'
1234    text = 'Start course registration'
1235    target = 'start_course_registration'
1236
1237    @property
1238    def target_url(self):
1239        if not self.context.getStudent().state in (CLEARED,RETURNING):
1240            return ''
1241        return self.view.url(self.view.context, self.target)
1242
1243class StartCourseRegistrationPage(WAeUPPage):
1244    grok.context(IStudentStudyCourse)
1245    grok.name('start_course_registration')
1246    grok.require('waeup.handleStudent')
1247    grok.template('enterpin')
1248    title = 'Start course registration'
1249    label = 'Start course registration'
1250    ac_prefix = 'SFE'
1251    notice = ''
1252    pnav = 4
1253    buttonname = 'Start course registration now'
1254
1255    def update(self, SUBMIT=None):
1256        if not self.context.getStudent().state in (CLEARED,RETURNING):
1257            self.flash("Wrong state.")
1258            self.redirect(self.url(self.context))
1259            return
1260        self.ac_series = self.request.form.get('ac_series', None)
1261        self.ac_number = self.request.form.get('ac_number', None)
1262
1263        if SUBMIT is None:
1264            return
1265        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1266        code = get_access_code(pin)
1267        if not code:
1268            self.flash('Activation code is invalid.')
1269            return
1270        # Mark pin as used (this also fires a pin related transition)
1271        # and fire transition start_clearance
1272        if code.state == USED:
1273            self.flash('Activation code has already been used.')
1274            return
1275        else:
1276            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1277            # Here we know that the ac is in state initialized so we do not
1278            # expect an exception, but the owner might be different
1279            if not invalidate_accesscode(
1280                pin,comment,self.context.getStudent().student_id):
1281                self.flash('You are not the owner of this access code.')
1282                return
1283        if self.context.getStudent().state == CLEARED:
1284            IWorkflowInfo(self.context.getStudent()).fireTransition(
1285                'pay_first_school_fee')
1286        elif self.context.getStudent().state == RETURNING:
1287            IWorkflowInfo(self.context.getStudent()).fireTransition(
1288                'pay_school_fee')
1289        self.flash('Course registration has been started.')
1290        self.redirect(self.url(self.context))
1291        return
1292
1293
1294class AddStudyLevelActionButton(AddActionButton):
1295    grok.order(1)
1296    grok.context(IStudentStudyCourse)
1297    grok.view(StudyCourseDisplayFormPage)
1298    grok.require('waeup.handleStudent')
1299    text = 'Add course list'
1300    target = 'add'
1301
1302    @property
1303    def target_url(self):
1304        student = self.view.context.getStudent()
1305        condition1 = student.state != 'school fee paid'
1306        condition2 = str(student['studycourse'].current_level) in \
1307            self.view.context.keys()
1308        if condition1 or condition2:
1309            return ''
1310        return self.view.url(self.view.context, self.target)
1311
1312class AddStudyLevelFormPage(WAeUPEditFormPage):
1313    """ Page for students to add current study levels
1314    """
1315    grok.context(IStudentStudyCourse)
1316    grok.name('add')
1317    grok.require('waeup.handleStudent')
1318    grok.template('studyleveladdpage')
1319    form_fields = grok.AutoFields(IStudentStudyCourse)
1320    title = 'Study Course'
1321    pnav = 4
1322
1323    @property
1324    def label(self):
1325        studylevelsource = StudyLevelSource().factory
1326        code = self.context.current_level
1327        title = studylevelsource.getTitle(self.context, code)
1328        return 'Add current level %s' % title
1329
1330    def emitLockMessage(self):
1331        self.flash('The requested form is locked (read-only).')
1332        self.redirect(self.url(self.context))
1333        return
1334
1335    def update(self):
1336        if self.context.getStudent().state != 'school fee paid':
1337            self.emitLockMessage()
1338            return
1339        super(AddStudyLevelFormPage, self).update()
1340        return
1341
1342    @grok.action('Create course list now')
1343    def addStudyLevel(self, **data):
1344        studylevel = StudentStudyLevel()
1345        studylevel.level = self.context.current_level
1346        studylevel.level_session = self.context.current_session
1347        try:
1348            self.context.addStudentStudyLevel(
1349                self.context.certificate,studylevel)
1350        except KeyError:
1351            self.flash('This level exists.')
1352        self.redirect(self.url(self.context))
1353        return
1354
1355class StudyLevelEditActionButton(ManageActionButton):
1356    grok.order(1)
1357    grok.context(IStudentStudyLevel)
1358    grok.view(StudyLevelDisplayFormPage)
1359    grok.require('waeup.handleStudent')
1360    text = 'Add and remove courses'
1361    target = 'edit'
1362
1363    @property
1364    def target_url(self):
1365        student = self.view.context.getStudent()
1366        condition1 = student.state != 'school fee paid'
1367        condition2 = student[
1368            'studycourse'].current_level != self.view.context.level
1369        if condition1 or condition2:
1370            return ''
1371        return self.view.url(self.view.context, self.target)
1372
1373class StudyLevelEditFormPage(WAeUPEditFormPage):
1374    """ Page to edit the student study level data by students
1375    """
1376    grok.context(IStudentStudyLevel)
1377    grok.name('edit')
1378    grok.require('waeup.handleStudent')
1379    grok.template('studyleveleditpage')
1380    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1381        'level_session', 'level_verdict')
1382    pnav = 4
1383
1384    def update(self):
1385        super(StudyLevelEditFormPage, self).update()
1386    #    tabs.need()
1387        datatable.need()
1388        return
1389
1390    @property
1391    def title(self):
1392        return 'Study Level %s' % self.context.level_title
1393
1394    @property
1395    def label(self):
1396        return 'Add and remove course tickets of study level %s' % self.context.level_title
1397
1398    @property
1399    def total_credits(self):
1400        total_credits = 0
1401        for key, val in self.context.items():
1402            total_credits += val.credits
1403        return total_credits
1404
1405    @grok.action('Add course ticket')
1406    def addCourseTicket(self, **data):
1407        self.redirect(self.url(self.context, 'ctadd'))
1408
1409    @grok.action('Remove selected tickets')
1410    def delCourseTicket(self, **data):
1411        form = self.request.form
1412        if form.has_key('val_id'):
1413            child_id = form['val_id']
1414        else:
1415            self.flash('No ticket selected.')
1416            self.redirect(self.url(self.context, '@@edit'))
1417            return
1418        if not isinstance(child_id, list):
1419            child_id = [child_id]
1420        deleted = []
1421        for id in child_id:
1422            # Students are not allowed to remove core tickets
1423            if not self.context[id].core_or_elective:
1424                try:
1425                    del self.context[id]
1426                    deleted.append(id)
1427                except:
1428                    self.flash('Could not delete %s: %s: %s' % (
1429                            id, sys.exc_info()[0], sys.exc_info()[1]))
1430        if len(deleted):
1431            self.flash('Successfully removed: %s' % ', '.join(deleted))
1432        self.redirect(self.url(self.context, u'@@edit'))
1433        return
1434
1435    @grok.action('Register course list')
1436    def register_courses(self, **data):
1437        state = IWorkflowState(self.context.getStudent()).getState()
1438        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1439        self.flash('Course list has been registered.')
1440        self.redirect(self.url(self.context))
1441        return
1442
1443class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1444    """Add a course ticket by student.
1445    """
1446    grok.name('ctadd')
1447    grok.require('waeup.handleStudent')
1448    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1449        'grade', 'score', 'core_or_elective', 'automatic')
1450
1451    @grok.action('Add course ticket')
1452    def addCourseTicket(self, **data):
1453        ticket = CourseTicket()
1454        course = data['course']
1455        ticket.automatic = False
1456        ticket.code = course.code
1457        ticket.title = course.title
1458        ticket.faculty = course.__parent__.__parent__.__parent__.title
1459        ticket.department = course.__parent__.__parent__.title
1460        ticket.credits = course.credits
1461        ticket.passmark = course.passmark
1462        ticket.semester = course.semester
1463        try:
1464            self.context.addCourseTicket(ticket)
1465        except KeyError:
1466            self.flash('The ticket exists.')
1467            return
1468        self.flash('Successfully added %s.' % ticket.code)
1469        self.redirect(self.url(self.context, u'@@edit'))
1470        return
Note: See TracBrowser for help on using the repository browser.