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

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

Harmonize sendCredentials which can now be used for all kinds of users.

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