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

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

Add Views for IBedTicket instances.

To get the old ZODB working, the following commands have to executed in the debug mode:

rootwaeup?configuration?.accommodation_states = []
import transaction
transaction.commit()

(provided that the University instance is called 'waeup').

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