source: main/waeup.sirp/branches/henrik-bootstrap/src/waeup/sirp/students/browser.py @ 11102

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

Add breadcrumbs to student site layout. Omit the Students breadcrumb if principal is a student.

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