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

Last change on this file since 6875 was 6870, checked in by Henrik Bettermann, 14 years ago

Add payment breadcrumb.

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