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

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

Use new principal attributes in contact form pages (applicants not yet ready).

(And some missing things from last checkin).

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