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

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

Implement PasswordValidator? global utility as suggested by Uli.

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