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

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

Provide a getPaymentDetails function which requires a SessionConfiguration? object. This function is for demonstration and testing only.

  • 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,
[6920]883            payment.surcharge_3,
884            error)  = getPaymentDetails(
[6876]885            data['p_category'],self.context.__parent__)
[6920]886        if error:
887            self.flash(error)
888            self.redirect(self.url(self.context, u'@@manage'))
889            return
[6869]890        self.context[payment.p_id] = payment
891        self.flash('Payment ticket created.')
892        self.redirect(self.url(self.context, u'@@manage'))
893        return
894
895class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
896    """ Page to view an online payment ticket
897    """
[6877]898    grok.context(IStudentOnlinePayment)
[6869]899    grok.name('index')
900    grok.require('waeup.viewStudent')
[6877]901    form_fields = grok.AutoFields(IStudentOnlinePayment)
[6869]902    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
903    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
904    pnav = 4
905
906    @property
907    def title(self):
908        return 'Online Payment Ticket %s' % self.context.p_id
909
910    @property
911    def label(self):
912        return '%s: Online Payment Ticket %s' % (
913            self.context.__parent__.__parent__.fullname,self.context.p_id)
914
[6635]915class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
916    """ Page to display the student accommodation data
917    """
918    grok.context(IStudentAccommodation)
919    grok.name('index')
[6660]920    grok.require('waeup.viewStudent')
[6635]921    form_fields = grok.AutoFields(IStudentAccommodation)
922    #grok.template('accommodationpage')
[6642]923    title = 'Accommodation'
924    pnav = 4
[6635]925
926    @property
927    def label(self):
[6818]928        return '%s: Accommodation Data' % self.context.__parent__.fullname
[6637]929
930class StudentHistoryPage(WAeUPPage):
931    """ Page to display student clearance data
932    """
933    grok.context(IStudent)
934    grok.name('history')
[6660]935    grok.require('waeup.viewStudent')
[6637]936    grok.template('studenthistory')
[6642]937    title = 'History'
938    pnav = 4
[6637]939
940    @property
941    def label(self):
[6818]942        return '%s: History' % self.context.fullname
[6694]943
944# Pages for students only
945
946class StudentBaseEditActionButton(ManageActionButton):
947    grok.order(1)
948    grok.context(IStudent)
949    grok.view(StudentBaseDisplayFormPage)
950    grok.require('waeup.handleStudent')
951    text = 'Change password'
952    target = 'bedit'
953
[6756]954class StudentPasswordSetting(grok.Adapter):
955    """Adapt IStudent to data needed for password settings.
956
957    We provide password getters/setters for the attached context (an
958    IStudent object) that cooperate seamless with the usual
959    formlib/form techniques.
960    """
961    grok.context(IStudent)
962    grok.provides(IStudentPasswordSetting)
963
964    def __init__(self, context):
[6818]965        self.name = context.fullname
[6756]966        self.password_repeat = context.password
967        self.context = context
968        return
969
970    def getPassword(self):
971        return self.context.password
972
973    def setPassword(self, password):
974        IUserAccount(self.context).setPassword(password)
975        return
976
977    password = property(getPassword, setPassword)
978
[6694]979class StudentBaseEditFormPage(WAeUPEditFormPage):
980    """ View to edit student base data by student
981    """
982    grok.context(IStudent)
983    grok.name('bedit')
984    grok.require('waeup.handleStudent')
[6756]985    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
986    #    'student_id', 'reg_number', 'matric_number')
987    form_fields = grok.AutoFields(IStudentPasswordSetting)
[6695]988    grok.template('baseeditpage')
[6694]989    label = 'Change password'
990    title = 'Base Data'
991    pnav = 4
992
993    def update(self):
994        super(StudentBaseEditFormPage, self).update()
995        self.wf_info = IWorkflowInfo(self.context)
996        return
997
[6756]998    def onFailure(self, action, data, errors):
999        new_status = []
1000        other_errors = False
1001        for error in errors:
1002            msg = getattr(error, 'message', '')
1003            if isinstance(msg, basestring) and msg != '':
1004                new_status.append(msg)
1005            else:
1006                other_errors = True
1007        if other_errors:
1008            if new_status:
1009                new_status.append('see below for further errors')
1010            else:
1011                new_status.append('See below for details.')
1012        if new_status:
1013            self.status = u'There were errors: %s' % ', '.join(new_status)
1014        return
1015
1016    @grok.action('Save', failure=onFailure)
[6694]1017    def save(self, **data):
[6771]1018        self.applyData(self.context, **data)
1019        self.flash('Form has been saved.')
[6694]1020        return
[6695]1021
[6719]1022class StudentClearanceStartActionButton(ManageActionButton):
1023    grok.order(1)
1024    grok.context(IStudent)
1025    grok.view(StudentClearanceDisplayFormPage)
1026    grok.require('waeup.handleStudent')
1027    icon = 'actionicon_start.png'
1028    text = 'Start clearance'
1029    target = 'start_clearance'
1030
1031    @property
1032    def target_url(self):
1033        if self.context.state != 'admitted':
1034            return ''
1035        return self.view.url(self.view.context, self.target)
1036
[6773]1037class StartClearancePage(WAeUPPage):
[6770]1038    grok.context(IStudent)
1039    grok.name('start_clearance')
1040    grok.require('waeup.handleStudent')
1041    grok.template('enterpin')
1042    title = 'Start clearance'
1043    label = 'Start clearance'
1044    ac_prefix = 'CLR'
1045    notice = ''
1046    pnav = 4
1047    buttonname = 'Start clearance now'
1048
1049    def update(self, SUBMIT=None):
1050        self.ac_series = self.request.form.get('ac_series', None)
1051        self.ac_number = self.request.form.get('ac_number', None)
1052
1053        if SUBMIT is None:
1054            return
1055        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1056        code = get_access_code(pin)
1057        if not code:
1058            self.flash('Access code is invalid.')
1059            return
1060        # Mark pin as used (this also fires a pin related transition)
1061        # and fire transition start_clearance
1062        if code.state == USED:
1063            self.flash('Access code has already been used.')
1064            return
1065        else:
1066            comment = u"AC invalidated for %s" % self.context.student_id
1067            # Here we know that the ac is in state initialized so we do not
1068            # expect an exception
1069            invalidate_accesscode(pin,comment)
1070            self.context.clr_code = pin
1071        IWorkflowInfo(self.context).fireTransition('start_clearance')
[6771]1072        self.flash('Clearance process has been started.')
[6770]1073        self.redirect(self.url(self.context,'cedit'))
1074        return
1075
[6695]1076class StudentClearanceEditActionButton(ManageActionButton):
1077    grok.order(1)
1078    grok.context(IStudent)
1079    grok.view(StudentClearanceDisplayFormPage)
1080    grok.require('waeup.handleStudent')
[6722]1081    text = 'Edit'
[6695]1082    target = 'cedit'
1083
[6717]1084    @property
1085    def target_url(self):
1086        if self.context.clearance_locked:
1087            return ''
1088        return self.view.url(self.view.context, self.target)
1089
[6695]1090class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1091    """ View to edit student clearance data by student
1092    """
1093    grok.context(IStudent)
1094    grok.name('cedit')
1095    grok.require('waeup.handleStudent')
[6756]1096    form_fields = grok.AutoFields(
1097        IStudentClearanceEdit).omit('clearance_locked')
[6722]1098    label = 'Edit clearance data'
[6695]1099    title = 'Clearance Data'
1100    pnav = 4
1101    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6718]1102
1103    def emitLockMessage(self):
1104        self.flash('The requested form is locked (read-only).')
1105        self.redirect(self.url(self.context))
1106        return
1107
1108    def update(self):
1109        if self.context.clearance_locked:
1110            self.emitLockMessage()
1111            return
1112        datepicker.need()
1113        return super(StudentClearanceEditFormPage, self).update()
[6719]1114
[6722]1115    @grok.action('Save')
1116    def save(self, **data):
1117        self.applyData(self.context, **data)
[6771]1118        self.flash('Clearance form has been saved.')
[6722]1119        return
1120
1121    @grok.action('Save and request clearance')
1122    def requestclearance(self, **data):
1123        self.applyData(self.context, **data)
1124        self.context._p_changed = True
1125        #if self.dataNotComplete():
1126        #    self.flash(self.dataNotComplete())
1127        #    return
[6771]1128        self.flash('Clearance form has been saved.')
[6769]1129        self.redirect(self.url(self.context,'request_clearance'))
[6722]1130        return
1131
[6773]1132class RequestClearancePage(WAeUPPage):
[6769]1133    grok.context(IStudent)
1134    grok.name('request_clearance')
1135    grok.require('waeup.handleStudent')
1136    grok.template('enterpin')
1137    title = 'Request clearance'
1138    label = 'Request clearance'
1139    notice = 'Enter the CLR access code used for starting clearance.'
1140    ac_prefix = 'CLR'
1141    pnav = 4
1142    buttonname = 'Request clearance now'
1143
1144    def update(self, SUBMIT=None):
1145        self.ac_series = self.request.form.get('ac_series', None)
1146        self.ac_number = self.request.form.get('ac_number', None)
1147        if SUBMIT is None:
1148            return
1149        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1150        if self.context.clr_code != pin:
1151            self.flash("This isn't your CLR access code.")
1152            return
1153        state = IWorkflowState(self.context).getState()
1154        # This shouldn't happen, but the application officer
1155        # might have forgotten to lock the form after changing the state
1156        if state != CLEARANCE:
1157            self.flash('This form cannot be submitted. Wrong state!')
1158            return
1159        IWorkflowInfo(self.context).fireTransition('request_clearance')
1160        self.flash('Clearance has been requested.')
1161        self.redirect(self.url(self.context))
[6789]1162        return
[6806]1163
[6808]1164class AddStudyLevelActionButton(AddActionButton):
1165    grok.order(1)
1166    grok.context(IStudentStudyCourse)
1167    grok.view(StudyCourseDisplayFormPage)
1168    grok.require('waeup.handleStudent')
1169    text = 'Add course list'
1170    target = 'add'
1171
1172    @property
1173    def target_url(self):
1174        student = self.view.context.getStudent()
1175        condition1 = student.state != 'school fee paid'
1176        condition2 = str(student['studycourse'].current_level) in \
1177            self.view.context.keys()
1178        if condition1 or condition2:
1179            return ''
1180        return self.view.url(self.view.context, self.target)
1181
[6806]1182class AddStudyLevelFormPage(WAeUPEditFormPage):
1183    """ Page for students to add current study levels
1184    """
1185    grok.context(IStudentStudyCourse)
1186    grok.name('add')
1187    grok.require('waeup.handleStudent')
1188    grok.template('studyleveladdpage')
1189    form_fields = grok.AutoFields(IStudentStudyCourse)
1190    title = 'Study Course'
1191    pnav = 4
1192
1193    @property
1194    def label(self):
1195        studylevelsource = StudyLevelSource().factory
1196        code = self.context.current_level
1197        title = studylevelsource.getTitle(self.context, code)
1198        return 'Add current level %s' % title
1199
1200    def emitLockMessage(self):
1201        self.flash('The requested form is locked (read-only).')
1202        self.redirect(self.url(self.context))
1203        return
1204
1205    def update(self):
1206        if self.context.getStudent().state != 'school fee paid':
1207            self.emitLockMessage()
1208            return
1209        super(AddStudyLevelFormPage, self).update()
1210        return
1211
1212    @grok.action('Create course list now')
1213    def addStudyLevel(self, **data):
1214        studylevel = StudentStudyLevel()
1215        studylevel.level = self.context.current_level
1216        studylevel.level_session = self.context.current_session
1217        try:
1218            self.context.addStudentStudyLevel(
1219                self.context.certificate,studylevel)
1220        except KeyError:
1221            self.flash('This level exists.')
1222        self.redirect(self.url(self.context))
1223        return
[6808]1224
1225class StudyLevelEditActionButton(ManageActionButton):
1226    grok.order(1)
1227    grok.context(IStudentStudyLevel)
1228    grok.view(StudyLevelDisplayFormPage)
1229    grok.require('waeup.handleStudent')
1230    text = 'Add and remove courses'
1231    target = 'edit'
1232
1233    @property
1234    def target_url(self):
1235        student = self.view.context.getStudent()
1236        condition1 = student.state != 'school fee paid'
1237        condition2 = student[
1238            'studycourse'].current_level != self.view.context.level
1239        if condition1 or condition2:
1240            return ''
1241        return self.view.url(self.view.context, self.target)
1242
1243class StudyLevelEditFormPage(WAeUPEditFormPage):
1244    """ Page to edit the student study level data by students
1245    """
1246    grok.context(IStudentStudyLevel)
1247    grok.name('edit')
1248    grok.require('waeup.handleStudent')
1249    grok.template('studyleveleditpage')
1250    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1251        'level_session', 'level_verdict')
1252    pnav = 4
1253
1254    def update(self):
1255        super(StudyLevelEditFormPage, self).update()
1256    #    tabs.need()
1257        datatable.need()
1258        return
1259
1260    @property
1261    def title(self):
1262        return 'Study Level %s' % self.context.level_title
1263
1264    @property
1265    def label(self):
1266        return 'Add and remove course tickets of study level %s' % self.context.level_title
1267
1268    @property
1269    def total_credits(self):
1270        total_credits = 0
1271        for key, val in self.context.items():
1272            total_credits += val.credits
1273        return total_credits
1274
1275    @grok.action('Add course ticket')
1276    def addCourseTicket(self, **data):
1277        self.redirect(self.url(self.context, 'ctadd'))
1278
1279    @grok.action('Remove selected tickets')
1280    def delCourseTicket(self, **data):
1281        form = self.request.form
1282        if form.has_key('val_id'):
1283            child_id = form['val_id']
1284        else:
1285            self.flash('No ticket selected.')
1286            self.redirect(self.url(self.context, '@@edit'))
1287            return
1288        if not isinstance(child_id, list):
1289            child_id = [child_id]
1290        deleted = []
1291        for id in child_id:
1292            # Student are not allowed to remove core tickets
1293            if not self.context[id].core_or_elective:
1294                try:
1295                    del self.context[id]
1296                    deleted.append(id)
1297                except:
1298                    self.flash('Could not delete %s: %s: %s' % (
1299                            id, sys.exc_info()[0], sys.exc_info()[1]))
1300        if len(deleted):
1301            self.flash('Successfully removed: %s' % ', '.join(deleted))
1302        self.redirect(self.url(self.context, u'@@edit'))
1303        return
1304
[6810]1305    @grok.action('Register course list')
1306    def register_courses(self, **data):
1307        state = IWorkflowState(self.context.getStudent()).getState()
1308        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1309        self.flash('Course list has been registered.')
1310        self.redirect(self.url(self.context))
1311        return
1312
[6808]1313class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1314    """Add a course ticket by student.
1315    """
1316    grok.name('ctadd')
1317    grok.require('waeup.handleStudent')
1318    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1319        'grade', 'score', 'core_or_elective', 'automatic')
1320
1321    @grok.action('Add course ticket')
1322    def addCourseTicket(self, **data):
1323        ticket = CourseTicket()
1324        course = data['course']
1325        ticket.automatic = False
1326        ticket.code = course.code
1327        ticket.title = course.title
1328        ticket.faculty = course.__parent__.__parent__.__parent__.title
1329        ticket.department = course.__parent__.__parent__.title
1330        ticket.credits = course.credits
1331        ticket.passmark = course.passmark
1332        ticket.semester = course.semester
1333        try:
1334            self.context.addCourseTicket(ticket)
1335        except KeyError:
1336            self.flash('The ticket exists.')
1337            return
1338        self.flash('Successfully added %s.' % ticket.code)
1339        self.redirect(self.url(self.context, u'@@edit'))
1340        return
Note: See TracBrowser for help on using the repository browser.