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

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

Include permission waeup.uploadStudentFile to handle file uploads.

Fix pagetemplate filesuploadpage.pt.

Rearrange fileupload.pt

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