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

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

Redirect to contact form after rejecting clearance and pre-fill subject line.

  • Property svn:keywords set to Id
File size: 69.4 KB
Line 
1## $Id: browser.py 7275 2011-12-05 07:20:41Z 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(2)
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 StudentClearanceManageFormPage(WAeUPEditFormPage):
523    """ Page to edit student clearance data
524    """
525    grok.context(IStudent)
526    grok.name('edit_clearance')
527    grok.require('waeup.manageStudent')
528    grok.template('clearanceeditpage')
529    form_fields = grok.AutoFields(IStudentClearance)
530    label = 'Manage clearance data'
531    title = 'Clearance Data'
532    pnav = 4
533    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
534
535    def update(self):
536        datepicker.need() # Enable jQuery datepicker in date fields.
537        tabs.need()
538        return super(StudentClearanceManageFormPage, self).update()
539
540    @grok.action('Save')
541    def save(self, **data):
542        msave(self, **data)
543        return
544
545class StudentClearPage(grok.View):
546    """ Clear student by clearance officer
547    """
548    grok.context(IStudent)
549    grok.name('clear')
550    grok.require('waeup.clearStudent')
551
552    def update(self):
553        if self.context.state == REQUESTED:
554            IWorkflowInfo(self.context).fireTransition('clear')
555            self.flash('Student has been cleared.')
556        else:
557            self.flash('Student is in the wrong state.')
558        self.redirect(self.url(self.context,'view_clearance'))
559        return
560
561    def render(self):
562        self.redirect(self.url(self.context, 'view_clearance'))
563        return
564
565class StudentRejectClearancePage(grok.View):
566    """ Reject clearance by clearance officers
567    """
568    grok.context(IStudent)
569    grok.name('reject_clearance')
570    grok.require('waeup.clearStudent')
571
572    def update(self):
573        if self.context.state == CLEARED:
574            IWorkflowInfo(self.context).fireTransition('reset4')
575            message = 'Clearance has been annulled'
576            self.flash(message)
577        elif self.context.state == REQUESTED:
578            IWorkflowInfo(self.context).fireTransition('reset3')
579            message = 'Clearance request has been rejected'
580            self.flash(message)
581        else:
582            self.flash('Student is in the wrong state.')
583            return
584        args = {'subject':message}
585        self.redirect(self.url(self.context) +
586            '/contactstudent?%s' % urlencode(args))
587        return
588
589    def render(self):
590        return
591
592class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
593    """ Page to display student personal data
594    """
595    grok.context(IStudent)
596    grok.name('view_personal')
597    grok.require('waeup.viewStudent')
598    form_fields = grok.AutoFields(IStudentPersonal)
599    title = 'Personal Data'
600    pnav = 4
601
602    @property
603    def label(self):
604        return '%s: Personal Data' % self.context.fullname
605
606class StudentPersonalManageActionButton(ManageActionButton):
607    grok.order(1)
608    grok.context(IStudent)
609    grok.view(StudentPersonalDisplayFormPage)
610    grok.require('waeup.manageStudent')
611    text = 'Manage'
612    target = 'edit_personal'
613
614class StudentPersonalManageFormPage(WAeUPEditFormPage):
615    """ Page to edit student clearance data
616    """
617    grok.context(IStudent)
618    grok.name('edit_personal')
619    grok.require('waeup.viewStudent')
620    form_fields = grok.AutoFields(IStudentPersonal)
621    label = 'Manage personal data'
622    title = 'Personal Data'
623    pnav = 4
624
625    @grok.action('Save')
626    def save(self, **data):
627        msave(self, **data)
628        return
629
630class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
631    """ Page to display the student study course data
632    """
633    grok.context(IStudentStudyCourse)
634    grok.name('index')
635    grok.require('waeup.viewStudent')
636    form_fields = grok.AutoFields(IStudentStudyCourse)
637    grok.template('studycoursepage')
638    title = 'Study Course'
639    pnav = 4
640
641    @property
642    def label(self):
643        return '%s: Study Course' % self.context.__parent__.fullname
644
645    @property
646    def current_mode(self):
647        if self.context.certificate:
648            current_mode = study_modes.getTermByToken(
649                self.context.certificate.study_mode).title
650            return current_mode
651        return
652       
653    @property
654    def department(self):
655        if self.context.certificate is not None:
656            return self.context.certificate.__parent__.__parent__
657        return
658
659    @property
660    def faculty(self):
661        if self.context.certificate is not None:
662            return self.context.certificate.__parent__.__parent__.__parent__
663        return
664
665class StudyCourseManageActionButton(ManageActionButton):
666    grok.order(1)
667    grok.context(IStudentStudyCourse)
668    grok.view(StudyCourseDisplayFormPage)
669    grok.require('waeup.manageStudent')
670    text = 'Manage'
671    target = 'manage'
672
673class StudyCourseManageFormPage(WAeUPEditFormPage):
674    """ Page to edit the student study course data
675    """
676    grok.context(IStudentStudyCourse)
677    grok.name('manage')
678    grok.require('waeup.manageStudent')
679    grok.template('studycoursemanagepage')
680    form_fields = grok.AutoFields(IStudentStudyCourse)
681    title = 'Study Course'
682    label = 'Manage study course'
683    pnav = 4
684    taboneactions = ['Save','Cancel']
685    tabtwoactions = ['Remove selected levels','Cancel']
686    tabthreeactions = ['Add study level']
687
688    def update(self):
689        super(StudyCourseManageFormPage, self).update()
690        tabs.need()
691        datatable.need()
692        return
693
694    @grok.action('Save')
695    def save(self, **data):
696        msave(self, **data)
697        return
698
699    @property
700    def level_dict(self):
701        studylevelsource = StudyLevelSource().factory
702        for code in studylevelsource.getValues(self.context):
703            title = studylevelsource.getTitle(self.context, code)
704            yield(dict(code=code, title=title))
705
706    @grok.action('Add study level')
707    def addStudyLevel(self, **data):
708        level_code = self.request.form.get('addlevel', None)
709        studylevel = StudentStudyLevel()
710        studylevel.level = int(level_code)
711        try:
712            self.context.addStudentStudyLevel(
713                self.context.certificate,studylevel)
714        except KeyError:
715            self.flash('This level exists.')
716        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
717        return
718
719    @grok.action('Remove selected levels')
720    def delStudyLevels(self, **data):
721        form = self.request.form
722        if form.has_key('val_id'):
723            child_id = form['val_id']
724        else:
725            self.flash('No study level selected.')
726            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
727            return
728        if not isinstance(child_id, list):
729            child_id = [child_id]
730        deleted = []
731        for id in child_id:
732            try:
733                del self.context[id]
734                deleted.append(id)
735            except:
736                self.flash('Could not delete %s: %s: %s' % (
737                        id, sys.exc_info()[0], sys.exc_info()[1]))
738        if len(deleted):
739            self.flash('Successfully removed: %s' % ', '.join(deleted))
740        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
741        return
742
743class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
744    """ Page to display student study levels
745    """
746    grok.context(IStudentStudyLevel)
747    grok.name('index')
748    grok.require('waeup.viewStudent')
749    form_fields = grok.AutoFields(IStudentStudyLevel)
750    grok.template('studylevelpage')
751    pnav = 4
752
753    @property
754    def title(self):
755        return 'Study Level %s' % self.context.level_title
756
757    @property
758    def label(self):
759        return '%s: Study Level %s' % (
760            self.context.getStudent().fullname,self.context.level_title)
761
762    @property
763    def total_credits(self):
764        total_credits = 0
765        for key, val in self.context.items():
766            total_credits += val.credits
767        return total_credits
768
769class CourseRegistrationSlipActionButton(ManageActionButton):
770    grok.order(1)
771    grok.context(IStudentStudyLevel)
772    grok.view(StudyLevelDisplayFormPage)
773    grok.require('waeup.viewStudent')
774    icon = 'actionicon_pdf.png'
775    text = 'Download course registration slip'
776    target = 'course_registration.pdf'
777
778class ExportPDFCourseRegistrationSlipPage(grok.View):
779    """Deliver a PDF slip of the context.
780    """
781    grok.context(IStudentStudyLevel)
782    grok.name('course_registration.pdf')
783    grok.require('waeup.viewStudent')
784    form_fields = grok.AutoFields(IStudentStudyLevel)
785    prefix = 'form'
786
787    @property
788    def label(self):
789        return 'Course Registration Slip %s' % self.context.level_title
790
791    def render(self):
792        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
793            self.request)
794        students_utils = getUtility(IStudentsUtils)
795        return students_utils.renderPDF(
796            self,'Course Registration', 'course_registration.pdf',
797            self.context.getStudent, studentview)
798
799class StudyLevelManageActionButton(ManageActionButton):
800    grok.order(2)
801    grok.context(IStudentStudyLevel)
802    grok.view(StudyLevelDisplayFormPage)
803    grok.require('waeup.manageStudent')
804    text = 'Manage'
805    target = 'manage'
806
807class StudyLevelManageFormPage(WAeUPEditFormPage):
808    """ Page to edit the student study level data
809    """
810    grok.context(IStudentStudyLevel)
811    grok.name('manage')
812    grok.require('waeup.manageStudent')
813    grok.template('studylevelmanagepage')
814    form_fields = grok.AutoFields(IStudentStudyLevel)
815    pnav = 4
816    taboneactions = ['Save','Cancel']
817    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
818
819    def update(self):
820        super(StudyLevelManageFormPage, self).update()
821        tabs.need()
822        datatable.need()
823        return
824
825    @property
826    def title(self):
827        return 'Study Level %s' % self.context.level_title
828
829    @property
830    def label(self):
831        return 'Manage study level %s' % self.context.level_title
832
833    @grok.action('Save')
834    def save(self, **data):
835        msave(self, **data)
836        return
837
838    @grok.action('Add course ticket')
839    def addCourseTicket(self, **data):
840        self.redirect(self.url(self.context, '@@add'))
841
842    @grok.action('Remove selected tickets')
843    def delCourseTicket(self, **data):
844        form = self.request.form
845        if form.has_key('val_id'):
846            child_id = form['val_id']
847        else:
848            self.flash('No ticket selected.')
849            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
850            return
851        if not isinstance(child_id, list):
852            child_id = [child_id]
853        deleted = []
854        for id in child_id:
855            try:
856                del self.context[id]
857                deleted.append(id)
858            except:
859                self.flash('Could not delete %s: %s: %s' % (
860                        id, sys.exc_info()[0], sys.exc_info()[1]))
861        if len(deleted):
862            self.flash('Successfully removed: %s' % ', '.join(deleted))
863        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
864        return
865
866class CourseTicketAddFormPage(WAeUPAddFormPage):
867    """Add a course ticket.
868    """
869    grok.context(IStudentStudyLevel)
870    grok.name('add')
871    grok.require('waeup.manageStudent')
872    label = 'Add course ticket'
873    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
874        'grade', 'score', 'automatic')
875    pnav = 4
876
877    @property
878    def title(self):
879        return 'Study Level %s' % self.context.level_title
880
881    @grok.action('Add course ticket')
882    def addCourseTicket(self, **data):
883        ticket = CourseTicket()
884        course = data['course']
885        ticket.core_or_elective = data['core_or_elective']
886        ticket.automatic = False
887        ticket.code = course.code
888        ticket.title = course.title
889        ticket.faculty = course.__parent__.__parent__.__parent__.title
890        ticket.department = course.__parent__.__parent__.title
891        ticket.credits = course.credits
892        ticket.passmark = course.passmark
893        ticket.semester = course.semester
894        try:
895            self.context.addCourseTicket(ticket)
896        except KeyError:
897            self.flash('The ticket exists.')
898            return
899        self.flash('Successfully added %s.' % ticket.code)
900        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
901        return
902
903    @grok.action('Cancel')
904    def cancel(self, **data):
905        self.redirect(self.url(self.context))
906
907class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
908    """ Page to display course tickets
909    """
910    grok.context(ICourseTicket)
911    grok.name('index')
912    grok.require('waeup.viewStudent')
913    form_fields = grok.AutoFields(ICourseTicket)
914    grok.template('courseticketpage')
915    pnav = 4
916
917    @property
918    def title(self):
919        return 'Course Ticket %s' % self.context.code
920
921    @property
922    def label(self):
923        return '%s: Course Ticket %s' % (
924            self.context.getStudent().fullname,self.context.code)
925
926class CourseTicketManageActionButton(ManageActionButton):
927    grok.order(1)
928    grok.context(ICourseTicket)
929    grok.view(CourseTicketDisplayFormPage)
930    grok.require('waeup.manageStudent')
931    text = 'Manage'
932    target = 'manage'
933
934class CourseTicketManageFormPage(WAeUPEditFormPage):
935    """ Page to manage course tickets
936    """
937    grok.context(ICourseTicket)
938    grok.name('manage')
939    grok.require('waeup.manageStudent')
940    form_fields = grok.AutoFields(ICourseTicket)
941    grok.template('courseticketmanagepage')
942    pnav = 4
943
944    @property
945    def title(self):
946        return 'Course Ticket %s' % self.context.code
947
948    @property
949    def label(self):
950        return 'Manage course ticket %s' % self.context.code
951
952    @grok.action('Save')
953    def save(self, **data):
954        msave(self, **data)
955        return
956
957# We don't need the display form page yet
958#class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
959#    """ Page to display the student payments
960#    """
961#    grok.context(IStudentPaymentsContainer)
962#    grok.name('view')
963#    grok.require('waeup.viewStudent')
964#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
965#    grok.template('paymentspage')
966#    title = 'Payments'
967#    pnav = 4
968
969#    @property
970#    def label(self):
971#        return '%s: Payments' % self.context.__parent__.fullname
972
973#    def update(self):
974#        super(PaymentsDisplayFormPage, self).update()
975#        datatable.need()
976#        return
977
978# This manage form page is for both students and students officers.
979class PaymentsManageFormPage(WAeUPEditFormPage):
980    """ Page to manage the student payments
981    """
982    grok.context(IStudentPaymentsContainer)
983    grok.name('index')
984    grok.require('waeup.payStudent')
985    form_fields = grok.AutoFields(IStudentPaymentsContainer)
986    grok.template('paymentsmanagepage')
987    title = 'Payments'
988    pnav = 4
989
990    def unremovable(self, ticket):
991        usertype = getattr(self.request.principal, 'user_type', None)
992        if not usertype:
993            return False
994        return (self.request.principal.user_type == 'student' and ticket.r_code)
995
996    @property
997    def label(self):
998        return '%s: Payments' % self.context.__parent__.fullname
999
1000    def update(self):
1001        super(PaymentsManageFormPage, self).update()
1002        datatable.need()
1003        return
1004
1005    @grok.action('Remove selected tickets')
1006    def delPaymentTicket(self, **data):
1007        form = self.request.form
1008        if form.has_key('val_id'):
1009            child_id = form['val_id']
1010        else:
1011            self.flash('No payment selected.')
1012            self.redirect(self.url(self.context))
1013            return
1014        if not isinstance(child_id, list):
1015            child_id = [child_id]
1016        deleted = []
1017        for id in child_id:
1018            # Students are not allowed to remove used payment tickets
1019            if not self.unremovable(self.context[id]):
1020                try:
1021                    del self.context[id]
1022                    deleted.append(id)
1023                except:
1024                    self.flash('Could not delete %s: %s: %s' % (
1025                            id, sys.exc_info()[0], sys.exc_info()[1]))
1026        if len(deleted):
1027            self.flash('Successfully removed: %s' % ', '.join(deleted))
1028            write_log_message(self,'removed: % s' % ', '.join(deleted))
1029        self.redirect(self.url(self.context))
1030        return
1031
1032    @grok.action('Add online payment ticket')
1033    def addPaymentTicket(self, **data):
1034        self.redirect(self.url(self.context, '@@addop'))
1035
1036#class OnlinePaymentManageActionButton(ManageActionButton):
1037#    grok.order(1)
1038#    grok.context(IStudentPaymentsContainer)
1039#    grok.view(PaymentsDisplayFormPage)
1040#    grok.require('waeup.manageStudent')
1041#    text = 'Manage payments'
1042#    target = 'manage'
1043
1044class OnlinePaymentAddFormPage(WAeUPAddFormPage):
1045    """ Page to add an online payment ticket
1046    """
1047    grok.context(IStudentPaymentsContainer)
1048    grok.name('addop')
1049    grok.require('waeup.payStudent')
1050    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
1051        'p_category')
1052    #zzgrok.template('addpaymentpage')
1053    label = 'Add online payment'
1054    title = 'Payments'
1055    pnav = 4
1056   
1057    @grok.action('Create ticket')
1058    def createTicket(self, **data):
1059        p_category = data['p_category']
1060        student = self.context.__parent__
1061        if p_category == 'bed_allocation' and student[
1062            'studycourse'].current_session != grok.getSite()[
1063            'configuration'].accommodation_session:
1064                self.flash(
1065                    'Your current session does not match accommodation session.')
1066                self.redirect(self.url(self.context))
1067                return
1068        students_utils = getUtility(IStudentsUtils)
1069        pay_details  = students_utils.getPaymentDetails(
1070            p_category,student)
1071        if pay_details['error']:
1072            self.flash(pay_details['error'])
1073            self.redirect(self.url(self.context))
1074            return
1075        p_item = pay_details['p_item']
1076        p_session = pay_details['p_session']
1077        for key in self.context.keys():
1078            ticket = self.context[key]
1079            if ticket.p_state == 'paid' and\
1080               ticket.p_category == p_category and \
1081               ticket.p_item == p_item and \
1082               ticket.p_session == p_session:
1083                  self.flash(
1084                      'This type of payment has already been made.')
1085                  self.redirect(self.url(self.context))
1086                  return
1087        payment = createObject(u'waeup.StudentOnlinePayment')
1088        self.applyData(payment, **data)
1089        timestamp = "%d" % int(time()*1000)
1090        #order_id = "%s%s" % (student_id[1:],timestamp)
1091        payment.p_id = "p%s" % timestamp
1092        payment.p_item = p_item
1093        payment.p_session = p_session
1094        payment.amount_auth = pay_details['amount']
1095        payment.surcharge_1 = pay_details['surcharge_1']
1096        payment.surcharge_2 = pay_details['surcharge_2']
1097        payment.surcharge_3 = pay_details['surcharge_3']
1098        self.context[payment.p_id] = payment
1099        self.flash('Payment ticket created.')
1100        self.redirect(self.url(self.context))
1101        return
1102
1103class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage):
1104    """ Page to view an online payment ticket
1105    """
1106    grok.context(IStudentOnlinePayment)
1107    grok.name('index')
1108    grok.require('waeup.viewStudent')
1109    form_fields = grok.AutoFields(IStudentOnlinePayment)
1110    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1111    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1112    pnav = 4
1113
1114    @property
1115    def title(self):
1116        return 'Online Payment Ticket %s' % self.context.p_id
1117
1118    @property
1119    def label(self):
1120        return '%s: Online Payment Ticket %s' % (
1121            self.context.getStudent().fullname,self.context.p_id)
1122
1123class PaymentReceiptActionButton(ManageActionButton):
1124    grok.order(1)
1125    grok.context(IStudentOnlinePayment)
1126    grok.view(OnlinePaymentDisplayFormPage)
1127    grok.require('waeup.viewStudent')
1128    icon = 'actionicon_pdf.png'
1129    text = 'Download payment receipt'
1130    target = 'payment_receipt.pdf'
1131
1132    @property
1133    def target_url(self):
1134        if self.context.p_state != 'paid':
1135            return ''
1136        return self.view.url(self.view.context, self.target)
1137
1138class RequestCallbackActionButton(ManageActionButton):
1139    grok.order(2)
1140    grok.context(IStudentOnlinePayment)
1141    grok.view(OnlinePaymentDisplayFormPage)
1142    grok.require('waeup.payStudent')
1143    icon = 'actionicon_call.png'
1144    text = 'Request callback'
1145    target = 'callback'
1146
1147    @property
1148    def target_url(self):
1149        if self.context.p_state != 'unpaid':
1150            return ''
1151        return self.view.url(self.view.context, self.target)
1152
1153class OnlinePaymentCallbackPage(grok.View):
1154    """ Callback view
1155    """
1156    grok.context(IStudentOnlinePayment)
1157    grok.name('callback')
1158    grok.require('waeup.payStudent')
1159
1160    # This update method simulates a valid callback und must be
1161    # specified in the customization package. The parameters must be taken
1162    # from the incoming request.
1163    def update(self):
1164        if self.context.p_state == 'paid':
1165            self.flash('This ticket has already been paid.')
1166            return
1167        student = self.context.getStudent()
1168        write_log_message(self,'valid callback: %s' % self.context.p_id)
1169        self.context.r_amount_approved = self.context.amount_auth
1170        self.context.r_card_num = u'0000'
1171        self.context.r_code = u'00'
1172        self.context.p_state = 'paid'
1173        self.context.payment_date = datetime.now()
1174        if self.context.p_category == 'clearance':
1175            # Create CLR access code
1176            pin, error = create_accesscode('CLR',0,student.student_id)
1177            if error:
1178                self.flash('Valid callback received. ' + error)
1179                return
1180            self.context.ac = pin
1181        elif self.context.p_category == 'schoolfee':
1182            # Create SFE access code
1183            pin, error = create_accesscode('SFE',0,student.student_id)
1184            if error:
1185                self.flash('Valid callback received. ' + error)
1186                return
1187            self.context.ac = pin
1188        elif self.context.p_category == 'bed_allocation':
1189            # Create HOS access code
1190            pin, error = create_accesscode('HOS',0,student.student_id)
1191            if error:
1192                self.flash('Valid callback received. ' + error)
1193                return
1194            self.context.ac = pin
1195        self.flash('Valid callback received.')
1196        return
1197
1198    def render(self):
1199        self.redirect(self.url(self.context, '@@index'))
1200        return
1201
1202class ExportPDFPaymentSlipPage(grok.View):
1203    """Deliver a PDF slip of the context.
1204    """
1205    grok.context(IStudentOnlinePayment)
1206    grok.name('payment_receipt.pdf')
1207    grok.require('waeup.viewStudent')
1208    form_fields = grok.AutoFields(IStudentOnlinePayment)
1209    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1210    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1211    prefix = 'form'
1212
1213    @property
1214    def label(self):
1215        return 'Online Payment Receipt %s' % self.context.p_id
1216
1217    def render(self):
1218        if self.context.p_state != 'paid':
1219            self.flash('Ticket not yet paid.')
1220            self.redirect(self.url(self.context))
1221            return
1222        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1223            self.request)
1224        students_utils = getUtility(IStudentsUtils)
1225        return students_utils.renderPDF(self,'Payment', 'payment_receipt.pdf',
1226            self.context.getStudent, studentview)
1227
1228# We don't need the display form page yet
1229#class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
1230#    """ Page to display the student accommodation data
1231#    """
1232#    grok.context(IStudentAccommodation)
1233#    grok.name('xxx')
1234#    grok.require('waeup.viewStudent')
1235#    form_fields = grok.AutoFields(IStudentAccommodation)
1236#    #grok.template('accommodationpage')
1237#    title = 'Accommodation'
1238#    pnav = 4
1239
1240#    @property
1241#    def label(self):
1242#        return '%s: Accommodation Data' % self.context.__parent__.fullname
1243
1244# This manage form page is for both students and students officers.
1245class AccommodationManageFormPage(WAeUPEditFormPage):
1246    """ Page to manage bed tickets.
1247    """
1248    grok.context(IStudentAccommodation)
1249    grok.name('index')
1250    grok.require('waeup.handleAccommodation')
1251    form_fields = grok.AutoFields(IStudentAccommodation)
1252    grok.template('accommodationmanagepage')
1253    title = 'Accommodation'
1254    pnav = 4
1255    officers_only_actions = ['Remove selected']
1256
1257    @property
1258    def label(self):
1259        return '%s: Accommodation' % self.context.__parent__.fullname
1260
1261    def update(self):
1262        super(AccommodationManageFormPage, self).update()
1263        datatable.need()
1264        return
1265
1266    @grok.action('Remove selected')
1267    def delBedTickets(self, **data):
1268        if getattr(self.request.principal, 'user_type', None) == 'student':
1269            self.flash('You are not allowed to remove bed tickets.')
1270            self.redirect(self.url(self.context))
1271            return
1272        form = self.request.form
1273        if form.has_key('val_id'):
1274            child_id = form['val_id']
1275        else:
1276            self.flash('No bed ticket selected.')
1277            self.redirect(self.url(self.context))
1278            return
1279        if not isinstance(child_id, list):
1280            child_id = [child_id]
1281        deleted = []
1282        for id in child_id:
1283            del self.context[id]
1284            deleted.append(id)
1285        if len(deleted):
1286            self.flash('Successfully removed: %s' % ', '.join(deleted))
1287            write_log_message(self,'removed: % s' % ', '.join(deleted))
1288        self.redirect(self.url(self.context))
1289        return
1290
1291    @property
1292    def selected_actions(self):
1293        sa = self.actions
1294        if getattr(self.request.principal, 'user_type', None) == 'student':
1295            sa = [action for action in self.actions
1296                  if not action.label in self.officers_only_actions]
1297        return sa
1298
1299class AddBedTicketActionButton(ManageActionButton):
1300    grok.order(1)
1301    grok.context(IStudentAccommodation)
1302    grok.view(AccommodationManageFormPage)
1303    grok.require('waeup.handleAccommodation')
1304    icon = 'actionicon_home.png'
1305    text = 'Book accommodation'
1306    target = 'add'
1307
1308class BedTicketAddPage(WAeUPPage):
1309    """ Page to add an online payment ticket
1310    """
1311    grok.context(IStudentAccommodation)
1312    grok.name('add')
1313    grok.require('waeup.handleAccommodation')
1314    grok.template('enterpin')
1315    ac_prefix = 'HOS'
1316    label = 'Add bed ticket'
1317    title = 'Add bed ticket'
1318    pnav = 4
1319    buttonname = 'Create bed ticket'
1320    notice = ''
1321
1322    def update(self, SUBMIT=None):
1323        student = self.context.getStudent()
1324        students_utils = getUtility(IStudentsUtils)
1325        acc_details  = students_utils.getAccommodationDetails(student)
1326        if not student.state in acc_details['allowed_states']:
1327            self.flash("You are in the wrong registration state.")
1328            self.redirect(self.url(self.context))
1329            return
1330        if student['studycourse'].current_session != acc_details['booking_session']:
1331            self.flash(
1332                'Your current session does not match accommodation session.')
1333            self.redirect(self.url(self.context))
1334            return
1335        if str(acc_details['booking_session']) in self.context.keys():
1336            self.flash('You already booked a bed space in current accommodation session.')
1337            self.redirect(self.url(self.context))
1338            return
1339        self.ac_series = self.request.form.get('ac_series', None)
1340        self.ac_number = self.request.form.get('ac_number', None)
1341        if SUBMIT is None:
1342            return
1343        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1344        code = get_access_code(pin)
1345        if not code:
1346            self.flash('Activation code is invalid.')
1347            return
1348        # Search and book bed
1349        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1350        entries = cat.searchResults(
1351            owner=(student.student_id,student.student_id))
1352        if len(entries):
1353            # If bed space has bee manually allocated use this bed
1354            bed = [entry for entry in entries][0]
1355        else:
1356            # else search for other available beds
1357            entries = cat.searchResults(
1358                bed_type=(acc_details['bt'],acc_details['bt']))
1359            available_beds = [
1360                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1361            if available_beds:
1362                students_utils = getUtility(IStudentsUtils)
1363                bed = students_utils.selectBed(available_beds)
1364                bed.bookBed(student.student_id)
1365            else:
1366                self.flash('There is no free bed in your category %s.'
1367                            % acc_details['bt'])
1368                return
1369        # Mark pin as used (this also fires a pin related transition)
1370        if code.state == USED:
1371            self.flash('Activation code has already been used.')
1372            return
1373        else:
1374            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1375            # Here we know that the ac is in state initialized so we do not
1376            # expect an exception, but the owner might be different
1377            if not invalidate_accesscode(
1378                pin,comment,self.context.getStudent().student_id):
1379                self.flash('You are not the owner of this access code.')
1380                return
1381        # Create bed ticket
1382        bedticket = createObject(u'waeup.BedTicket')
1383        bedticket.booking_code = pin
1384        bedticket.booking_session = acc_details['booking_session']
1385        bedticket.bed_type = acc_details['bt']
1386        bedticket.bed = bed
1387        hall_title = bed.__parent__.hostel_name
1388        coordinates = bed.getBedCoordinates()[1:]
1389        block, room_nr, bed_nr = coordinates
1390        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1391            hall_title, block, room_nr, bed_nr, bed.bed_type)
1392        key = str(acc_details['booking_session'])
1393        self.context[key] = bedticket
1394        self.flash('Bed ticket created and bed booked: %s'
1395            % bedticket.bed_coordinates)
1396        self.redirect(self.url(self.context))
1397        return
1398
1399class BedTicketDisplayFormPage(WAeUPDisplayFormPage):
1400    """ Page to display bed tickets
1401    """
1402    grok.context(IBedTicket)
1403    grok.name('index')
1404    grok.require('waeup.handleAccommodation')
1405    form_fields = grok.AutoFields(IBedTicket)
1406    form_fields[
1407        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1408    pnav = 4
1409
1410    @property
1411    def label(self):
1412        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1413
1414    @property
1415    def title(self):
1416        return 'Bed Ticket %s' % self.context.getSessionString()
1417
1418class BedTicketSlipActionButton(ManageActionButton):
1419    grok.order(1)
1420    grok.context(IBedTicket)
1421    grok.view(BedTicketDisplayFormPage)
1422    grok.require('waeup.handleAccommodation')
1423    icon = 'actionicon_pdf.png'
1424    text = 'Download bed allocation slip'
1425    target = 'bed_allocation.pdf'
1426
1427class ExportPDFBedTicketSlipPage(grok.View):
1428    """Deliver a PDF slip of the context.
1429    """
1430    grok.context(IBedTicket)
1431    grok.name('bed_allocation.pdf')
1432    grok.require('waeup.handleAccommodation')
1433    form_fields = grok.AutoFields(IBedTicket)
1434    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1435    prefix = 'form'
1436
1437    @property
1438    def label(self):
1439        return 'Bed Allocation %s' % self.context.bed_coordinates
1440
1441    def render(self):
1442        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1443            self.request)
1444        students_utils = getUtility(IStudentsUtils)
1445        return students_utils.renderPDF(
1446            self,'Bed Allocation', 'bed_allocation.pdf',
1447            self.context.getStudent, studentview)
1448
1449class RelocateStudentActionButton(ManageActionButton):
1450    grok.order(2)
1451    grok.context(IBedTicket)
1452    grok.view(BedTicketDisplayFormPage)
1453    grok.require('waeup.manageHostels')
1454    icon = 'actionicon_reload.png'
1455    text = 'Relocate student'
1456    target = 'relocate'
1457
1458class BedTicketRelocationPage(grok.View):
1459    """ Callback view
1460    """
1461    grok.context(IBedTicket)
1462    grok.name('relocate')
1463    grok.require('waeup.manageHostels')
1464
1465    # Relocate student if student parameters have changed or the bed_type
1466    # of the bed has changed
1467    def update(self):
1468        student = self.context.getStudent()
1469        students_utils = getUtility(IStudentsUtils)
1470        acc_details  = students_utils.getAccommodationDetails(student)
1471        if self.context.bed != None and \
1472              'reserved' in self.context.bed.bed_type:
1473            self.flash("Students in reserved beds can't be relocated.")
1474            self.redirect(self.url(self.context))
1475            return
1476        if acc_details['bt'] == self.context.bed_type and \
1477                self.context.bed != None and \
1478                self.context.bed.bed_type == self.context.bed_type:
1479            self.flash("Student can't be relocated.")
1480            self.redirect(self.url(self.context))
1481            return
1482        # Search a bed
1483        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1484        entries = cat.searchResults(
1485            owner=(student.student_id,student.student_id))
1486        if len(entries) and self.context.bed == None:
1487            # If booking has been cancelled but other bed space has been
1488            # manually allocated after cancellation use this bed
1489            new_bed = [entry for entry in entries][0]
1490        else:
1491            # Search for other available beds
1492            entries = cat.searchResults(
1493                bed_type=(acc_details['bt'],acc_details['bt']))
1494            available_beds = [
1495                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1496            if available_beds:
1497                students_utils = getUtility(IStudentsUtils)
1498                new_bed = students_utils.selectBed(available_beds)
1499                new_bed.bookBed(student.student_id)
1500            else:
1501                self.flash('There is no free bed in your category %s.'
1502                            % acc_details['bt'])
1503                self.redirect(self.url(self.context))
1504                return
1505        # Rlease old bed if exists
1506        if self.context.bed != None:
1507            self.context.bed.owner = NOT_OCCUPIED
1508            notify(grok.ObjectModifiedEvent(self.context.bed))
1509        # Alocate new bed
1510        self.context.bed_type = acc_details['bt']
1511        self.context.bed = new_bed
1512        hall_title = new_bed.__parent__.hostel_name
1513        coordinates = new_bed.getBedCoordinates()[1:]
1514        block, room_nr, bed_nr = coordinates
1515        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1516            hall_title, block, room_nr, bed_nr, new_bed.bed_type)
1517        self.flash('Student relocated: %s' % self.context.bed_coordinates)
1518        self.redirect(self.url(self.context))
1519        return
1520
1521    def render(self):
1522        #self.redirect(self.url(self.context, '@@index'))
1523        return
1524
1525class StudentHistoryPage(WAeUPPage):
1526    """ Page to display student clearance data
1527    """
1528    grok.context(IStudent)
1529    grok.name('history')
1530    grok.require('waeup.viewStudent')
1531    grok.template('studenthistory')
1532    title = 'History'
1533    pnav = 4
1534
1535    @property
1536    def label(self):
1537        return '%s: History' % self.context.fullname
1538
1539# Pages for students only
1540
1541class StudentBaseActionButton(ManageActionButton):
1542    grok.order(1)
1543    grok.context(IStudent)
1544    grok.view(StudentBaseDisplayFormPage)
1545    grok.require('waeup.handleStudent')
1546    text = 'Edit base data'
1547    target = 'edit_base'
1548
1549class StudentPasswordActionButton(ManageActionButton):
1550    grok.order(2)
1551    grok.context(IStudent)
1552    grok.view(StudentBaseDisplayFormPage)
1553    grok.require('waeup.handleStudent')
1554    icon = 'actionicon_key.png'
1555    text = 'Change password'
1556    target = 'change_password'
1557
1558class StudentPassportActionButton(ManageActionButton):
1559    grok.order(3)
1560    grok.context(IStudent)
1561    grok.view(StudentBaseDisplayFormPage)
1562    grok.require('waeup.handleStudent')
1563    icon = 'actionicon_portrait.png'
1564    text = 'Change portrait'
1565    target = 'change_portrait'
1566
1567    @property
1568    def target_url(self):
1569        if self.context.state != 'admitted':
1570            return ''
1571        return self.view.url(self.view.context, self.target)
1572
1573class StudentBaseEditFormPage(WAeUPEditFormPage):
1574    """ View to edit student base data
1575    """
1576    grok.context(IStudent)
1577    grok.name('edit_base')
1578    grok.require('waeup.handleStudent')
1579    form_fields = grok.AutoFields(IStudentBase).select(
1580        'email', 'phone')
1581    label = 'Edit base data'
1582    title = 'Base Data'
1583    pnav = 4
1584
1585    @grok.action('Save')
1586    def save(self, **data):
1587        msave(self, **data)
1588        return
1589
1590class StudentChangePasswordPage(WAeUPEditFormPage):
1591    """ View to manage student base data
1592    """
1593    grok.context(IStudent)
1594    grok.name('change_password')
1595    grok.require('waeup.handleStudent')
1596    grok.template('change_password')
1597    label = 'Change password'
1598    title = 'Base Data'
1599    pnav = 4
1600
1601    @grok.action('Save')
1602    def save(self, **data):
1603        form = self.request.form
1604        password = form.get('change_password', None)
1605        password_ctl = form.get('change_password_repeat', None)
1606        if password:
1607            validator = getUtility(IPasswordValidator)
1608            errors = validator.validate_password(password, password_ctl)
1609            if not errors:
1610                IUserAccount(self.context).setPassword(password)
1611                write_log_message(self, 'saved: password')
1612                self.flash('Password changed.')
1613            else:
1614                self.flash( ' '.join(errors))
1615        return
1616
1617class StudentFilesUploadPage(WAeUPPage):
1618    """ View to upload files by student
1619    """
1620    grok.context(IStudent)
1621    grok.name('change_portrait')
1622    grok.require('waeup.uploadStudentFile')
1623    grok.template('filesuploadpage')
1624    label = 'Upload portrait'
1625    title = 'Base Data'
1626    pnav = 4
1627
1628    def update(self):
1629        if self.context.getStudent().state != 'admitted':
1630            emit_lock_message(self)
1631            return
1632        super(StudentFilesUploadPage, self).update()
1633        return
1634
1635class StudentClearanceStartActionButton(ManageActionButton):
1636    grok.order(1)
1637    grok.context(IStudent)
1638    grok.view(StudentClearanceDisplayFormPage)
1639    grok.require('waeup.handleStudent')
1640    icon = 'actionicon_start.gif'
1641    text = 'Start clearance'
1642    target = 'start_clearance'
1643
1644    @property
1645    def target_url(self):
1646        if self.context.state != 'admitted':
1647            return ''
1648        return self.view.url(self.view.context, self.target)
1649
1650class StartClearancePage(WAeUPPage):
1651    grok.context(IStudent)
1652    grok.name('start_clearance')
1653    grok.require('waeup.handleStudent')
1654    grok.template('enterpin')
1655    title = 'Start clearance'
1656    label = 'Start clearance'
1657    ac_prefix = 'CLR'
1658    notice = ''
1659    pnav = 4
1660    buttonname = 'Start clearance now'
1661
1662    @property
1663    def all_required_fields_filled(self):
1664        if self.context.email and self.context.phone:
1665            return True
1666        return False
1667
1668    @property
1669    def portrait_uploaded(self):
1670        store = getUtility(IExtFileStore)
1671        if store.getFileByContext(self.context, attr=u'passport.jpg'):
1672            return True
1673        return False
1674
1675    def update(self, SUBMIT=None):
1676        if not self.context.state == 'admitted':
1677            self.flash("Wrong state.")
1678            self.redirect(self.url(self.context))
1679            return
1680        if not self.portrait_uploaded:
1681            self.flash("No portrait uploaded.")
1682            self.redirect(self.url(self.context, 'change_portrait'))
1683            return
1684        if not self.all_required_fields_filled:
1685            self.flash("Not all required fields filled.")
1686            self.redirect(self.url(self.context, 'edit_base'))
1687            return
1688        self.ac_series = self.request.form.get('ac_series', None)
1689        self.ac_number = self.request.form.get('ac_number', None)
1690
1691        if SUBMIT is None:
1692            return
1693        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1694        code = get_access_code(pin)
1695        if not code:
1696            self.flash('Activation code is invalid.')
1697            return
1698        # Mark pin as used (this also fires a pin related transition)
1699        # and fire transition start_clearance
1700        if code.state == USED:
1701            self.flash('Activation code has already been used.')
1702            return
1703        else:
1704            comment = u"AC invalidated for %s" % self.context.student_id
1705            # Here we know that the ac is in state initialized so we do not
1706            # expect an exception, but the owner might be different
1707            if not invalidate_accesscode(pin,comment,self.context.student_id):
1708                self.flash('You are not the owner of this access code.')
1709                return
1710            self.context.clr_code = pin
1711        IWorkflowInfo(self.context).fireTransition('start_clearance')
1712        self.flash('Clearance process has been started.')
1713        self.redirect(self.url(self.context,'cedit'))
1714        return
1715
1716class StudentClearanceEditActionButton(ManageActionButton):
1717    grok.order(1)
1718    grok.context(IStudent)
1719    grok.view(StudentClearanceDisplayFormPage)
1720    grok.require('waeup.handleStudent')
1721    text = 'Edit'
1722    target = 'cedit'
1723
1724    @property
1725    def target_url(self):
1726        if self.context.clearance_locked:
1727            return ''
1728        return self.view.url(self.view.context, self.target)
1729
1730class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1731    """ View to edit student clearance data by student
1732    """
1733    grok.context(IStudent)
1734    grok.name('cedit')
1735    grok.require('waeup.handleStudent')
1736    form_fields = grok.AutoFields(
1737        IStudentClearanceEdit).omit('clearance_locked')
1738    label = 'Edit clearance data'
1739    title = 'Clearance Data'
1740    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1741
1742    def update(self):
1743        if self.context.clearance_locked:
1744            emit_lock_message(self)
1745            return
1746        return super(StudentClearanceEditFormPage, self).update()
1747
1748    @grok.action('Save')
1749    def save(self, **data):
1750        self.applyData(self.context, **data)
1751        self.flash('Clearance form has been saved.')
1752        return
1753
1754    # To be specified in the customisation package
1755    def dataNotComplete(self):
1756        #store = getUtility(IExtFileStore)
1757        #if not store.getFileByContext(self.context, attr=u'xyz.jpg'):
1758        #    return 'No xyz scan uploaded.'
1759        return False
1760
1761    @grok.action('Save and request clearance')
1762    def requestClearance(self, **data):
1763        self.applyData(self.context, **data)
1764        #self.context._p_changed = True
1765        if self.dataNotComplete():
1766            self.flash(self.dataNotComplete())
1767            return
1768        self.flash('Clearance form has been saved.')
1769        self.redirect(self.url(self.context,'request_clearance'))
1770        return
1771
1772class RequestClearancePage(WAeUPPage):
1773    grok.context(IStudent)
1774    grok.name('request_clearance')
1775    grok.require('waeup.handleStudent')
1776    grok.template('enterpin')
1777    title = 'Request clearance'
1778    label = 'Request clearance'
1779    notice = 'Enter the CLR access code used for starting clearance.'
1780    ac_prefix = 'CLR'
1781    pnav = 4
1782    buttonname = 'Request clearance now'
1783
1784    def update(self, SUBMIT=None):
1785        self.ac_series = self.request.form.get('ac_series', None)
1786        self.ac_number = self.request.form.get('ac_number', None)
1787        if SUBMIT is None:
1788            return
1789        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1790        if self.context.clr_code != pin:
1791            self.flash("This isn't your CLR access code.")
1792            return
1793        state = IWorkflowState(self.context).getState()
1794        # This shouldn't happen, but the application officer
1795        # might have forgotten to lock the form after changing the state
1796        if state != CLEARANCE:
1797            self.flash('This form cannot be submitted. Wrong state!')
1798            return
1799        IWorkflowInfo(self.context).fireTransition('request_clearance')
1800        self.flash('Clearance has been requested.')
1801        self.redirect(self.url(self.context))
1802        return
1803
1804class CourseRegistrationStartActionButton(ManageActionButton):
1805    grok.order(1)
1806    grok.context(IStudentStudyCourse)
1807    grok.view(StudyCourseDisplayFormPage)
1808    grok.require('waeup.handleStudent')
1809    icon = 'actionicon_start.gif'
1810    text = 'Start course registration'
1811    target = 'start_course_registration'
1812
1813    @property
1814    def target_url(self):
1815        if not self.context.getStudent().state in (CLEARED,RETURNING):
1816            return ''
1817        return self.view.url(self.view.context, self.target)
1818
1819class StartCourseRegistrationPage(WAeUPPage):
1820    grok.context(IStudentStudyCourse)
1821    grok.name('start_course_registration')
1822    grok.require('waeup.handleStudent')
1823    grok.template('enterpin')
1824    title = 'Start course registration'
1825    label = 'Start course registration'
1826    ac_prefix = 'SFE'
1827    notice = ''
1828    pnav = 4
1829    buttonname = 'Start course registration now'
1830
1831    def update(self, SUBMIT=None):
1832        if not self.context.getStudent().state in (CLEARED,RETURNING):
1833            self.flash("Wrong state.")
1834            self.redirect(self.url(self.context))
1835            return
1836        self.ac_series = self.request.form.get('ac_series', None)
1837        self.ac_number = self.request.form.get('ac_number', None)
1838
1839        if SUBMIT is None:
1840            return
1841        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1842        code = get_access_code(pin)
1843        if not code:
1844            self.flash('Activation code is invalid.')
1845            return
1846        # Mark pin as used (this also fires a pin related transition)
1847        # and fire transition start_clearance
1848        if code.state == USED:
1849            self.flash('Activation code has already been used.')
1850            return
1851        else:
1852            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1853            # Here we know that the ac is in state initialized so we do not
1854            # expect an exception, but the owner might be different
1855            if not invalidate_accesscode(
1856                pin,comment,self.context.getStudent().student_id):
1857                self.flash('You are not the owner of this access code.')
1858                return
1859        if self.context.getStudent().state == CLEARED:
1860            IWorkflowInfo(self.context.getStudent()).fireTransition(
1861                'pay_first_school_fee')
1862        elif self.context.getStudent().state == RETURNING:
1863            IWorkflowInfo(self.context.getStudent()).fireTransition(
1864                'pay_school_fee')
1865        self.flash('Course registration has been started.')
1866        self.redirect(self.url(self.context))
1867        return
1868
1869
1870class AddStudyLevelActionButton(AddActionButton):
1871    grok.order(1)
1872    grok.context(IStudentStudyCourse)
1873    grok.view(StudyCourseDisplayFormPage)
1874    grok.require('waeup.handleStudent')
1875    text = 'Add course list'
1876    target = 'add'
1877
1878    @property
1879    def target_url(self):
1880        student = self.view.context.getStudent()
1881        condition1 = student.state != 'school fee paid'
1882        condition2 = str(student['studycourse'].current_level) in \
1883            self.view.context.keys()
1884        if condition1 or condition2:
1885            return ''
1886        return self.view.url(self.view.context, self.target)
1887
1888class AddStudyLevelFormPage(WAeUPEditFormPage):
1889    """ Page for students to add current study levels
1890    """
1891    grok.context(IStudentStudyCourse)
1892    grok.name('add')
1893    grok.require('waeup.handleStudent')
1894    grok.template('studyleveladdpage')
1895    form_fields = grok.AutoFields(IStudentStudyCourse)
1896    title = 'Study Course'
1897    pnav = 4
1898
1899    @property
1900    def label(self):
1901        studylevelsource = StudyLevelSource().factory
1902        code = self.context.current_level
1903        title = studylevelsource.getTitle(self.context, code)
1904        return 'Add current level %s' % title
1905
1906    def update(self):
1907        if self.context.getStudent().state != 'school fee paid':
1908            emit_lock_message(self)
1909            return
1910        super(AddStudyLevelFormPage, self).update()
1911        return
1912
1913    @grok.action('Create course list now')
1914    def addStudyLevel(self, **data):
1915        studylevel = StudentStudyLevel()
1916        studylevel.level = self.context.current_level
1917        studylevel.level_session = self.context.current_session
1918        try:
1919            self.context.addStudentStudyLevel(
1920                self.context.certificate,studylevel)
1921        except KeyError:
1922            self.flash('This level exists.')
1923        self.redirect(self.url(self.context))
1924        return
1925
1926class StudyLevelEditActionButton(ManageActionButton):
1927    grok.order(1)
1928    grok.context(IStudentStudyLevel)
1929    grok.view(StudyLevelDisplayFormPage)
1930    grok.require('waeup.handleStudent')
1931    text = 'Add and remove courses'
1932    target = 'edit'
1933
1934    @property
1935    def target_url(self):
1936        student = self.view.context.getStudent()
1937        condition1 = student.state != 'school fee paid'
1938        condition2 = student[
1939            'studycourse'].current_level != self.view.context.level
1940        if condition1 or condition2:
1941            return ''
1942        return self.view.url(self.view.context, self.target)
1943
1944class StudyLevelEditFormPage(WAeUPEditFormPage):
1945    """ Page to edit the student study level data by students
1946    """
1947    grok.context(IStudentStudyLevel)
1948    grok.name('edit')
1949    grok.require('waeup.handleStudent')
1950    grok.template('studyleveleditpage')
1951    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1952        'level_session', 'level_verdict')
1953    pnav = 4
1954
1955    def update(self):
1956        super(StudyLevelEditFormPage, self).update()
1957    #    tabs.need()
1958        datatable.need()
1959        return
1960
1961    @property
1962    def title(self):
1963        return 'Study Level %s' % self.context.level_title
1964
1965    @property
1966    def label(self):
1967        return 'Add and remove course tickets of study level %s' % self.context.level_title
1968
1969    @property
1970    def total_credits(self):
1971        total_credits = 0
1972        for key, val in self.context.items():
1973            total_credits += val.credits
1974        return total_credits
1975
1976    @grok.action('Add course ticket')
1977    def addCourseTicket(self, **data):
1978        self.redirect(self.url(self.context, 'ctadd'))
1979
1980    @grok.action('Remove selected tickets')
1981    def delCourseTicket(self, **data):
1982        form = self.request.form
1983        if form.has_key('val_id'):
1984            child_id = form['val_id']
1985        else:
1986            self.flash('No ticket selected.')
1987            self.redirect(self.url(self.context, '@@edit'))
1988            return
1989        if not isinstance(child_id, list):
1990            child_id = [child_id]
1991        deleted = []
1992        for id in child_id:
1993            # Students are not allowed to remove core tickets
1994            if not self.context[id].core_or_elective:
1995                try:
1996                    del self.context[id]
1997                    deleted.append(id)
1998                except:
1999                    self.flash('Could not delete %s: %s: %s' % (
2000                            id, sys.exc_info()[0], sys.exc_info()[1]))
2001        if len(deleted):
2002            self.flash('Successfully removed: %s' % ', '.join(deleted))
2003        self.redirect(self.url(self.context, u'@@edit'))
2004        return
2005
2006    @grok.action('Register course list')
2007    def RegisterCourses(self, **data):
2008        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
2009        self.flash('Course list has been registered.')
2010        self.redirect(self.url(self.context))
2011        return
2012
2013class CourseTicketAddFormPage2(CourseTicketAddFormPage):
2014    """Add a course ticket by student.
2015    """
2016    grok.name('ctadd')
2017    grok.require('waeup.handleStudent')
2018    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
2019        'grade', 'score', 'core_or_elective', 'automatic')
2020
2021    @grok.action('Add course ticket')
2022    def addCourseTicket(self, **data):
2023        ticket = CourseTicket()
2024        course = data['course']
2025        ticket.automatic = False
2026        ticket.code = course.code
2027        ticket.title = course.title
2028        ticket.faculty = course.__parent__.__parent__.__parent__.title
2029        ticket.department = course.__parent__.__parent__.title
2030        ticket.credits = course.credits
2031        ticket.passmark = course.passmark
2032        ticket.semester = course.semester
2033        try:
2034            self.context.addCourseTicket(ticket)
2035        except KeyError:
2036            self.flash('The ticket exists.')
2037            return
2038        self.flash('Successfully added %s.' % ticket.code)
2039        self.redirect(self.url(self.context, u'@@edit'))
2040        return
Note: See TracBrowser for help on using the repository browser.