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

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

Implement OnlinePaymentCallbackPage? to simulate a valid callback.

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