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

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

In Student Online Payments we have to store the item session. Thus we need StudentOnlinePayment? must get its own interface which includes a session attribute.

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