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

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

Allow more than one ticket of same type if former tickets are not paid. The tickets might have been processed by the payment gateway and received an unsuccessful callback.

  • Property svn:keywords set to Id
File size: 69.6 KB
Line 
1## $Id: browser.py 7248 2011-12-01 13:01:24Z 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.viewStudentsContainer')
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        fullname = self.request.principal.title
378        try:
379            email_from = self.request.principal.email
380        except AttributeError:
381            email_from = self.config.email_admin
382        username = self.request.principal.id
383        usertype = self.request.principal.user_type.title()
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,usertype,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_state == 'paid' and\
1083               ticket.p_category == p_category and \
1084               ticket.p_item == p_item and \
1085               ticket.p_session == p_session:
1086                  self.flash(
1087                      'This type of payment ticket exists and ticket has been paid.')
1088                  self.redirect(self.url(self.context))
1089                  return
1090        payment = createObject(u'waeup.StudentOnlinePayment')
1091        self.applyData(payment, **data)
1092        timestamp = "%d" % int(time()*1000)
1093        #order_id = "%s%s" % (student_id[1:],timestamp)
1094        payment.p_id = "p%s" % timestamp
1095        payment.p_item = p_item
1096        payment.p_session = p_session
1097        payment.amount_auth = pay_details['amount']
1098        payment.surcharge_1 = pay_details['surcharge_1']
1099        payment.surcharge_2 = pay_details['surcharge_2']
1100        payment.surcharge_3 = pay_details['surcharge_3']
1101        self.context[payment.p_id] = payment
1102        self.flash('Payment ticket created.')
1103        self.redirect(self.url(self.context))
1104        return
1105
1106class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
1107    """ Page to view an online payment ticket
1108    """
1109    grok.context(IStudentOnlinePayment)
1110    grok.name('index')
1111    grok.require('waeup.viewStudent')
1112    form_fields = grok.AutoFields(IStudentOnlinePayment)
1113    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1114    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1115    pnav = 4
1116
1117    @property
1118    def title(self):
1119        return 'Online Payment Ticket %s' % self.context.p_id
1120
1121    @property
1122    def label(self):
1123        return '%s: Online Payment Ticket %s' % (
1124            self.context.getStudent().fullname,self.context.p_id)
1125
1126class PaymentReceiptActionButton(ManageActionButton):
1127    grok.order(1)
1128    grok.context(IStudentOnlinePayment)
1129    grok.view(OnlinePaymentDisplayFormPage)
1130    grok.require('waeup.viewStudent')
1131    icon = 'actionicon_pdf.png'
1132    text = 'Download payment receipt'
1133    target = 'payment_receipt.pdf'
1134
1135    @property
1136    def target_url(self):
1137        if self.context.p_state != 'paid':
1138            return ''
1139        return self.view.url(self.view.context, self.target)
1140
1141class RequestCallbackActionButton(ManageActionButton):
1142    grok.order(2)
1143    grok.context(IStudentOnlinePayment)
1144    grok.view(OnlinePaymentDisplayFormPage)
1145    grok.require('waeup.payStudent')
1146    icon = 'actionicon_call.png'
1147    text = 'Request callback'
1148    target = 'callback'
1149
1150    @property
1151    def target_url(self):
1152        if self.context.p_state != 'unpaid':
1153            return ''
1154        return self.view.url(self.view.context, self.target)
1155
1156class OnlinePaymentCallbackPage(grok.View):
1157    """ Callback view
1158    """
1159    grok.context(IStudentOnlinePayment)
1160    grok.name('callback')
1161    grok.require('waeup.payStudent')
1162
1163    # This update method simulates a valid callback und must be
1164    # specified in the customization package. The parameters must be taken
1165    # from the incoming request.
1166    def update(self):
1167        if self.context.p_state == 'paid':
1168            self.flash('This ticket has already been paid.')
1169            return
1170        student = self.context.getStudent()
1171        write_log_message(self,'valid callback: %s' % self.context.p_id)
1172        self.context.r_amount_approved = self.context.amount_auth
1173        self.context.r_card_num = u'0000'
1174        self.context.r_code = u'00'
1175        self.context.p_state = 'paid'
1176        self.context.payment_date = datetime.now()
1177        if self.context.p_category == 'clearance':
1178            # Create CLR access code
1179            pin, error = create_accesscode('CLR',0,student.student_id)
1180            if error:
1181                self.flash('Valid callback received. ' + error)
1182                return
1183            self.context.ac = pin
1184        elif self.context.p_category == 'schoolfee':
1185            # Create SFE access code
1186            pin, error = create_accesscode('SFE',0,student.student_id)
1187            if error:
1188                self.flash('Valid callback received. ' + error)
1189                return
1190            self.context.ac = pin
1191        elif self.context.p_category == 'bed_allocation':
1192            # Create HOS access code
1193            pin, error = create_accesscode('HOS',0,student.student_id)
1194            if error:
1195                self.flash('Valid callback received. ' + error)
1196                return
1197            self.context.ac = pin
1198        self.flash('Valid callback received.')
1199        return
1200
1201    def render(self):
1202        self.redirect(self.url(self.context, '@@index'))
1203        return
1204
1205class ExportPDFPaymentSlipPage(grok.View):
1206    """Deliver a PDF slip of the context.
1207    """
1208    grok.context(IStudentOnlinePayment)
1209    grok.name('payment_receipt.pdf')
1210    grok.require('waeup.viewStudent')
1211    form_fields = grok.AutoFields(IStudentOnlinePayment)
1212    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1213    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1214    prefix = 'form'
1215
1216    @property
1217    def label(self):
1218        return 'Online Payment Receipt %s' % self.context.p_id
1219
1220    def render(self):
1221        if self.context.p_state != 'paid':
1222            self.flash('Ticket not yet paid.')
1223            self.redirect(self.url(self.context))
1224            return
1225        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1226            self.request)
1227        students_utils = getUtility(IStudentsUtils)
1228        return students_utils.renderPDF(self,'Payment', 'payment_receipt.pdf',
1229            self.context.getStudent, studentview)
1230
1231# We don't need the display form page yet
1232#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1233#    """ Page to display the student accommodation data
1234#    """
1235#    grok.context(IStudentAccommodation)
1236#    grok.name('xxx')
1237#    grok.require('waeup.viewStudent')
1238#    form_fields = grok.AutoFields(IStudentAccommodation)
1239#    #grok.template('accommodationpage')
1240#    title = 'Accommodation'
1241#    pnav = 4
1242
1243#    @property
1244#    def label(self):
1245#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1246
1247# This manage form page is for both students and students officers.
1248class AccommodationManageFormPage(WAeUPEditFormPage):
1249    """ Page to manage bed tickets.
1250    """
1251    grok.context(IStudentAccommodation)
1252    grok.name('index')
1253    grok.require('waeup.handleAccommodation')
1254    form_fields = grok.AutoFields(IStudentAccommodation)
1255    grok.template('accommodationmanagepage')
1256    title = 'Accommodation'
1257    pnav = 4
1258    officers_only_actions = ['Remove selected']
1259
1260    def formatDatetime(self,datetimeobj):
1261        if isinstance(datetimeobj, datetime):
1262            return datetimeobj.strftime("%Y-%m-%d %H:%M:%S")
1263        else:
1264            return None
1265
1266    @property
1267    def label(self):
1268        return '%s: Accommodation' % self.context.__parent__.fullname
1269
1270    def update(self):
1271        super(AccommodationManageFormPage, self).update()
1272        datatable.need()
1273        return
1274
1275    @grok.action('Remove selected')
1276    def delBedTickets(self, **data):
1277        if getattr(self.request.principal, 'user_type', None) == 'student':
1278            self.flash('You are not allowed to remove bed tickets.')
1279            self.redirect(self.url(self.context))
1280            return
1281        form = self.request.form
1282        if form.has_key('val_id'):
1283            child_id = form['val_id']
1284        else:
1285            self.flash('No bed ticket selected.')
1286            self.redirect(self.url(self.context))
1287            return
1288        if not isinstance(child_id, list):
1289            child_id = [child_id]
1290        deleted = []
1291        for id in child_id:
1292            del self.context[id]
1293            deleted.append(id)
1294        if len(deleted):
1295            self.flash('Successfully removed: %s' % ', '.join(deleted))
1296            write_log_message(self,'removed: % s' % ', '.join(deleted))
1297        self.redirect(self.url(self.context))
1298        return
1299
1300    @property
1301    def selected_actions(self):
1302        sa = self.actions
1303        if getattr(self.request.principal, 'user_type', None) == 'student':
1304            sa = [action for action in self.actions
1305                  if not action.label in self.officers_only_actions]
1306        return sa
1307
1308class AddBedTicketActionButton(ManageActionButton):
1309    grok.order(1)
1310    grok.context(IStudentAccommodation)
1311    grok.view(AccommodationManageFormPage)
1312    grok.require('waeup.handleAccommodation')
1313    icon = 'actionicon_home.png'
1314    text = 'Book accommodation'
1315    target = 'add'
1316
1317class BedTicketAddPage(WAeUPPage):
1318    """ Page to add an online payment ticket
1319    """
1320    grok.context(IStudentAccommodation)
1321    grok.name('add')
1322    grok.require('waeup.handleAccommodation')
1323    grok.template('enterpin')
1324    ac_prefix = 'HOS'
1325    label = 'Add bed ticket'
1326    title = 'Add bed ticket'
1327    pnav = 4
1328    buttonname = 'Create bed ticket'
1329    notice = ''
1330
1331    def update(self, SUBMIT=None):
1332        student = self.context.getStudent()
1333        students_utils = getUtility(IStudentsUtils)
1334        acc_details  = students_utils.getAccommodationDetails(student)
1335        if not student.state in acc_details['allowed_states']:
1336            self.flash("You are in the wrong registration state.")
1337            self.redirect(self.url(self.context))
1338            return
1339        if student['studycourse'].current_session != acc_details['booking_session']:
1340            self.flash(
1341                'Your current session does not match accommodation session.')
1342            self.redirect(self.url(self.context))
1343            return
1344        if str(acc_details['booking_session']) in self.context.keys():
1345            self.flash('You already booked a bed space in current accommodation session.')
1346            self.redirect(self.url(self.context))
1347            return
1348        self.ac_series = self.request.form.get('ac_series', None)
1349        self.ac_number = self.request.form.get('ac_number', None)
1350        if SUBMIT is None:
1351            return
1352        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1353        code = get_access_code(pin)
1354        if not code:
1355            self.flash('Activation code is invalid.')
1356            return
1357        # Search and book bed
1358        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1359        entries = cat.searchResults(
1360            owner=(student.student_id,student.student_id))
1361        if len(entries):
1362            # If bed space has bee manually allocated use this bed
1363            bed = [entry for entry in entries][0]
1364        else:
1365            # else search for other available beds
1366            entries = cat.searchResults(
1367                bed_type=(acc_details['bt'],acc_details['bt']))
1368            available_beds = [
1369                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1370            if available_beds:
1371                students_utils = getUtility(IStudentsUtils)
1372                bed = students_utils.selectBed(available_beds)
1373                bed.bookBed(student.student_id)
1374            else:
1375                self.flash('There is no free bed in your category %s.'
1376                            % acc_details['bt'])
1377                return
1378        # Mark pin as used (this also fires a pin related transition)
1379        if code.state == USED:
1380            self.flash('Activation code has already been used.')
1381            return
1382        else:
1383            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1384            # Here we know that the ac is in state initialized so we do not
1385            # expect an exception, but the owner might be different
1386            if not invalidate_accesscode(
1387                pin,comment,self.context.getStudent().student_id):
1388                self.flash('You are not the owner of this access code.')
1389                return
1390        # Create bed ticket
1391        bedticket = createObject(u'waeup.BedTicket')
1392        bedticket.booking_code = pin
1393        bedticket.booking_session = acc_details['booking_session']
1394        bedticket.bed_type = acc_details['bt']
1395        bedticket.bed = bed
1396        hall_title = bed.__parent__.hostel_name
1397        coordinates = bed.getBedCoordinates()[1:]
1398        block, room_nr, bed_nr = coordinates
1399        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1400            hall_title, block, room_nr, bed_nr, bed.bed_type)
1401        key = str(acc_details['booking_session'])
1402        self.context[key] = bedticket
1403        self.flash('Bed ticket created and bed booked: %s'
1404            % bedticket.bed_coordinates)
1405        self.redirect(self.url(self.context))
1406        return
1407
1408class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1409    """ Page to display bed tickets
1410    """
1411    grok.context(IBedTicket)
1412    grok.name('index')
1413    grok.require('waeup.handleAccommodation')
1414    form_fields = grok.AutoFields(IBedTicket)
1415    form_fields[
1416        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1417    pnav = 4
1418
1419    @property
1420    def label(self):
1421        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1422
1423    @property
1424    def title(self):
1425        return 'Bed Ticket %s' % self.context.getSessionString()
1426
1427class BedTicketSlipActionButton(ManageActionButton):
1428    grok.order(1)
1429    grok.context(IBedTicket)
1430    grok.view(BedTicketDisplayFormPage)
1431    grok.require('waeup.handleAccommodation')
1432    icon = 'actionicon_pdf.png'
1433    text = 'Download bed allocation slip'
1434    target = 'bed_allocation.pdf'
1435
1436class ExportPDFBedTicketSlipPage(grok.View):
1437    """Deliver a PDF slip of the context.
1438    """
1439    grok.context(IBedTicket)
1440    grok.name('bed_allocation.pdf')
1441    grok.require('waeup.handleAccommodation')
1442    form_fields = grok.AutoFields(IBedTicket)
1443    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1444    prefix = 'form'
1445
1446    @property
1447    def label(self):
1448        return 'Bed Allocation %s' % self.context.bed_coordinates
1449
1450    def render(self):
1451        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1452            self.request)
1453        students_utils = getUtility(IStudentsUtils)
1454        return students_utils.renderPDF(
1455            self,'Bed Allocation', 'bed_allocation.pdf',
1456            self.context.getStudent, studentview)
1457
1458class RelocateStudentActionButton(ManageActionButton):
1459    grok.order(2)
1460    grok.context(IBedTicket)
1461    grok.view(BedTicketDisplayFormPage)
1462    grok.require('waeup.manageHostels')
1463    icon = 'actionicon_reload.png'
1464    text = 'Relocate student'
1465    target = 'relocate'
1466
1467class BedTicketRelocationPage(grok.View):
1468    """ Callback view
1469    """
1470    grok.context(IBedTicket)
1471    grok.name('relocate')
1472    grok.require('waeup.manageHostels')
1473
1474    # Relocate student if student parameters have changed or the bed_type
1475    # of the bed has changed
1476    def update(self):
1477        student = self.context.getStudent()
1478        students_utils = getUtility(IStudentsUtils)
1479        acc_details  = students_utils.getAccommodationDetails(student)
1480        if self.context.bed != None and \
1481              'reserved' in self.context.bed.bed_type:
1482            self.flash("Students in reserved beds can't be relocated.")
1483            self.redirect(self.url(self.context))
1484            return
1485        if acc_details['bt'] == self.context.bed_type and \
1486                self.context.bed != None and \
1487                self.context.bed.bed_type == self.context.bed_type:
1488            self.flash("Student can't be relocated.")
1489            self.redirect(self.url(self.context))
1490            return
1491        # Search a bed
1492        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1493        entries = cat.searchResults(
1494            owner=(student.student_id,student.student_id))
1495        if len(entries) and self.context.bed == None:
1496            # If booking has been cancelled but other bed space has been
1497            # manually allocated after cancellation use this bed
1498            new_bed = [entry for entry in entries][0]
1499        else:
1500            # Search for other available beds
1501            entries = cat.searchResults(
1502                bed_type=(acc_details['bt'],acc_details['bt']))
1503            available_beds = [
1504                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1505            if available_beds:
1506                students_utils = getUtility(IStudentsUtils)
1507                new_bed = students_utils.selectBed(available_beds)
1508                new_bed.bookBed(student.student_id)
1509            else:
1510                self.flash('There is no free bed in your category %s.'
1511                            % acc_details['bt'])
1512                self.redirect(self.url(self.context))
1513                return
1514        # Rlease old bed if exists
1515        if self.context.bed != None:
1516            self.context.bed.owner = NOT_OCCUPIED
1517            notify(grok.ObjectModifiedEvent(self.context.bed))
1518        # Alocate new bed
1519        self.context.bed_type = acc_details['bt']
1520        self.context.bed = new_bed
1521        hall_title = new_bed.__parent__.hostel_name
1522        coordinates = new_bed.getBedCoordinates()[1:]
1523        block, room_nr, bed_nr = coordinates
1524        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1525            hall_title, block, room_nr, bed_nr, new_bed.bed_type)
1526        self.flash('Student relocated: %s' % self.context.bed_coordinates)
1527        self.redirect(self.url(self.context))
1528        return
1529
1530    def render(self):
1531        #self.redirect(self.url(self.context, '@@index'))
1532        return
1533
1534class StudentHistoryPage(WAeUPPage):
1535    """ Page to display student clearance data
1536    """
1537    grok.context(IStudent)
1538    grok.name('history')
1539    grok.require('waeup.viewStudent')
1540    grok.template('studenthistory')
1541    title = 'History'
1542    pnav = 4
1543
1544    @property
1545    def label(self):
1546        return '%s: History' % self.context.fullname
1547
1548# Pages for students only
1549
1550class StudentBaseActionButton(ManageActionButton):
1551    grok.order(1)
1552    grok.context(IStudent)
1553    grok.view(StudentBaseDisplayFormPage)
1554    grok.require('waeup.handleStudent')
1555    text = 'Edit base data'
1556    target = 'edit_base'
1557
1558class StudentPasswordActionButton(ManageActionButton):
1559    grok.order(2)
1560    grok.context(IStudent)
1561    grok.view(StudentBaseDisplayFormPage)
1562    grok.require('waeup.handleStudent')
1563    icon = 'actionicon_key.png'
1564    text = 'Change password'
1565    target = 'change_password'
1566
1567class StudentPassportActionButton(ManageActionButton):
1568    grok.order(3)
1569    grok.context(IStudent)
1570    grok.view(StudentBaseDisplayFormPage)
1571    grok.require('waeup.handleStudent')
1572    icon = 'actionicon_portrait.png'
1573    text = 'Change portrait'
1574    target = 'change_portrait'
1575
1576    @property
1577    def target_url(self):
1578        if self.context.state != 'admitted':
1579            return ''
1580        return self.view.url(self.view.context, self.target)
1581
1582class StudentBaseEditFormPage(WAeUPEditFormPage):
1583    """ View to edit student base data
1584    """
1585    grok.context(IStudent)
1586    grok.name('edit_base')
1587    grok.require('waeup.handleStudent')
1588    form_fields = grok.AutoFields(IStudentBase).select(
1589        'email', 'phone')
1590    label = 'Edit base data'
1591    title = 'Base Data'
1592    pnav = 4
1593
1594    @grok.action('Save')
1595    def save(self, **data):
1596        msave(self, **data)
1597        return
1598
1599class StudentChangePasswordPage(WAeUPEditFormPage):
1600    """ View to manage student base data
1601    """
1602    grok.context(IStudent)
1603    grok.name('change_password')
1604    grok.require('waeup.handleStudent')
1605    grok.template('change_password')
1606    label = 'Change password'
1607    title = 'Base Data'
1608    pnav = 4
1609
1610    @grok.action('Save')
1611    def save(self, **data):
1612        form = self.request.form
1613        password = form.get('change_password', None)
1614        password_ctl = form.get('change_password_repeat', None)
1615        if password:
1616            validator = getUtility(IPasswordValidator)
1617            errors = validator.validate_password(password, password_ctl)
1618            if not errors:
1619                IUserAccount(self.context).setPassword(password)
1620                write_log_message(self, 'saved: password')
1621                self.flash('Password changed.')
1622            else:
1623                self.flash( ' '.join(errors))
1624        return
1625
1626class StudentFilesUploadPage(WAeUPPage):
1627    """ View to upload files by student
1628    """
1629    grok.context(IStudent)
1630    grok.name('change_portrait')
1631    grok.require('waeup.uploadStudentFile')
1632    grok.template('filesuploadpage')
1633    label = 'Upload portrait'
1634    title = 'Base Data'
1635    pnav = 4
1636
1637    def update(self):
1638        if self.context.getStudent().state != 'admitted':
1639            emit_lock_message(self)
1640            return
1641        super(StudentFilesUploadPage, self).update()
1642        return
1643
1644class StudentClearanceStartActionButton(ManageActionButton):
1645    grok.order(1)
1646    grok.context(IStudent)
1647    grok.view(StudentClearanceDisplayFormPage)
1648    grok.require('waeup.handleStudent')
1649    icon = 'actionicon_start.gif'
1650    text = 'Start clearance'
1651    target = 'start_clearance'
1652
1653    @property
1654    def target_url(self):
1655        if self.context.state != 'admitted':
1656            return ''
1657        return self.view.url(self.view.context, self.target)
1658
1659class StartClearancePage(WAeUPPage):
1660    grok.context(IStudent)
1661    grok.name('start_clearance')
1662    grok.require('waeup.handleStudent')
1663    grok.template('enterpin')
1664    title = 'Start clearance'
1665    label = 'Start clearance'
1666    ac_prefix = 'CLR'
1667    notice = ''
1668    pnav = 4
1669    buttonname = 'Start clearance now'
1670
1671    @property
1672    def all_required_fields_filled(self):
1673        if self.context.email and self.context.phone:
1674            return True
1675        return False
1676
1677    @property
1678    def portrait_uploaded(self):
1679        store = getUtility(IExtFileStore)
1680        if store.getFileByContext(self.context, attr=u'passport.jpg'):
1681            return True
1682        return False
1683
1684    def update(self, SUBMIT=None):
1685        if not self.context.state == 'admitted':
1686            self.flash("Wrong state.")
1687            self.redirect(self.url(self.context))
1688            return
1689        if not self.portrait_uploaded:
1690            self.flash("No portrait uploaded.")
1691            self.redirect(self.url(self.context, 'change_portrait'))
1692            return
1693        if not self.all_required_fields_filled:
1694            self.flash("Not all required fields filled.")
1695            self.redirect(self.url(self.context, 'edit_base'))
1696            return
1697        self.ac_series = self.request.form.get('ac_series', None)
1698        self.ac_number = self.request.form.get('ac_number', None)
1699
1700        if SUBMIT is None:
1701            return
1702        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1703        code = get_access_code(pin)
1704        if not code:
1705            self.flash('Activation code is invalid.')
1706            return
1707        # Mark pin as used (this also fires a pin related transition)
1708        # and fire transition start_clearance
1709        if code.state == USED:
1710            self.flash('Activation code has already been used.')
1711            return
1712        else:
1713            comment = u"AC invalidated for %s" % self.context.student_id
1714            # Here we know that the ac is in state initialized so we do not
1715            # expect an exception, but the owner might be different
1716            if not invalidate_accesscode(pin,comment,self.context.student_id):
1717                self.flash('You are not the owner of this access code.')
1718                return
1719            self.context.clr_code = pin
1720        IWorkflowInfo(self.context).fireTransition('start_clearance')
1721        self.flash('Clearance process has been started.')
1722        self.redirect(self.url(self.context,'cedit'))
1723        return
1724
1725class StudentClearanceEditActionButton(ManageActionButton):
1726    grok.order(1)
1727    grok.context(IStudent)
1728    grok.view(StudentClearanceDisplayFormPage)
1729    grok.require('waeup.handleStudent')
1730    text = 'Edit'
1731    target = 'cedit'
1732
1733    @property
1734    def target_url(self):
1735        if self.context.clearance_locked:
1736            return ''
1737        return self.view.url(self.view.context, self.target)
1738
1739class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1740    """ View to edit student clearance data by student
1741    """
1742    grok.context(IStudent)
1743    grok.name('cedit')
1744    grok.require('waeup.handleStudent')
1745    form_fields = grok.AutoFields(
1746        IStudentClearanceEdit).omit('clearance_locked')
1747    label = 'Edit clearance data'
1748    title = 'Clearance Data'
1749    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1750
1751    def update(self):
1752        if self.context.clearance_locked:
1753            emit_lock_message(self)
1754            return
1755        return super(StudentClearanceEditFormPage, self).update()
1756
1757    @grok.action('Save')
1758    def save(self, **data):
1759        self.applyData(self.context, **data)
1760        self.flash('Clearance form has been saved.')
1761        return
1762
1763    @grok.action('Save and request clearance')
1764    def requestClearance(self, **data):
1765        self.applyData(self.context, **data)
1766        self.context._p_changed = True
1767        #if self.dataNotComplete():
1768        #    self.flash(self.dataNotComplete())
1769        #    return
1770        self.flash('Clearance form has been saved.')
1771        self.redirect(self.url(self.context,'request_clearance'))
1772        return
1773
1774class RequestClearancePage(WAeUPPage):
1775    grok.context(IStudent)
1776    grok.name('request_clearance')
1777    grok.require('waeup.handleStudent')
1778    grok.template('enterpin')
1779    title = 'Request clearance'
1780    label = 'Request clearance'
1781    notice = 'Enter the CLR access code used for starting clearance.'
1782    ac_prefix = 'CLR'
1783    pnav = 4
1784    buttonname = 'Request clearance now'
1785
1786    def update(self, SUBMIT=None):
1787        self.ac_series = self.request.form.get('ac_series', None)
1788        self.ac_number = self.request.form.get('ac_number', None)
1789        if SUBMIT is None:
1790            return
1791        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1792        if self.context.clr_code != pin:
1793            self.flash("This isn't your CLR access code.")
1794            return
1795        state = IWorkflowState(self.context).getState()
1796        # This shouldn't happen, but the application officer
1797        # might have forgotten to lock the form after changing the state
1798        if state != CLEARANCE:
1799            self.flash('This form cannot be submitted. Wrong state!')
1800            return
1801        IWorkflowInfo(self.context).fireTransition('request_clearance')
1802        self.flash('Clearance has been requested.')
1803        self.redirect(self.url(self.context))
1804        return
1805
1806class CourseRegistrationStartActionButton(ManageActionButton):
1807    grok.order(1)
1808    grok.context(IStudentStudyCourse)
1809    grok.view(StudyCourseDisplayFormPage)
1810    grok.require('waeup.handleStudent')
1811    icon = 'actionicon_start.gif'
1812    text = 'Start course registration'
1813    target = 'start_course_registration'
1814
1815    @property
1816    def target_url(self):
1817        if not self.context.getStudent().state in (CLEARED,RETURNING):
1818            return ''
1819        return self.view.url(self.view.context, self.target)
1820
1821class StartCourseRegistrationPage(WAeUPPage):
1822    grok.context(IStudentStudyCourse)
1823    grok.name('start_course_registration')
1824    grok.require('waeup.handleStudent')
1825    grok.template('enterpin')
1826    title = 'Start course registration'
1827    label = 'Start course registration'
1828    ac_prefix = 'SFE'
1829    notice = ''
1830    pnav = 4
1831    buttonname = 'Start course registration now'
1832
1833    def update(self, SUBMIT=None):
1834        if not self.context.getStudent().state in (CLEARED,RETURNING):
1835            self.flash("Wrong state.")
1836            self.redirect(self.url(self.context))
1837            return
1838        self.ac_series = self.request.form.get('ac_series', None)
1839        self.ac_number = self.request.form.get('ac_number', None)
1840
1841        if SUBMIT is None:
1842            return
1843        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1844        code = get_access_code(pin)
1845        if not code:
1846            self.flash('Activation code is invalid.')
1847            return
1848        # Mark pin as used (this also fires a pin related transition)
1849        # and fire transition start_clearance
1850        if code.state == USED:
1851            self.flash('Activation code has already been used.')
1852            return
1853        else:
1854            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1855            # Here we know that the ac is in state initialized so we do not
1856            # expect an exception, but the owner might be different
1857            if not invalidate_accesscode(
1858                pin,comment,self.context.getStudent().student_id):
1859                self.flash('You are not the owner of this access code.')
1860                return
1861        if self.context.getStudent().state == CLEARED:
1862            IWorkflowInfo(self.context.getStudent()).fireTransition(
1863                'pay_first_school_fee')
1864        elif self.context.getStudent().state == RETURNING:
1865            IWorkflowInfo(self.context.getStudent()).fireTransition(
1866                'pay_school_fee')
1867        self.flash('Course registration has been started.')
1868        self.redirect(self.url(self.context))
1869        return
1870
1871
1872class AddStudyLevelActionButton(AddActionButton):
1873    grok.order(1)
1874    grok.context(IStudentStudyCourse)
1875    grok.view(StudyCourseDisplayFormPage)
1876    grok.require('waeup.handleStudent')
1877    text = 'Add course list'
1878    target = 'add'
1879
1880    @property
1881    def target_url(self):
1882        student = self.view.context.getStudent()
1883        condition1 = student.state != 'school fee paid'
1884        condition2 = str(student['studycourse'].current_level) in \
1885            self.view.context.keys()
1886        if condition1 or condition2:
1887            return ''
1888        return self.view.url(self.view.context, self.target)
1889
1890class AddStudyLevelFormPage(WAeUPEditFormPage):
1891    """ Page for students to add current study levels
1892    """
1893    grok.context(IStudentStudyCourse)
1894    grok.name('add')
1895    grok.require('waeup.handleStudent')
1896    grok.template('studyleveladdpage')
1897    form_fields = grok.AutoFields(IStudentStudyCourse)
1898    title = 'Study Course'
1899    pnav = 4
1900
1901    @property
1902    def label(self):
1903        studylevelsource = StudyLevelSource().factory
1904        code = self.context.current_level
1905        title = studylevelsource.getTitle(self.context, code)
1906        return 'Add current level %s' % title
1907
1908    def update(self):
1909        if self.context.getStudent().state != 'school fee paid':
1910            emit_lock_message(self)
1911            return
1912        super(AddStudyLevelFormPage, self).update()
1913        return
1914
1915    @grok.action('Create course list now')
1916    def addStudyLevel(self, **data):
1917        studylevel = StudentStudyLevel()
1918        studylevel.level = self.context.current_level
1919        studylevel.level_session = self.context.current_session
1920        try:
1921            self.context.addStudentStudyLevel(
1922                self.context.certificate,studylevel)
1923        except KeyError:
1924            self.flash('This level exists.')
1925        self.redirect(self.url(self.context))
1926        return
1927
1928class StudyLevelEditActionButton(ManageActionButton):
1929    grok.order(1)
1930    grok.context(IStudentStudyLevel)
1931    grok.view(StudyLevelDisplayFormPage)
1932    grok.require('waeup.handleStudent')
1933    text = 'Add and remove courses'
1934    target = 'edit'
1935
1936    @property
1937    def target_url(self):
1938        student = self.view.context.getStudent()
1939        condition1 = student.state != 'school fee paid'
1940        condition2 = student[
1941            'studycourse'].current_level != self.view.context.level
1942        if condition1 or condition2:
1943            return ''
1944        return self.view.url(self.view.context, self.target)
1945
1946class StudyLevelEditFormPage(WAeUPEditFormPage):
1947    """ Page to edit the student study level data by students
1948    """
1949    grok.context(IStudentStudyLevel)
1950    grok.name('edit')
1951    grok.require('waeup.handleStudent')
1952    grok.template('studyleveleditpage')
1953    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1954        'level_session', 'level_verdict')
1955    pnav = 4
1956
1957    def update(self):
1958        super(StudyLevelEditFormPage, self).update()
1959    #    tabs.need()
1960        datatable.need()
1961        return
1962
1963    @property
1964    def title(self):
1965        return 'Study Level %s' % self.context.level_title
1966
1967    @property
1968    def label(self):
1969        return 'Add and remove course tickets of study level %s' % self.context.level_title
1970
1971    @property
1972    def total_credits(self):
1973        total_credits = 0
1974        for key, val in self.context.items():
1975            total_credits += val.credits
1976        return total_credits
1977
1978    @grok.action('Add course ticket')
1979    def addCourseTicket(self, **data):
1980        self.redirect(self.url(self.context, 'ctadd'))
1981
1982    @grok.action('Remove selected tickets')
1983    def delCourseTicket(self, **data):
1984        form = self.request.form
1985        if form.has_key('val_id'):
1986            child_id = form['val_id']
1987        else:
1988            self.flash('No ticket selected.')
1989            self.redirect(self.url(self.context, '@@edit'))
1990            return
1991        if not isinstance(child_id, list):
1992            child_id = [child_id]
1993        deleted = []
1994        for id in child_id:
1995            # Students are not allowed to remove core tickets
1996            if not self.context[id].core_or_elective:
1997                try:
1998                    del self.context[id]
1999                    deleted.append(id)
2000                except:
2001                    self.flash('Could not delete %s: %s: %s' % (
2002                            id, sys.exc_info()[0], sys.exc_info()[1]))
2003        if len(deleted):
2004            self.flash('Successfully removed: %s' % ', '.join(deleted))
2005        self.redirect(self.url(self.context, u'@@edit'))
2006        return
2007
2008    @grok.action('Register course list')
2009    def RegisterCourses(self, **data):
2010        state = IWorkflowState(self.context.getStudent()).getState()
2011        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
2012        self.flash('Course list has been registered.')
2013        self.redirect(self.url(self.context))
2014        return
2015
2016class CourseTicketAddFormPage2(CourseTicketAddFormPage):
2017    """Add a course ticket by student.
2018    """
2019    grok.name('ctadd')
2020    grok.require('waeup.handleStudent')
2021    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
2022        'grade', 'score', 'core_or_elective', 'automatic')
2023
2024    @grok.action('Add course ticket')
2025    def addCourseTicket(self, **data):
2026        ticket = CourseTicket()
2027        course = data['course']
2028        ticket.automatic = False
2029        ticket.code = course.code
2030        ticket.title = course.title
2031        ticket.faculty = course.__parent__.__parent__.__parent__.title
2032        ticket.department = course.__parent__.__parent__.title
2033        ticket.credits = course.credits
2034        ticket.passmark = course.passmark
2035        ticket.semester = course.semester
2036        try:
2037            self.context.addCourseTicket(ticket)
2038        except KeyError:
2039            self.flash('The ticket exists.')
2040            return
2041        self.flash('Successfully added %s.' % ticket.code)
2042        self.redirect(self.url(self.context, u'@@edit'))
2043        return
Note: See TracBrowser for help on using the repository browser.