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

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

Show current study mode on StudyCourseDisplayFormPage?.

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