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

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

Implement download page for pdf payment receipts (ExportPDFPaymentSlipPage) including student base data.

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