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

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

Check if booking perios is expired before allocating a bed.

To do: The update method of BedTicketAddPage? is too long and code has to be split and moved to data models.

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