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

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

Show current study mode on StudyCourseDisplayFormPage?.

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