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

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

In the base package we can only create payment tickets with amount = 0. Amount calculation must be done in customization packages.

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