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

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

reg_number must be provided when adding students. Otherwise new students don't find their record.

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