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

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

Rename permissions and add clearStudent permission.

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