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

Last change on this file since 7146 was 7145, checked in by Henrik Bettermann, 14 years ago

Change some function names according to the style guide:

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

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