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

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

Implement write_log_message function and log removal of payments as well as valid callbacks.

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