source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser.py @ 9607

Last change on this file since 9607 was 9211, checked in by uli, 12 years ago

Rollback r9209. Looks like multiple merges from trunk confuse svn when merging back into trunk.

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