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

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

Implement ExportPDFCourseRegistrationSlipPage (work in progess).

PDF payment receipt mustn't be available if ticket has not yet been paid.

  • Property svn:keywords set to Id
File size: 64.1 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 CourseRegistrationSlipActionButton(ManageActionButton):
645    grok.order(1)
646    grok.context(IStudentStudyLevel)
647    grok.require('waeup.viewStudent')
648    icon = 'actionicon_pdf.png'
649    text = 'Download course registration slip'
650    target = 'course_registration.pdf'
651
652class ExportPDFCourseRegistrationSlipPage(grok.View):
653    """Deliver a PDF slip of the context.
654    """
655    grok.context(IStudentStudyLevel)
656    grok.name('course_registration.pdf')
657    grok.require('waeup.viewStudent')
658    form_fields = grok.AutoFields(IStudentStudyLevel)
659    prefix = 'form'
660
661    @property
662    def label(self):
663        return 'Course Registration Slip %s' % self.context.level_title
664
665    def render(self):
666        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
667            self.request)
668        return renderPDF(self,'Course Registration', 'course_registration.pdf',
669            self.context.getStudent, studentview)
670
671class StudyLevelManageActionButton(ManageActionButton):
672    grok.order(2)
673    grok.context(IStudentStudyLevel)
674    grok.view(StudyLevelDisplayFormPage)
675    grok.require('waeup.manageStudents')
676    text = 'Manage'
677    target = 'manage'
678
679class StudyLevelManageFormPage(WAeUPEditFormPage):
680    """ Page to edit the student study level data
681    """
682    grok.context(IStudentStudyLevel)
683    grok.name('manage')
684    grok.require('waeup.manageStudents')
685    grok.template('studylevelmanagepage')
686    form_fields = grok.AutoFields(IStudentStudyLevel)
687    pnav = 4
688    taboneactions = ['Save','Cancel']
689    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
690
691    def update(self):
692        super(StudyLevelManageFormPage, self).update()
693        tabs.need()
694        datatable.need()
695        return
696
697    @property
698    def title(self):
699        return 'Study Level %s' % self.context.level_title
700
701    @property
702    def label(self):
703        return 'Manage study level %s' % self.context.level_title
704
705    @grok.action('Save')
706    def save(self, **data):
707        msave(self, **data)
708        return
709
710    @grok.action('Add course ticket')
711    def addCourseTicket(self, **data):
712        self.redirect(self.url(self.context, '@@add'))
713
714    @grok.action('Remove selected tickets')
715    def delCourseTicket(self, **data):
716        form = self.request.form
717        if form.has_key('val_id'):
718            child_id = form['val_id']
719        else:
720            self.flash('No ticket selected.')
721            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
722            return
723        if not isinstance(child_id, list):
724            child_id = [child_id]
725        deleted = []
726        for id in child_id:
727            try:
728                del self.context[id]
729                deleted.append(id)
730            except:
731                self.flash('Could not delete %s: %s: %s' % (
732                        id, sys.exc_info()[0], sys.exc_info()[1]))
733        if len(deleted):
734            self.flash('Successfully removed: %s' % ', '.join(deleted))
735        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
736        return
737
738class CourseTicketAddFormPage(WAeUPAddFormPage):
739    """Add a course ticket.
740    """
741    grok.context(IStudentStudyLevel)
742    grok.name('add')
743    grok.require('waeup.manageStudents')
744    label = 'Add course ticket'
745    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
746        'grade', 'score', 'automatic')
747    pnav = 4
748
749    @property
750    def title(self):
751        return 'Study Level %s' % self.context.level_title
752
753    @grok.action('Add course ticket')
754    def addCourseTicket(self, **data):
755        ticket = CourseTicket()
756        course = data['course']
757        ticket.core_or_elective = data['core_or_elective']
758        ticket.automatic = False
759        ticket.code = course.code
760        ticket.title = course.title
761        ticket.faculty = course.__parent__.__parent__.__parent__.title
762        ticket.department = course.__parent__.__parent__.title
763        ticket.credits = course.credits
764        ticket.passmark = course.passmark
765        ticket.semester = course.semester
766        try:
767            self.context.addCourseTicket(ticket)
768        except KeyError:
769            self.flash('The ticket exists.')
770            return
771        self.flash('Successfully added %s.' % ticket.code)
772        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
773        return
774
775    @grok.action('Cancel')
776    def cancel(self, **data):
777        self.redirect(self.url(self.context))
778
779class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
780    """ Page to display course tickets
781    """
782    grok.context(ICourseTicket)
783    grok.name('index')
784    grok.require('waeup.viewStudent')
785    form_fields = grok.AutoFields(ICourseTicket)
786    grok.template('courseticketpage')
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 '%s: Course Ticket %s' % (
796            self.context.getStudent().fullname,self.context.code)
797
798class CourseTicketManageActionButton(ManageActionButton):
799    grok.order(1)
800    grok.context(ICourseTicket)
801    grok.view(CourseTicketDisplayFormPage)
802    grok.require('waeup.manageStudents')
803    text = 'Manage'
804    target = 'manage'
805
806class CourseTicketManageFormPage(WAeUPEditFormPage):
807    """ Page to manage course tickets
808    """
809    grok.context(ICourseTicket)
810    grok.name('manage')
811    grok.require('waeup.manageStudents')
812    form_fields = grok.AutoFields(ICourseTicket)
813    grok.template('courseticketmanagepage')
814    pnav = 4
815
816    @property
817    def title(self):
818        return 'Course Ticket %s' % self.context.code
819
820    @property
821    def label(self):
822        return 'Manage course ticket %s' % self.context.code
823
824    @grok.action('Save')
825    def save(self, **data):
826        msave(self, **data)
827        return
828
829# We don't need the display form page yet
830#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
831#    """ Page to display the student payments
832#    """
833#    grok.context(IStudentPaymentsContainer)
834#    grok.name('view')
835#    grok.require('waeup.viewStudent')
836#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
837#    grok.template('paymentspage')
838#    title = 'Payments'
839#    pnav = 4
840
841#    def formatDatetime(self,datetimeobj):
842#        if isinstance(datetimeobj, datetime):
843#            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
844#        else:
845#            return None
846
847#    @property
848#    def label(self):
849#        return '%s: Payments' % self.context.__parent__.fullname
850
851#    def update(self):
852#        super(PaymentsDisplayFormPage, self).update()
853#        datatable.need()
854#        return
855
856# This manage form page is for both students and students officers.
857class PaymentsManageFormPage(WAeUPEditFormPage):
858    """ Page to manage the student payments
859    """
860    grok.context(IStudentPaymentsContainer)
861    grok.name('index')
862    grok.require('waeup.handleStudent')
863    form_fields = grok.AutoFields(IStudentPaymentsContainer)
864    grok.template('paymentsmanagepage')
865    title = 'Payments'
866    pnav = 4
867
868    def unremovable(self, ticket):
869        prm = get_principal_role_manager()
870        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
871        return ('waeup.Student' in roles and ticket.r_code)
872
873    def formatDatetime(self,datetimeobj):
874        if isinstance(datetimeobj, datetime):
875            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
876        else:
877            return None
878
879    @property
880    def label(self):
881        return '%s: Payments' % self.context.__parent__.fullname
882
883    def update(self):
884        super(PaymentsManageFormPage, self).update()
885        datatable.need()
886        return
887
888    @grok.action('Remove selected tickets')
889    def delPaymentTicket(self, **data):
890        form = self.request.form
891        if form.has_key('val_id'):
892            child_id = form['val_id']
893        else:
894            self.flash('No payment selected.')
895            self.redirect(self.url(self.context))
896            return
897        if not isinstance(child_id, list):
898            child_id = [child_id]
899        deleted = []
900        for id in child_id:
901            # Students are not allowed to remove used payment tickets
902            if not self.unremovable(self.context[id]):
903                try:
904                    del self.context[id]
905                    deleted.append(id)
906                except:
907                    self.flash('Could not delete %s: %s: %s' % (
908                            id, sys.exc_info()[0], sys.exc_info()[1]))
909        if len(deleted):
910            self.flash('Successfully removed: %s' % ', '.join(deleted))
911            write_log_message(self,'removed: % s' % ', '.join(deleted))
912        self.redirect(self.url(self.context))
913        return
914
915    @grok.action('Add online payment ticket')
916    def addPaymentTicket(self, **data):
917        self.redirect(self.url(self.context, '@@addop'))
918
919#class OnlinePaymentManageActionButton(ManageActionButton):
920#    grok.order(1)
921#    grok.context(IStudentPaymentsContainer)
922#    grok.view(PaymentsDisplayFormPage)
923#    grok.require('waeup.manageStudents')
924#    text = 'Manage payments'
925#    target = 'manage'
926
927class OnlinePaymentAddFormPage(WAeUPAddFormPage):
928    """ Page to add an online payment ticket
929    """
930    grok.context(IStudentPaymentsContainer)
931    grok.name('addop')
932    grok.require('waeup.handleStudent')
933    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
934        'p_category')
935    #zzgrok.template('addpaymentpage')
936    label = 'Add online payment'
937    title = 'Payments'
938    pnav = 4
939   
940    # To be sepezified in customization packages
941    def getPaymentDetails(self, category, student):
942        return getPaymentDetails(category, student)
943
944    @grok.action('Create ticket')
945    def createTicket(self, **data):
946        p_category = data['p_category']
947        student = self.context.__parent__
948        if p_category == 'bed_allocation' and student[
949            'studycourse'].current_session != grok.getSite()[
950            'configuration'].accommodation_session:
951                self.flash(
952                    'Your current session does not match accommodation session.')
953                self.redirect(self.url(self.context))
954                return
955        pay_details  = self.getPaymentDetails(
956            p_category,student)
957        if pay_details['error']:
958            self.flash(pay_details['error'])
959            self.redirect(self.url(self.context))
960            return
961        p_item = pay_details['p_item']
962        p_session = pay_details['p_session']
963        for key in self.context.keys():
964            ticket = self.context[key]
965            if ticket.p_category == p_category and \
966               ticket.p_item == p_item and \
967               ticket.p_session == p_session:
968                  self.flash(
969                      'This payment ticket already exists.')
970                  self.redirect(self.url(self.context))
971                  return
972        payment = createObject(u'waeup.StudentOnlinePayment')
973        self.applyData(payment, **data)
974        timestamp = "%d" % int(time()*1000)
975        #order_id = "%s%s" % (student_id[1:],timestamp)
976        payment.p_id = "p%s" % timestamp
977        payment.p_item = p_item
978        payment.p_session = p_session
979        payment.amount_auth = pay_details['amount']
980        payment.surcharge_1 = pay_details['surcharge_1']
981        payment.surcharge_2 = pay_details['surcharge_2']
982        payment.surcharge_3 = pay_details['surcharge_3']
983        self.context[payment.p_id] = payment
984        self.flash('Payment ticket created.')
985        self.redirect(self.url(self.context))
986        return
987
988class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
989    """ Page to view an online payment ticket
990    """
991    grok.context(IStudentOnlinePayment)
992    grok.name('index')
993    grok.require('waeup.viewStudent')
994    form_fields = grok.AutoFields(IStudentOnlinePayment)
995    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
996    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
997    pnav = 4
998
999    @property
1000    def title(self):
1001        return 'Online Payment Ticket %s' % self.context.p_id
1002
1003    @property
1004    def label(self):
1005        return '%s: Online Payment Ticket %s' % (
1006            self.context.getStudent().fullname,self.context.p_id)
1007
1008class PaymentReceiptActionButton(ManageActionButton):
1009    grok.order(1)
1010    grok.context(IStudentOnlinePayment)
1011    grok.require('waeup.viewStudent')
1012    icon = 'actionicon_pdf.png'
1013    text = 'Download payment receipt'
1014    target = 'payment_receipt.pdf'
1015
1016    @property
1017    def target_url(self):
1018        if self.context.p_state != 'paid':
1019            return ''
1020        return self.view.url(self.view.context, self.target)
1021
1022class OnlinePaymentCallbackPage(grok.View):
1023    """ Callback view
1024    """
1025    grok.context(IStudentOnlinePayment)
1026    grok.name('callback')
1027    grok.require('waeup.payStudent')
1028
1029    # This update method simulates a valid callback und must be
1030    # specified in the customization package. The parameters must be taken
1031    # from the incoming request.
1032    def update(self):
1033        if self.context.p_state == 'paid':
1034            self.flash('This ticket has already been paid.')
1035            return
1036        student = self.context.getStudent()
1037        write_log_message(self,'valid callback: %s' % self.context.p_id)
1038        self.context.r_amount_approved = self.context.amount_auth
1039        self.context.r_card_num = u'0000'
1040        self.context.r_code = u'00'
1041        self.context.p_state = 'paid'
1042        self.context.payment_date = datetime.now()
1043        if self.context.p_category == 'clearance':
1044            # Create CLR access code
1045            pin, error = create_accesscode('CLR',0,student.student_id)
1046            if error:
1047                self.flash('Valid callback received. ' + error)
1048                return
1049            self.context.ac = pin
1050        elif self.context.p_category == 'schoolfee':
1051            # Create SFE access code
1052            pin, error = create_accesscode('SFE',0,student.student_id)
1053            if error:
1054                self.flash('Valid callback received. ' + error)
1055                return
1056            self.context.ac = pin
1057        elif self.context.p_category == 'bed_allocation':
1058            # Create HOS access code
1059            pin, error = create_accesscode('HOS',0,student.student_id)
1060            if error:
1061                self.flash('Valid callback received. ' + error)
1062                return
1063            self.context.ac = pin
1064        self.flash('Valid callback received.')
1065        return
1066
1067    def render(self):
1068        self.redirect(self.url(self.context, '@@index'))
1069        return
1070
1071class ExportPDFPaymentSlipPage(grok.View):
1072    """Deliver a PDF slip of the context.
1073    """
1074    grok.context(IStudentOnlinePayment)
1075    grok.name('payment_receipt.pdf')
1076    grok.require('waeup.viewStudent')
1077    form_fields = grok.AutoFields(IStudentOnlinePayment)
1078    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1079    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1080    prefix = 'form'
1081
1082    @property
1083    def label(self):
1084        return 'Online Payment Receipt %s' % self.context.p_id
1085
1086    def render(self):
1087        if self.context.p_state != 'paid':
1088            self.flash('Ticket not yet paid.')
1089            self.redirect(self.url(self.context))
1090            return
1091        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1092            self.request)
1093        return renderPDF(self,'Payment', 'payment_receipt.pdf',
1094            self.context.getStudent, studentview)
1095
1096# We don't need the display form page yet
1097#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1098#    """ Page to display the student accommodation data
1099#    """
1100#    grok.context(IStudentAccommodation)
1101#    grok.name('xxx')
1102#    grok.require('waeup.viewStudent')
1103#    form_fields = grok.AutoFields(IStudentAccommodation)
1104#    #grok.template('accommodationpage')
1105#    title = 'Accommodation'
1106#    pnav = 4
1107
1108#    @property
1109#    def label(self):
1110#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1111
1112# This manage form page is for both students and students officers.
1113class AccommodationManageFormPage(WAeUPEditFormPage):
1114    """ Page to manage bed tickets.
1115    """
1116    grok.context(IStudentAccommodation)
1117    grok.name('index')
1118    grok.require('waeup.handleStudent')
1119    form_fields = grok.AutoFields(IStudentAccommodation)
1120    grok.template('accommodationmanagepage')
1121    title = 'Accommodation'
1122    pnav = 4
1123    officers_only_actions = ['Remove selected']
1124
1125    def formatDatetime(self,datetimeobj):
1126        if isinstance(datetimeobj, datetime):
1127            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1128        else:
1129            return None
1130
1131    @property
1132    def label(self):
1133        return '%s: Accommodation' % self.context.__parent__.fullname
1134
1135    def update(self):
1136        super(AccommodationManageFormPage, self).update()
1137        datatable.need()
1138        return
1139
1140    @property
1141    def is_student(self):
1142        prm = get_principal_role_manager()
1143        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
1144        return 'waeup.Student' in roles
1145
1146    @grok.action('Remove selected')
1147    def delBedTickets(self, **data):
1148        if self.is_student:
1149            self.flash('You are not allowed to remove bed tickets.')
1150            self.redirect(self.url(self.context))
1151            return
1152        form = self.request.form
1153        if form.has_key('val_id'):
1154            child_id = form['val_id']
1155        else:
1156            self.flash('No bed ticket selected.')
1157            self.redirect(self.url(self.context))
1158            return
1159        if not isinstance(child_id, list):
1160            child_id = [child_id]
1161        deleted = []
1162        for id in child_id:
1163            try:
1164                del self.context[id]
1165                deleted.append(id)
1166            except:
1167                self.flash('Could not delete %s: %s: %s' % (
1168                        id, sys.exc_info()[0], sys.exc_info()[1]))
1169        if len(deleted):
1170            self.flash('Successfully removed: %s' % ', '.join(deleted))
1171            write_log_message(self,'removed: % s' % ', '.join(deleted))
1172        self.redirect(self.url(self.context))
1173        return
1174
1175    @property
1176    def selected_actions(self):
1177        sa = self.actions
1178        if self.is_student:
1179            sa = [action for action in self.actions
1180                  if not action.label in self.officers_only_actions]
1181        return sa
1182
1183class AddBedTicketActionButton(ManageActionButton):
1184    grok.order(1)
1185    grok.context(IStudentAccommodation)
1186    grok.view(AccommodationManageFormPage)
1187    grok.require('waeup.handleStudent')
1188    icon = 'actionicon_home.png'
1189    text = 'Book accommodation'
1190    target = 'add'
1191
1192class BedTicketAddPage(WAeUPPage):
1193    """ Page to add an online payment ticket
1194    """
1195    grok.context(IStudentAccommodation)
1196    grok.name('add')
1197    grok.require('waeup.handleStudent')
1198    grok.template('enterpin')
1199    ac_prefix = 'HOS'
1200    label = 'Add bed ticket'
1201    title = 'Add bed ticket'
1202    pnav = 4
1203    buttonname = 'Create bed ticket'
1204    notice = ''
1205
1206    # To be sepezified in customization packages
1207    def getAccommodationDetails(self, student):
1208        return getAccommodationDetails(student)
1209
1210    # To be sepezified in customization packages
1211    def selectBed(self, available_beds):
1212        return selectBed(available_beds)
1213
1214    def update(self, SUBMIT=None):
1215        student = self.context.getStudent()
1216        acc_details  = self.getAccommodationDetails(student)
1217        if not student.state in acc_details['allowed_states']:
1218            self.flash("You are in the wrong registration state.")
1219            self.redirect(self.url(self.context))
1220            return
1221        if str(grok.getSite()[
1222                'configuration'].accommodation_session) in self.context.keys():
1223            self.flash('You already booked a bed space for the current accommodation session.')
1224            self.redirect(self.url(self.context))
1225            return
1226        self.ac_series = self.request.form.get('ac_series', None)
1227        self.ac_number = self.request.form.get('ac_number', None)
1228        if SUBMIT is None:
1229            return
1230        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1231        code = get_access_code(pin)
1232        if not code:
1233            self.flash('Activation code is invalid.')
1234            return
1235        # Search a bed and exit if no bed is found or if the student
1236        # has already booked a bed
1237        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1238        entries = cat.searchResults(
1239            bed_type=(acc_details['bt'],acc_details['bt']))
1240        available_beds = [
1241            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1242        if not available_beds:
1243            self.flash('There is no free bed in your category %s.'
1244                        % acc_details['bt'])
1245            return
1246        entries = cat.searchResults(
1247            owner=(student.student_id,student.student_id))
1248        # Cannot happen but anyway ...
1249        if len(entries):
1250            bed = [entry for entry in entries][0]
1251            self.flash('You already booked bed %s.'
1252                        % bed.bed_id)
1253            return
1254        # Mark pin as used (this also fires a pin related transition)
1255        if code.state == USED:
1256            self.flash('Activation code has already been used.')
1257            return
1258        else:
1259            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1260            # Here we know that the ac is in state initialized so we do not
1261            # expect an exception, but the owner might be different
1262            if not invalidate_accesscode(
1263                pin,comment,self.context.getStudent().student_id):
1264                self.flash('You are not the owner of this access code.')
1265                return
1266        # Create bed ticket and book bed
1267        bedticket = createObject(u'waeup.BedTicket')
1268        bedticket.booking_code = pin
1269        bedticket.booking_session = acc_details['booking_session']
1270        bedticket.bed_type = acc_details['bt']
1271        bed = self.selectBed(available_beds)
1272        bed.bookBed(student.student_id)
1273        bedticket.bed = bed
1274        hall_title = bed.__parent__.hostel_name
1275        coordinates = bed.getBedCoordinates()[1:]
1276        block, room_nr, bed_nr = coordinates
1277        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1278            hall_title, block, room_nr, bed_nr)
1279        key = str(acc_details['booking_session'])
1280        self.context[key] = bedticket
1281        self.flash('Bed ticket created and bed booked: %s'
1282            % bedticket.bed_coordinates)
1283        self.redirect(self.url(self.context))
1284        return
1285
1286class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1287    """ Page to display bed tickets
1288    """
1289    grok.context(IBedTicket)
1290    grok.name('index')
1291    grok.require('waeup.viewStudent')
1292    form_fields = grok.AutoFields(IBedTicket)
1293    form_fields[
1294        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1295    pnav = 4
1296
1297    @property
1298    def label(self):
1299        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1300
1301    @property
1302    def title(self):
1303        return 'Bed Ticket %s' % self.context.getSessionString()
1304
1305class BedTicketSlipActionButton(ManageActionButton):
1306    grok.order(1)
1307    grok.context(IBedTicket)
1308    grok.require('waeup.viewStudent')
1309    icon = 'actionicon_pdf.png'
1310    text = 'Download bed allocation slip'
1311    target = 'bed_allocation.pdf'
1312
1313class ExportPDFBedTicketSlipPage(grok.View):
1314    """Deliver a PDF slip of the context.
1315    """
1316    grok.context(IBedTicket)
1317    grok.name('bed_allocation.pdf')
1318    grok.require('waeup.viewStudent')
1319    form_fields = grok.AutoFields(IBedTicket)
1320    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1321    prefix = 'form'
1322
1323    @property
1324    def label(self):
1325        return 'Bed Allocation %s' % self.context.bed_coordinates
1326
1327    def render(self):
1328        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1329            self.request)
1330        return renderPDF(self,'Bed Allocation', 'bed_allocation.pdf',
1331            self.context.getStudent, studentview)
1332
1333class RelocateStudentActionButton(ManageActionButton):
1334    grok.order(2)
1335    grok.context(IBedTicket)
1336    grok.view(BedTicketDisplayFormPage)
1337    grok.require('waeup.manageHostels')
1338    icon = 'actionicon_reload.png'
1339    text = 'Relocate student'
1340    target = 'relocate'
1341
1342class BedTicketRelocationPage(grok.View):
1343    """ Callback view
1344    """
1345    grok.context(IBedTicket)
1346    grok.name('relocate')
1347    grok.require('waeup.manageHostels')
1348
1349    # To be sepezified in customization packages
1350    def getAccommodationDetails(self, student):
1351        return getAccommodationDetails(student)
1352
1353    # To be sepezified in customization packages
1354    def selectBed(self, available_beds):
1355        return selectBed(available_beds)
1356
1357    # This update method simulates a valid callback und must be
1358    # specified in the customization package. The parameters must be taken
1359    # from the incoming request.
1360    def update(self):
1361        student = self.context.getStudent()
1362        acc_details  = self.getAccommodationDetails(student)
1363        if acc_details['bt'] == self.context.bed_type:
1364            self.flash("Bed category hasn't changed. Student can't be relocated.")
1365            return
1366        # Search a bed and exit if no bed is found or if the student
1367        # has already booked a bed
1368        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1369        entries = cat.searchResults(
1370            bed_type=(acc_details['bt'],acc_details['bt']))
1371        available_beds = [
1372            entry for entry in entries if entry.owner == NOT_OCCUPIED]
1373        if not available_beds:
1374            self.flash('There is no free bed in your category %s.'
1375                        % acc_details['bt'])
1376            return
1377        entries = cat.searchResults(
1378            owner=(student.student_id,student.student_id))
1379        # Rlease old bed
1380        allocated_bed = self.context.bed
1381        allocated_bed.owner = NOT_OCCUPIED
1382        notify(grok.ObjectModifiedEvent(allocated_bed))
1383        # Alocate new bed
1384        self.context.bed_type = acc_details['bt']
1385        bed = self.selectBed(available_beds)
1386        bed.bookBed(student.student_id)
1387        self.context.bed = bed
1388        hall_title = bed.__parent__.hostel_name
1389        coordinates = bed.getBedCoordinates()[1:]
1390        block, room_nr, bed_nr = coordinates
1391        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % (
1392            hall_title, block, room_nr, bed_nr)
1393        self.flash('Student relocated and new bed booked: %s'
1394            % self.context.bed_coordinates)
1395        self.redirect(self.url(self.context))
1396        return
1397
1398    def render(self):
1399        self.redirect(self.url(self.context, '@@index'))
1400        return
1401
1402class StudentHistoryPage(WAeUPPage):
1403    """ Page to display student clearance data
1404    """
1405    grok.context(IStudent)
1406    grok.name('history')
1407    grok.require('waeup.viewStudent')
1408    grok.template('studenthistory')
1409    title = 'History'
1410    pnav = 4
1411
1412    @property
1413    def label(self):
1414        return '%s: History' % self.context.fullname
1415
1416# Pages for students only
1417
1418class StudentBaseEditActionButton(ManageActionButton):
1419    grok.order(1)
1420    grok.context(IStudent)
1421    grok.view(StudentBaseDisplayFormPage)
1422    grok.require('waeup.handleStudent')
1423    text = 'Change password'
1424    target = 'bedit'
1425
1426class StudentPasswordSetting(grok.Adapter):
1427    """Adapt IStudent to data needed for password settings.
1428
1429    We provide password getters/setters for the attached context (an
1430    IStudent object) that cooperate seamless with the usual
1431    formlib/form techniques.
1432    """
1433    grok.context(IStudent)
1434    grok.provides(IStudentPasswordSetting)
1435
1436    def __init__(self, context):
1437        self.name = context.fullname
1438        self.password_repeat = context.password
1439        self.context = context
1440        return
1441
1442    def getPassword(self):
1443        return self.context.password
1444
1445    def setPassword(self, password):
1446        IUserAccount(self.context).setPassword(password)
1447        return
1448
1449    password = property(getPassword, setPassword)
1450
1451class StudentBaseEditFormPage(WAeUPEditFormPage):
1452    """ View to edit student base data by student
1453    """
1454    grok.context(IStudent)
1455    grok.name('bedit')
1456    grok.require('waeup.handleStudent')
1457    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
1458    #    'student_id', 'reg_number', 'matric_number')
1459    form_fields = grok.AutoFields(IStudentPasswordSetting)
1460    grok.template('baseeditpage')
1461    label = 'Change password'
1462    title = 'Base Data'
1463    pnav = 4
1464
1465    def update(self):
1466        super(StudentBaseEditFormPage, self).update()
1467        self.wf_info = IWorkflowInfo(self.context)
1468        return
1469
1470    def onFailure(self, action, data, errors):
1471        new_status = []
1472        other_errors = False
1473        for error in errors:
1474            msg = getattr(error, 'message', '')
1475            if isinstance(msg, basestring) and msg != '':
1476                new_status.append(msg)
1477            else:
1478                other_errors = True
1479        if other_errors:
1480            if new_status:
1481                new_status.append('see below for further errors')
1482            else:
1483                new_status.append('See below for details.')
1484        if new_status:
1485            self.status = u'There were errors: %s' % ', '.join(new_status)
1486        return
1487
1488    @grok.action('Save', failure=onFailure)
1489    def save(self, **data):
1490        self.applyData(self.context, **data)
1491        self.flash('Form has been saved.')
1492        return
1493
1494class StudentClearanceStartActionButton(ManageActionButton):
1495    grok.order(1)
1496    grok.context(IStudent)
1497    grok.view(StudentClearanceDisplayFormPage)
1498    grok.require('waeup.handleStudent')
1499    icon = 'actionicon_start.png'
1500    text = 'Start clearance'
1501    target = 'start_clearance'
1502
1503    @property
1504    def target_url(self):
1505        if self.context.state != 'admitted':
1506            return ''
1507        return self.view.url(self.view.context, self.target)
1508
1509class StartClearancePage(WAeUPPage):
1510    grok.context(IStudent)
1511    grok.name('start_clearance')
1512    grok.require('waeup.handleStudent')
1513    grok.template('enterpin')
1514    title = 'Start clearance'
1515    label = 'Start clearance'
1516    ac_prefix = 'CLR'
1517    notice = ''
1518    pnav = 4
1519    buttonname = 'Start clearance now'
1520
1521    def update(self, SUBMIT=None):
1522        if not self.context.state == 'admitted':
1523            self.flash("Wrong state.")
1524            self.redirect(self.url(self.context))
1525            return
1526        self.ac_series = self.request.form.get('ac_series', None)
1527        self.ac_number = self.request.form.get('ac_number', None)
1528
1529        if SUBMIT is None:
1530            return
1531        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1532        code = get_access_code(pin)
1533        if not code:
1534            self.flash('Activation code is invalid.')
1535            return
1536        # Mark pin as used (this also fires a pin related transition)
1537        # and fire transition start_clearance
1538        if code.state == USED:
1539            self.flash('Activation code has already been used.')
1540            return
1541        else:
1542            comment = u"AC invalidated for %s" % self.context.student_id
1543            # Here we know that the ac is in state initialized so we do not
1544            # expect an exception, but the owner might be different
1545            if not invalidate_accesscode(pin,comment,self.context.student_id):
1546                self.flash('You are not the owner of this access code.')
1547                return
1548            self.context.clr_code = pin
1549        IWorkflowInfo(self.context).fireTransition('start_clearance')
1550        self.flash('Clearance process has been started.')
1551        self.redirect(self.url(self.context,'cedit'))
1552        return
1553
1554class StudentClearanceEditActionButton(ManageActionButton):
1555    grok.order(1)
1556    grok.context(IStudent)
1557    grok.view(StudentClearanceDisplayFormPage)
1558    grok.require('waeup.handleStudent')
1559    text = 'Edit'
1560    target = 'cedit'
1561
1562    @property
1563    def target_url(self):
1564        if self.context.clearance_locked:
1565            return ''
1566        return self.view.url(self.view.context, self.target)
1567
1568class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1569    """ View to edit student clearance data by student
1570    """
1571    grok.context(IStudent)
1572    grok.name('cedit')
1573    grok.require('waeup.handleStudent')
1574    form_fields = grok.AutoFields(
1575        IStudentClearanceEdit).omit('clearance_locked')
1576    label = 'Edit clearance data'
1577    title = 'Clearance Data'
1578    pnav = 4
1579    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1580
1581    def emitLockMessage(self):
1582        self.flash('The requested form is locked (read-only).')
1583        self.redirect(self.url(self.context))
1584        return
1585
1586    def update(self):
1587        if self.context.clearance_locked:
1588            self.emitLockMessage()
1589            return
1590        datepicker.need()
1591        return super(StudentClearanceEditFormPage, self).update()
1592
1593    @grok.action('Save')
1594    def save(self, **data):
1595        self.applyData(self.context, **data)
1596        self.flash('Clearance form has been saved.')
1597        return
1598
1599    @grok.action('Save and request clearance')
1600    def requestclearance(self, **data):
1601        self.applyData(self.context, **data)
1602        self.context._p_changed = True
1603        #if self.dataNotComplete():
1604        #    self.flash(self.dataNotComplete())
1605        #    return
1606        self.flash('Clearance form has been saved.')
1607        self.redirect(self.url(self.context,'request_clearance'))
1608        return
1609
1610class RequestClearancePage(WAeUPPage):
1611    grok.context(IStudent)
1612    grok.name('request_clearance')
1613    grok.require('waeup.handleStudent')
1614    grok.template('enterpin')
1615    title = 'Request clearance'
1616    label = 'Request clearance'
1617    notice = 'Enter the CLR access code used for starting clearance.'
1618    ac_prefix = 'CLR'
1619    pnav = 4
1620    buttonname = 'Request clearance now'
1621
1622    def update(self, SUBMIT=None):
1623        self.ac_series = self.request.form.get('ac_series', None)
1624        self.ac_number = self.request.form.get('ac_number', None)
1625        if SUBMIT is None:
1626            return
1627        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1628        if self.context.clr_code != pin:
1629            self.flash("This isn't your CLR access code.")
1630            return
1631        state = IWorkflowState(self.context).getState()
1632        # This shouldn't happen, but the application officer
1633        # might have forgotten to lock the form after changing the state
1634        if state != CLEARANCE:
1635            self.flash('This form cannot be submitted. Wrong state!')
1636            return
1637        IWorkflowInfo(self.context).fireTransition('request_clearance')
1638        self.flash('Clearance has been requested.')
1639        self.redirect(self.url(self.context))
1640        return
1641
1642class CourseRegistrationStartActionButton(ManageActionButton):
1643    grok.order(1)
1644    grok.context(IStudentStudyCourse)
1645    grok.view(StudyCourseDisplayFormPage)
1646    grok.require('waeup.handleStudent')
1647    icon = 'actionicon_start.png'
1648    text = 'Start course registration'
1649    target = 'start_course_registration'
1650
1651    @property
1652    def target_url(self):
1653        if not self.context.getStudent().state in (CLEARED,RETURNING):
1654            return ''
1655        return self.view.url(self.view.context, self.target)
1656
1657class StartCourseRegistrationPage(WAeUPPage):
1658    grok.context(IStudentStudyCourse)
1659    grok.name('start_course_registration')
1660    grok.require('waeup.handleStudent')
1661    grok.template('enterpin')
1662    title = 'Start course registration'
1663    label = 'Start course registration'
1664    ac_prefix = 'SFE'
1665    notice = ''
1666    pnav = 4
1667    buttonname = 'Start course registration now'
1668
1669    def update(self, SUBMIT=None):
1670        if not self.context.getStudent().state in (CLEARED,RETURNING):
1671            self.flash("Wrong state.")
1672            self.redirect(self.url(self.context))
1673            return
1674        self.ac_series = self.request.form.get('ac_series', None)
1675        self.ac_number = self.request.form.get('ac_number', None)
1676
1677        if SUBMIT is None:
1678            return
1679        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1680        code = get_access_code(pin)
1681        if not code:
1682            self.flash('Activation code is invalid.')
1683            return
1684        # Mark pin as used (this also fires a pin related transition)
1685        # and fire transition start_clearance
1686        if code.state == USED:
1687            self.flash('Activation code has already been used.')
1688            return
1689        else:
1690            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1691            # Here we know that the ac is in state initialized so we do not
1692            # expect an exception, but the owner might be different
1693            if not invalidate_accesscode(
1694                pin,comment,self.context.getStudent().student_id):
1695                self.flash('You are not the owner of this access code.')
1696                return
1697        if self.context.getStudent().state == CLEARED:
1698            IWorkflowInfo(self.context.getStudent()).fireTransition(
1699                'pay_first_school_fee')
1700        elif self.context.getStudent().state == RETURNING:
1701            IWorkflowInfo(self.context.getStudent()).fireTransition(
1702                'pay_school_fee')
1703        self.flash('Course registration has been started.')
1704        self.redirect(self.url(self.context))
1705        return
1706
1707
1708class AddStudyLevelActionButton(AddActionButton):
1709    grok.order(1)
1710    grok.context(IStudentStudyCourse)
1711    grok.view(StudyCourseDisplayFormPage)
1712    grok.require('waeup.handleStudent')
1713    text = 'Add course list'
1714    target = 'add'
1715
1716    @property
1717    def target_url(self):
1718        student = self.view.context.getStudent()
1719        condition1 = student.state != 'school fee paid'
1720        condition2 = str(student['studycourse'].current_level) in \
1721            self.view.context.keys()
1722        if condition1 or condition2:
1723            return ''
1724        return self.view.url(self.view.context, self.target)
1725
1726class AddStudyLevelFormPage(WAeUPEditFormPage):
1727    """ Page for students to add current study levels
1728    """
1729    grok.context(IStudentStudyCourse)
1730    grok.name('add')
1731    grok.require('waeup.handleStudent')
1732    grok.template('studyleveladdpage')
1733    form_fields = grok.AutoFields(IStudentStudyCourse)
1734    title = 'Study Course'
1735    pnav = 4
1736
1737    @property
1738    def label(self):
1739        studylevelsource = StudyLevelSource().factory
1740        code = self.context.current_level
1741        title = studylevelsource.getTitle(self.context, code)
1742        return 'Add current level %s' % title
1743
1744    def emitLockMessage(self):
1745        self.flash('The requested form is locked (read-only).')
1746        self.redirect(self.url(self.context))
1747        return
1748
1749    def update(self):
1750        if self.context.getStudent().state != 'school fee paid':
1751            self.emitLockMessage()
1752            return
1753        super(AddStudyLevelFormPage, self).update()
1754        return
1755
1756    @grok.action('Create course list now')
1757    def addStudyLevel(self, **data):
1758        studylevel = StudentStudyLevel()
1759        studylevel.level = self.context.current_level
1760        studylevel.level_session = self.context.current_session
1761        try:
1762            self.context.addStudentStudyLevel(
1763                self.context.certificate,studylevel)
1764        except KeyError:
1765            self.flash('This level exists.')
1766        self.redirect(self.url(self.context))
1767        return
1768
1769class StudyLevelEditActionButton(ManageActionButton):
1770    grok.order(1)
1771    grok.context(IStudentStudyLevel)
1772    grok.view(StudyLevelDisplayFormPage)
1773    grok.require('waeup.handleStudent')
1774    text = 'Add and remove courses'
1775    target = 'edit'
1776
1777    @property
1778    def target_url(self):
1779        student = self.view.context.getStudent()
1780        condition1 = student.state != 'school fee paid'
1781        condition2 = student[
1782            'studycourse'].current_level != self.view.context.level
1783        if condition1 or condition2:
1784            return ''
1785        return self.view.url(self.view.context, self.target)
1786
1787class StudyLevelEditFormPage(WAeUPEditFormPage):
1788    """ Page to edit the student study level data by students
1789    """
1790    grok.context(IStudentStudyLevel)
1791    grok.name('edit')
1792    grok.require('waeup.handleStudent')
1793    grok.template('studyleveleditpage')
1794    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1795        'level_session', 'level_verdict')
1796    pnav = 4
1797
1798    def update(self):
1799        super(StudyLevelEditFormPage, self).update()
1800    #    tabs.need()
1801        datatable.need()
1802        return
1803
1804    @property
1805    def title(self):
1806        return 'Study Level %s' % self.context.level_title
1807
1808    @property
1809    def label(self):
1810        return 'Add and remove course tickets of study level %s' % self.context.level_title
1811
1812    @property
1813    def total_credits(self):
1814        total_credits = 0
1815        for key, val in self.context.items():
1816            total_credits += val.credits
1817        return total_credits
1818
1819    @grok.action('Add course ticket')
1820    def addCourseTicket(self, **data):
1821        self.redirect(self.url(self.context, 'ctadd'))
1822
1823    @grok.action('Remove selected tickets')
1824    def delCourseTicket(self, **data):
1825        form = self.request.form
1826        if form.has_key('val_id'):
1827            child_id = form['val_id']
1828        else:
1829            self.flash('No ticket selected.')
1830            self.redirect(self.url(self.context, '@@edit'))
1831            return
1832        if not isinstance(child_id, list):
1833            child_id = [child_id]
1834        deleted = []
1835        for id in child_id:
1836            # Students are not allowed to remove core tickets
1837            if not self.context[id].core_or_elective:
1838                try:
1839                    del self.context[id]
1840                    deleted.append(id)
1841                except:
1842                    self.flash('Could not delete %s: %s: %s' % (
1843                            id, sys.exc_info()[0], sys.exc_info()[1]))
1844        if len(deleted):
1845            self.flash('Successfully removed: %s' % ', '.join(deleted))
1846        self.redirect(self.url(self.context, u'@@edit'))
1847        return
1848
1849    @grok.action('Register course list')
1850    def register_courses(self, **data):
1851        state = IWorkflowState(self.context.getStudent()).getState()
1852        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1853        self.flash('Course list has been registered.')
1854        self.redirect(self.url(self.context))
1855        return
1856
1857class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1858    """Add a course ticket by student.
1859    """
1860    grok.name('ctadd')
1861    grok.require('waeup.handleStudent')
1862    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1863        'grade', 'score', 'core_or_elective', 'automatic')
1864
1865    @grok.action('Add course ticket')
1866    def addCourseTicket(self, **data):
1867        ticket = CourseTicket()
1868        course = data['course']
1869        ticket.automatic = False
1870        ticket.code = course.code
1871        ticket.title = course.title
1872        ticket.faculty = course.__parent__.__parent__.__parent__.title
1873        ticket.department = course.__parent__.__parent__.title
1874        ticket.credits = course.credits
1875        ticket.passmark = course.passmark
1876        ticket.semester = course.semester
1877        try:
1878            self.context.addCourseTicket(ticket)
1879        except KeyError:
1880            self.flash('The ticket exists.')
1881            return
1882        self.flash('Successfully added %s.' % ticket.code)
1883        self.redirect(self.url(self.context, u'@@edit'))
1884        return
Note: See TracBrowser for help on using the repository browser.