source: main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py @ 7686

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

Uses sources instead of vocabularies and feed sources with dictionaries defined in SIRPUtils. This way we can easily customize the sources.

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