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

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

Provide a getPaymentDetails function which requires a SessionConfiguration? object. This function is for demonstration and testing only.

  • Property svn:keywords set to Id
File size: 43.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 AccommodationDisplayFormPage(WAeUPDisplayFormPage):
916    """ Page to display the student accommodation data
917    """
918    grok.context(IStudentAccommodation)
919    grok.name('index')
920    grok.require('waeup.viewStudent')
921    form_fields = grok.AutoFields(IStudentAccommodation)
922    #grok.template('accommodationpage')
923    title = 'Accommodation'
924    pnav = 4
925
926    @property
927    def label(self):
928        return '%s: Accommodation Data' % self.context.__parent__.fullname
929
930class StudentHistoryPage(WAeUPPage):
931    """ Page to display student clearance data
932    """
933    grok.context(IStudent)
934    grok.name('history')
935    grok.require('waeup.viewStudent')
936    grok.template('studenthistory')
937    title = 'History'
938    pnav = 4
939
940    @property
941    def label(self):
942        return '%s: History' % self.context.fullname
943
944# Pages for students only
945
946class StudentBaseEditActionButton(ManageActionButton):
947    grok.order(1)
948    grok.context(IStudent)
949    grok.view(StudentBaseDisplayFormPage)
950    grok.require('waeup.handleStudent')
951    text = 'Change password'
952    target = 'bedit'
953
954class StudentPasswordSetting(grok.Adapter):
955    """Adapt IStudent to data needed for password settings.
956
957    We provide password getters/setters for the attached context (an
958    IStudent object) that cooperate seamless with the usual
959    formlib/form techniques.
960    """
961    grok.context(IStudent)
962    grok.provides(IStudentPasswordSetting)
963
964    def __init__(self, context):
965        self.name = context.fullname
966        self.password_repeat = context.password
967        self.context = context
968        return
969
970    def getPassword(self):
971        return self.context.password
972
973    def setPassword(self, password):
974        IUserAccount(self.context).setPassword(password)
975        return
976
977    password = property(getPassword, setPassword)
978
979class StudentBaseEditFormPage(WAeUPEditFormPage):
980    """ View to edit student base data by student
981    """
982    grok.context(IStudent)
983    grok.name('bedit')
984    grok.require('waeup.handleStudent')
985    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
986    #    'student_id', 'reg_number', 'matric_number')
987    form_fields = grok.AutoFields(IStudentPasswordSetting)
988    grok.template('baseeditpage')
989    label = 'Change password'
990    title = 'Base Data'
991    pnav = 4
992
993    def update(self):
994        super(StudentBaseEditFormPage, self).update()
995        self.wf_info = IWorkflowInfo(self.context)
996        return
997
998    def onFailure(self, action, data, errors):
999        new_status = []
1000        other_errors = False
1001        for error in errors:
1002            msg = getattr(error, 'message', '')
1003            if isinstance(msg, basestring) and msg != '':
1004                new_status.append(msg)
1005            else:
1006                other_errors = True
1007        if other_errors:
1008            if new_status:
1009                new_status.append('see below for further errors')
1010            else:
1011                new_status.append('See below for details.')
1012        if new_status:
1013            self.status = u'There were errors: %s' % ', '.join(new_status)
1014        return
1015
1016    @grok.action('Save', failure=onFailure)
1017    def save(self, **data):
1018        self.applyData(self.context, **data)
1019        self.flash('Form has been saved.')
1020        return
1021
1022class StudentClearanceStartActionButton(ManageActionButton):
1023    grok.order(1)
1024    grok.context(IStudent)
1025    grok.view(StudentClearanceDisplayFormPage)
1026    grok.require('waeup.handleStudent')
1027    icon = 'actionicon_start.png'
1028    text = 'Start clearance'
1029    target = 'start_clearance'
1030
1031    @property
1032    def target_url(self):
1033        if self.context.state != 'admitted':
1034            return ''
1035        return self.view.url(self.view.context, self.target)
1036
1037class StartClearancePage(WAeUPPage):
1038    grok.context(IStudent)
1039    grok.name('start_clearance')
1040    grok.require('waeup.handleStudent')
1041    grok.template('enterpin')
1042    title = 'Start clearance'
1043    label = 'Start clearance'
1044    ac_prefix = 'CLR'
1045    notice = ''
1046    pnav = 4
1047    buttonname = 'Start clearance now'
1048
1049    def update(self, SUBMIT=None):
1050        self.ac_series = self.request.form.get('ac_series', None)
1051        self.ac_number = self.request.form.get('ac_number', None)
1052
1053        if SUBMIT is None:
1054            return
1055        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1056        code = get_access_code(pin)
1057        if not code:
1058            self.flash('Access code is invalid.')
1059            return
1060        # Mark pin as used (this also fires a pin related transition)
1061        # and fire transition start_clearance
1062        if code.state == USED:
1063            self.flash('Access code has already been used.')
1064            return
1065        else:
1066            comment = u"AC invalidated for %s" % self.context.student_id
1067            # Here we know that the ac is in state initialized so we do not
1068            # expect an exception
1069            invalidate_accesscode(pin,comment)
1070            self.context.clr_code = pin
1071        IWorkflowInfo(self.context).fireTransition('start_clearance')
1072        self.flash('Clearance process has been started.')
1073        self.redirect(self.url(self.context,'cedit'))
1074        return
1075
1076class StudentClearanceEditActionButton(ManageActionButton):
1077    grok.order(1)
1078    grok.context(IStudent)
1079    grok.view(StudentClearanceDisplayFormPage)
1080    grok.require('waeup.handleStudent')
1081    text = 'Edit'
1082    target = 'cedit'
1083
1084    @property
1085    def target_url(self):
1086        if self.context.clearance_locked:
1087            return ''
1088        return self.view.url(self.view.context, self.target)
1089
1090class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1091    """ View to edit student clearance data by student
1092    """
1093    grok.context(IStudent)
1094    grok.name('cedit')
1095    grok.require('waeup.handleStudent')
1096    form_fields = grok.AutoFields(
1097        IStudentClearanceEdit).omit('clearance_locked')
1098    label = 'Edit clearance data'
1099    title = 'Clearance Data'
1100    pnav = 4
1101    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1102
1103    def emitLockMessage(self):
1104        self.flash('The requested form is locked (read-only).')
1105        self.redirect(self.url(self.context))
1106        return
1107
1108    def update(self):
1109        if self.context.clearance_locked:
1110            self.emitLockMessage()
1111            return
1112        datepicker.need()
1113        return super(StudentClearanceEditFormPage, self).update()
1114
1115    @grok.action('Save')
1116    def save(self, **data):
1117        self.applyData(self.context, **data)
1118        self.flash('Clearance form has been saved.')
1119        return
1120
1121    @grok.action('Save and request clearance')
1122    def requestclearance(self, **data):
1123        self.applyData(self.context, **data)
1124        self.context._p_changed = True
1125        #if self.dataNotComplete():
1126        #    self.flash(self.dataNotComplete())
1127        #    return
1128        self.flash('Clearance form has been saved.')
1129        self.redirect(self.url(self.context,'request_clearance'))
1130        return
1131
1132class RequestClearancePage(WAeUPPage):
1133    grok.context(IStudent)
1134    grok.name('request_clearance')
1135    grok.require('waeup.handleStudent')
1136    grok.template('enterpin')
1137    title = 'Request clearance'
1138    label = 'Request clearance'
1139    notice = 'Enter the CLR access code used for starting clearance.'
1140    ac_prefix = 'CLR'
1141    pnav = 4
1142    buttonname = 'Request clearance now'
1143
1144    def update(self, SUBMIT=None):
1145        self.ac_series = self.request.form.get('ac_series', None)
1146        self.ac_number = self.request.form.get('ac_number', None)
1147        if SUBMIT is None:
1148            return
1149        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1150        if self.context.clr_code != pin:
1151            self.flash("This isn't your CLR access code.")
1152            return
1153        state = IWorkflowState(self.context).getState()
1154        # This shouldn't happen, but the application officer
1155        # might have forgotten to lock the form after changing the state
1156        if state != CLEARANCE:
1157            self.flash('This form cannot be submitted. Wrong state!')
1158            return
1159        IWorkflowInfo(self.context).fireTransition('request_clearance')
1160        self.flash('Clearance has been requested.')
1161        self.redirect(self.url(self.context))
1162        return
1163
1164class AddStudyLevelActionButton(AddActionButton):
1165    grok.order(1)
1166    grok.context(IStudentStudyCourse)
1167    grok.view(StudyCourseDisplayFormPage)
1168    grok.require('waeup.handleStudent')
1169    text = 'Add course list'
1170    target = 'add'
1171
1172    @property
1173    def target_url(self):
1174        student = self.view.context.getStudent()
1175        condition1 = student.state != 'school fee paid'
1176        condition2 = str(student['studycourse'].current_level) in \
1177            self.view.context.keys()
1178        if condition1 or condition2:
1179            return ''
1180        return self.view.url(self.view.context, self.target)
1181
1182class AddStudyLevelFormPage(WAeUPEditFormPage):
1183    """ Page for students to add current study levels
1184    """
1185    grok.context(IStudentStudyCourse)
1186    grok.name('add')
1187    grok.require('waeup.handleStudent')
1188    grok.template('studyleveladdpage')
1189    form_fields = grok.AutoFields(IStudentStudyCourse)
1190    title = 'Study Course'
1191    pnav = 4
1192
1193    @property
1194    def label(self):
1195        studylevelsource = StudyLevelSource().factory
1196        code = self.context.current_level
1197        title = studylevelsource.getTitle(self.context, code)
1198        return 'Add current level %s' % title
1199
1200    def emitLockMessage(self):
1201        self.flash('The requested form is locked (read-only).')
1202        self.redirect(self.url(self.context))
1203        return
1204
1205    def update(self):
1206        if self.context.getStudent().state != 'school fee paid':
1207            self.emitLockMessage()
1208            return
1209        super(AddStudyLevelFormPage, self).update()
1210        return
1211
1212    @grok.action('Create course list now')
1213    def addStudyLevel(self, **data):
1214        studylevel = StudentStudyLevel()
1215        studylevel.level = self.context.current_level
1216        studylevel.level_session = self.context.current_session
1217        try:
1218            self.context.addStudentStudyLevel(
1219                self.context.certificate,studylevel)
1220        except KeyError:
1221            self.flash('This level exists.')
1222        self.redirect(self.url(self.context))
1223        return
1224
1225class StudyLevelEditActionButton(ManageActionButton):
1226    grok.order(1)
1227    grok.context(IStudentStudyLevel)
1228    grok.view(StudyLevelDisplayFormPage)
1229    grok.require('waeup.handleStudent')
1230    text = 'Add and remove courses'
1231    target = 'edit'
1232
1233    @property
1234    def target_url(self):
1235        student = self.view.context.getStudent()
1236        condition1 = student.state != 'school fee paid'
1237        condition2 = student[
1238            'studycourse'].current_level != self.view.context.level
1239        if condition1 or condition2:
1240            return ''
1241        return self.view.url(self.view.context, self.target)
1242
1243class StudyLevelEditFormPage(WAeUPEditFormPage):
1244    """ Page to edit the student study level data by students
1245    """
1246    grok.context(IStudentStudyLevel)
1247    grok.name('edit')
1248    grok.require('waeup.handleStudent')
1249    grok.template('studyleveleditpage')
1250    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1251        'level_session', 'level_verdict')
1252    pnav = 4
1253
1254    def update(self):
1255        super(StudyLevelEditFormPage, self).update()
1256    #    tabs.need()
1257        datatable.need()
1258        return
1259
1260    @property
1261    def title(self):
1262        return 'Study Level %s' % self.context.level_title
1263
1264    @property
1265    def label(self):
1266        return 'Add and remove course tickets of study level %s' % self.context.level_title
1267
1268    @property
1269    def total_credits(self):
1270        total_credits = 0
1271        for key, val in self.context.items():
1272            total_credits += val.credits
1273        return total_credits
1274
1275    @grok.action('Add course ticket')
1276    def addCourseTicket(self, **data):
1277        self.redirect(self.url(self.context, 'ctadd'))
1278
1279    @grok.action('Remove selected tickets')
1280    def delCourseTicket(self, **data):
1281        form = self.request.form
1282        if form.has_key('val_id'):
1283            child_id = form['val_id']
1284        else:
1285            self.flash('No ticket selected.')
1286            self.redirect(self.url(self.context, '@@edit'))
1287            return
1288        if not isinstance(child_id, list):
1289            child_id = [child_id]
1290        deleted = []
1291        for id in child_id:
1292            # Student are not allowed to remove core tickets
1293            if not self.context[id].core_or_elective:
1294                try:
1295                    del self.context[id]
1296                    deleted.append(id)
1297                except:
1298                    self.flash('Could not delete %s: %s: %s' % (
1299                            id, sys.exc_info()[0], sys.exc_info()[1]))
1300        if len(deleted):
1301            self.flash('Successfully removed: %s' % ', '.join(deleted))
1302        self.redirect(self.url(self.context, u'@@edit'))
1303        return
1304
1305    @grok.action('Register course list')
1306    def register_courses(self, **data):
1307        state = IWorkflowState(self.context.getStudent()).getState()
1308        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1309        self.flash('Course list has been registered.')
1310        self.redirect(self.url(self.context))
1311        return
1312
1313class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1314    """Add a course ticket by student.
1315    """
1316    grok.name('ctadd')
1317    grok.require('waeup.handleStudent')
1318    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1319        'grade', 'score', 'core_or_elective', 'automatic')
1320
1321    @grok.action('Add course ticket')
1322    def addCourseTicket(self, **data):
1323        ticket = CourseTicket()
1324        course = data['course']
1325        ticket.automatic = False
1326        ticket.code = course.code
1327        ticket.title = course.title
1328        ticket.faculty = course.__parent__.__parent__.__parent__.title
1329        ticket.department = course.__parent__.__parent__.title
1330        ticket.credits = course.credits
1331        ticket.passmark = course.passmark
1332        ticket.semester = course.semester
1333        try:
1334            self.context.addCourseTicket(ticket)
1335        except KeyError:
1336            self.flash('The ticket exists.')
1337            return
1338        self.flash('Successfully added %s.' % ticket.code)
1339        self.redirect(self.url(self.context, u'@@edit'))
1340        return
Note: See TracBrowser for help on using the repository browser.