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

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

Translate boolean values in tables and slips.

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