source: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py @ 9031

Last change on this file since 9031 was 9028, checked in by Henrik Bettermann, 12 years ago

Disable states and transitions which are not allowed for pg students. Not yet fully tested.

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