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

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

Add payment session column. Remove unused pagetemplate.

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