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

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

Add a 'toggle all' Javascript function to klick all select boxes at the same time in in large forms. Use this function in the containermanagepage.pt.

jquery.dataTables.min.js: Allow viewing 1000 and even 5000 rows.

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