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

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

Insert container content table on pdf slips. Use this automatic insertion for course ticket lists on ExportPDFCourseRegistrationSlipPages.

ToDo?: The table does not show up if there are too many lines and it thus doesn't fit on the page.

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