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

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

Fix typo.

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