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

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

Add buttons and views for activating and deactivating student accounts.

Add history messages and log entries when students are being activated or deactivated.

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