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

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

Add payment breadcrumb.

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