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

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

Move all action button viewlets from browser module to viewlets module.

  • Property svn:keywords set to Id
File size: 66.4 KB
Line 
1## $Id: browser.py 7591 2012-02-06 16:57:37Z 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# We don't need the display form page yet
932#class PaymentsDisplayFormPage(SIRPDisplayFormPage):
933#    """ Page to display the student payments
934#    """
935#    grok.context(IStudentPaymentsContainer)
936#    grok.name('view')
937#    grok.require('waeup.viewStudent')
938#    form_fields = grok.AutoFields(IStudentPaymentsContainer)
939#    grok.template('paymentspage')
940#    pnav = 4
941
942#    @property
943#    def label(self):
944#        return '%s: Payments' % self.context.__parent__.display_fullname
945
946#    def update(self):
947#        super(PaymentsDisplayFormPage, self).update()
948#        datatable.need()
949#        return
950
951# This manage form page is for both students and students officers.
952class PaymentsManageFormPage(SIRPEditFormPage):
953    """ Page to manage the student payments
954    """
955    grok.context(IStudentPaymentsContainer)
956    grok.name('index')
957    grok.require('waeup.payStudent')
958    form_fields = grok.AutoFields(IStudentPaymentsContainer)
959    grok.template('paymentsmanagepage')
960    pnav = 4
961
962    def unremovable(self, ticket):
963        usertype = getattr(self.request.principal, 'user_type', None)
964        if not usertype:
965            return False
966        return (self.request.principal.user_type == 'student' and ticket.r_code)
967
968    @property
969    def label(self):
970        return '%s: Payments' % self.context.__parent__.display_fullname
971
972    def update(self):
973        super(PaymentsManageFormPage, self).update()
974        datatable.need()
975        warning.need()
976        return
977
978    @jsaction('Remove selected tickets')
979    def delPaymentTicket(self, **data):
980        form = self.request.form
981        if form.has_key('val_id'):
982            child_id = form['val_id']
983        else:
984            self.flash('No payment selected.')
985            self.redirect(self.url(self.context))
986            return
987        if not isinstance(child_id, list):
988            child_id = [child_id]
989        deleted = []
990        for id in child_id:
991            # Students are not allowed to remove used payment tickets
992            if not self.unremovable(self.context[id]):
993                try:
994                    del self.context[id]
995                    deleted.append(id)
996                except:
997                    self.flash('Could not delete %s: %s: %s' % (
998                            id, sys.exc_info()[0], sys.exc_info()[1]))
999        if len(deleted):
1000            self.flash('Successfully removed: %s' % ', '.join(deleted))
1001            write_log_message(self,'removed: % s' % ', '.join(deleted))
1002        self.redirect(self.url(self.context))
1003        return
1004
1005    @action('Add online payment ticket')
1006    def addPaymentTicket(self, **data):
1007        self.redirect(self.url(self.context, '@@addop'))
1008
1009class OnlinePaymentAddFormPage(SIRPAddFormPage):
1010    """ Page to add an online payment ticket
1011    """
1012    grok.context(IStudentPaymentsContainer)
1013    grok.name('addop')
1014    grok.require('waeup.payStudent')
1015    form_fields = grok.AutoFields(IStudentOnlinePayment).select(
1016        'p_category')
1017    label = 'Add online payment'
1018    pnav = 4
1019   
1020    @action('Create ticket', style='primary')
1021    def createTicket(self, **data):
1022        p_category = data['p_category']
1023        student = self.context.__parent__
1024        if p_category == 'bed_allocation' and student[
1025            'studycourse'].current_session != grok.getSite()[
1026            'configuration'].accommodation_session:
1027                self.flash(
1028                    'Your current session does not match accommodation session.')
1029                self.redirect(self.url(self.context))
1030                return
1031        students_utils = getUtility(IStudentsUtils)
1032        pay_details  = students_utils.getPaymentDetails(
1033            p_category,student)
1034        if pay_details['error']:
1035            self.flash(pay_details['error'])
1036            self.redirect(self.url(self.context))
1037            return
1038        p_item = pay_details['p_item']
1039        p_session = pay_details['p_session']
1040        for key in self.context.keys():
1041            ticket = self.context[key]
1042            if ticket.p_state == 'paid' and\
1043               ticket.p_category == p_category and \
1044               ticket.p_item == p_item and \
1045               ticket.p_session == p_session:
1046                  self.flash(
1047                      'This type of payment has already been made.')
1048                  self.redirect(self.url(self.context))
1049                  return
1050        payment = createObject(u'waeup.StudentOnlinePayment')
1051        self.applyData(payment, **data)
1052        timestamp = "%d" % int(time()*1000)
1053        #order_id = "%s%s" % (student_id[1:],timestamp)
1054        payment.p_id = "p%s" % timestamp
1055        payment.p_item = p_item
1056        payment.p_session = p_session
1057        payment.amount_auth = pay_details['amount']
1058        payment.surcharge_1 = pay_details['surcharge_1']
1059        payment.surcharge_2 = pay_details['surcharge_2']
1060        payment.surcharge_3 = pay_details['surcharge_3']
1061        self.context[payment.p_id] = payment
1062        self.flash('Payment ticket created.')
1063        self.redirect(self.url(self.context))
1064        return
1065
1066class OnlinePaymentDisplayFormPage(SIRPDisplayFormPage):
1067    """ Page to view an online payment ticket
1068    """
1069    grok.context(IStudentOnlinePayment)
1070    grok.name('index')
1071    grok.require('waeup.viewStudent')
1072    form_fields = grok.AutoFields(IStudentOnlinePayment)
1073    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1074    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1075    pnav = 4
1076
1077    @property
1078    def label(self):
1079        return '%s: Online Payment Ticket %s' % (
1080            self.context.getStudent().display_fullname,self.context.p_id)
1081
1082class OnlinePaymentCallbackPage(UtilityView, grok.View):
1083    """ Callback view
1084    """
1085    grok.context(IStudentOnlinePayment)
1086    grok.name('callback')
1087    grok.require('waeup.payStudent')
1088
1089    # This update method simulates a valid callback und must be
1090    # specified in the customization package. The parameters must be taken
1091    # from the incoming request.
1092    def update(self):
1093        if self.context.p_state == 'paid':
1094            self.flash('This ticket has already been paid.')
1095            return
1096        student = self.context.getStudent()
1097        write_log_message(self,'valid callback: %s' % self.context.p_id)
1098        self.context.r_amount_approved = self.context.amount_auth
1099        self.context.r_card_num = u'0000'
1100        self.context.r_code = u'00'
1101        self.context.p_state = 'paid'
1102        self.context.payment_date = datetime.now()
1103        if self.context.p_category == 'clearance':
1104            # Create CLR access code
1105            pin, error = create_accesscode('CLR',0,student.student_id)
1106            if error:
1107                self.flash('Valid callback received. ' + error)
1108                return
1109            self.context.ac = pin
1110        elif self.context.p_category == 'schoolfee':
1111            # Create SFE access code
1112            pin, error = create_accesscode('SFE',0,student.student_id)
1113            if error:
1114                self.flash('Valid callback received. ' + error)
1115                return
1116            self.context.ac = pin
1117        elif self.context.p_category == 'bed_allocation':
1118            # Create HOS access code
1119            pin, error = create_accesscode('HOS',0,student.student_id)
1120            if error:
1121                self.flash('Valid callback received. ' + error)
1122                return
1123            self.context.ac = pin
1124        self.flash('Valid callback received.')
1125        return
1126
1127    def render(self):
1128        self.redirect(self.url(self.context, '@@index'))
1129        return
1130
1131class ExportPDFPaymentSlipPage(UtilityView, grok.View):
1132    """Deliver a PDF slip of the context.
1133    """
1134    grok.context(IStudentOnlinePayment)
1135    grok.name('payment_receipt.pdf')
1136    grok.require('waeup.viewStudent')
1137    form_fields = grok.AutoFields(IStudentOnlinePayment)
1138    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
1139    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
1140    prefix = 'form'
1141    title = 'Payment Data'
1142
1143    @property
1144    def label(self):
1145        return 'Online Payment Receipt %s' % self.context.p_id
1146
1147    def render(self):
1148        if self.context.p_state != 'paid':
1149            self.flash('Ticket not yet paid.')
1150            self.redirect(self.url(self.context))
1151            return
1152        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1153            self.request)
1154        students_utils = getUtility(IStudentsUtils)
1155        return students_utils.renderPDF(self, 'payment_receipt.pdf',
1156            self.context.getStudent(), studentview)
1157
1158# We don't need the display form page yet
1159#class AccommodationDisplayFormPage(SIRPDisplayFormPage):
1160#    """ Page to display the student accommodation data
1161#    """
1162#    grok.context(IStudentAccommodation)
1163#    grok.name('xxx')
1164#    grok.require('waeup.viewStudent')
1165#    form_fields = grok.AutoFields(IStudentAccommodation)
1166#    #grok.template('accommodationpage')
1167#    pnav = 4
1168
1169#    @property
1170#    def label(self):
1171#        return '%s: Accommodation Data' % self.context.__parent__.display_fullname
1172
1173# This manage form page is for both students and students officers.
1174class AccommodationManageFormPage(SIRPEditFormPage):
1175    """ Page to manage bed tickets.
1176    """
1177    grok.context(IStudentAccommodation)
1178    grok.name('index')
1179    grok.require('waeup.handleAccommodation')
1180    form_fields = grok.AutoFields(IStudentAccommodation)
1181    grok.template('accommodationmanagepage')
1182    pnav = 4
1183    officers_only_actions = ['Remove selected']
1184
1185    @property
1186    def label(self):
1187        return '%s: Accommodation' % self.context.__parent__.display_fullname
1188
1189    def update(self):
1190        super(AccommodationManageFormPage, self).update()
1191        datatable.need()
1192        warning.need()
1193        return
1194
1195    @jsaction('Remove selected')
1196    def delBedTickets(self, **data):
1197        if getattr(self.request.principal, 'user_type', None) == 'student':
1198            self.flash('You are not allowed to remove bed tickets.')
1199            self.redirect(self.url(self.context))
1200            return
1201        form = self.request.form
1202        if form.has_key('val_id'):
1203            child_id = form['val_id']
1204        else:
1205            self.flash('No bed ticket selected.')
1206            self.redirect(self.url(self.context))
1207            return
1208        if not isinstance(child_id, list):
1209            child_id = [child_id]
1210        deleted = []
1211        for id in child_id:
1212            del self.context[id]
1213            deleted.append(id)
1214        if len(deleted):
1215            self.flash('Successfully removed: %s' % ', '.join(deleted))
1216            write_log_message(self,'removed: % s' % ', '.join(deleted))
1217        self.redirect(self.url(self.context))
1218        return
1219
1220    @property
1221    def selected_actions(self):
1222        sa = self.actions
1223        if getattr(self.request.principal, 'user_type', None) == 'student':
1224            sa = [action for action in self.actions
1225                  if not action.label in self.officers_only_actions]
1226        return sa
1227
1228class BedTicketAddPage(SIRPPage):
1229    """ Page to add an online payment ticket
1230    """
1231    grok.context(IStudentAccommodation)
1232    grok.name('add')
1233    grok.require('waeup.handleAccommodation')
1234    grok.template('enterpin')
1235    ac_prefix = 'HOS'
1236    label = 'Add bed ticket'
1237    pnav = 4
1238    buttonname = 'Create bed ticket'
1239    notice = ''
1240
1241    def update(self, SUBMIT=None):
1242        student = self.context.getStudent()
1243        students_utils = getUtility(IStudentsUtils)
1244        acc_details  = students_utils.getAccommodationDetails(student)
1245        if not acc_details:
1246            self.flash("Your data are incomplete.")
1247            self.redirect(self.url(self.context))
1248            return
1249        if not student.state in acc_details['allowed_states']:
1250            self.flash("You are in the wrong registration state.")
1251            self.redirect(self.url(self.context))
1252            return
1253        if student['studycourse'].current_session != acc_details['booking_session']:
1254            self.flash(
1255                'Your current session does not match accommodation session.')
1256            self.redirect(self.url(self.context))
1257            return
1258        if str(acc_details['booking_session']) in self.context.keys():
1259            self.flash('You already booked a bed space in current accommodation session.')
1260            self.redirect(self.url(self.context))
1261            return
1262        self.ac_series = self.request.form.get('ac_series', None)
1263        self.ac_number = self.request.form.get('ac_number', None)
1264        if SUBMIT is None:
1265            return
1266        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1267        code = get_access_code(pin)
1268        if not code:
1269            self.flash('Activation code is invalid.')
1270            return
1271        # Search and book bed
1272        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1273        entries = cat.searchResults(
1274            owner=(student.student_id,student.student_id))
1275        if len(entries):
1276            # If bed space has bee manually allocated use this bed
1277            bed = [entry for entry in entries][0]
1278        else:
1279            # else search for other available beds
1280            entries = cat.searchResults(
1281                bed_type=(acc_details['bt'],acc_details['bt']))
1282            available_beds = [
1283                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1284            if available_beds:
1285                students_utils = getUtility(IStudentsUtils)
1286                bed = students_utils.selectBed(available_beds)
1287                bed.bookBed(student.student_id)
1288            else:
1289                self.flash('There is no free bed in your category %s.'
1290                            % acc_details['bt'])
1291                return
1292        # Mark pin as used (this also fires a pin related transition)
1293        if code.state == USED:
1294            self.flash('Activation code has already been used.')
1295            return
1296        else:
1297            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1298            # Here we know that the ac is in state initialized so we do not
1299            # expect an exception, but the owner might be different
1300            if not invalidate_accesscode(
1301                pin,comment,self.context.getStudent().student_id):
1302                self.flash('You are not the owner of this access code.')
1303                return
1304        # Create bed ticket
1305        bedticket = createObject(u'waeup.BedTicket')
1306        bedticket.booking_code = pin
1307        bedticket.booking_session = acc_details['booking_session']
1308        bedticket.bed_type = acc_details['bt']
1309        bedticket.bed = bed
1310        hall_title = bed.__parent__.hostel_name
1311        coordinates = bed.getBedCoordinates()[1:]
1312        block, room_nr, bed_nr = coordinates
1313        bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1314            hall_title, block, room_nr, bed_nr, bed.bed_type)
1315        key = str(acc_details['booking_session'])
1316        self.context[key] = bedticket
1317        self.flash('Bed ticket created and bed booked: %s'
1318            % bedticket.bed_coordinates)
1319        self.redirect(self.url(self.context))
1320        return
1321
1322class BedTicketDisplayFormPage(SIRPDisplayFormPage):
1323    """ Page to display bed tickets
1324    """
1325    grok.context(IBedTicket)
1326    grok.name('index')
1327    grok.require('waeup.handleAccommodation')
1328    form_fields = grok.AutoFields(IBedTicket)
1329    form_fields[
1330        'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1331    pnav = 4
1332
1333    @property
1334    def label(self):
1335        return 'Bed Ticket for Session %s' % self.context.getSessionString()
1336
1337class ExportPDFBedTicketSlipPage(UtilityView, grok.View):
1338    """Deliver a PDF slip of the context.
1339    """
1340    grok.context(IBedTicket)
1341    grok.name('bed_allocation.pdf')
1342    grok.require('waeup.handleAccommodation')
1343    form_fields = grok.AutoFields(IBedTicket)
1344    form_fields['booking_date'].custom_widget = FriendlyDateDisplayWidget('le')
1345    prefix = 'form'
1346    title = 'Bed Allocation Data'
1347
1348    @property
1349    def label(self):
1350        return 'Bed Allocation: %s' % self.context.bed_coordinates
1351
1352    def render(self):
1353        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
1354            self.request)
1355        students_utils = getUtility(IStudentsUtils)
1356        return students_utils.renderPDF(
1357            self, 'bed_allocation.pdf',
1358            self.context.getStudent(), studentview)
1359
1360class BedTicketRelocationPage(UtilityView, grok.View):
1361    """ Callback view
1362    """
1363    grok.context(IBedTicket)
1364    grok.name('relocate')
1365    grok.require('waeup.manageHostels')
1366
1367    # Relocate student if student parameters have changed or the bed_type
1368    # of the bed has changed
1369    def update(self):
1370        student = self.context.getStudent()
1371        students_utils = getUtility(IStudentsUtils)
1372        acc_details  = students_utils.getAccommodationDetails(student)
1373        if self.context.bed != None and \
1374              'reserved' in self.context.bed.bed_type:
1375            self.flash("Students in reserved beds can't be relocated.")
1376            self.redirect(self.url(self.context))
1377            return
1378        if acc_details['bt'] == self.context.bed_type and \
1379                self.context.bed != None and \
1380                self.context.bed.bed_type == self.context.bed_type:
1381            self.flash("Student can't be relocated.")
1382            self.redirect(self.url(self.context))
1383            return
1384        # Search a bed
1385        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
1386        entries = cat.searchResults(
1387            owner=(student.student_id,student.student_id))
1388        if len(entries) and self.context.bed == None:
1389            # If booking has been cancelled but other bed space has been
1390            # manually allocated after cancellation use this bed
1391            new_bed = [entry for entry in entries][0]
1392        else:
1393            # Search for other available beds
1394            entries = cat.searchResults(
1395                bed_type=(acc_details['bt'],acc_details['bt']))
1396            available_beds = [
1397                entry for entry in entries if entry.owner == NOT_OCCUPIED]
1398            if available_beds:
1399                students_utils = getUtility(IStudentsUtils)
1400                new_bed = students_utils.selectBed(available_beds)
1401                new_bed.bookBed(student.student_id)
1402            else:
1403                self.flash('There is no free bed in your category %s.'
1404                            % acc_details['bt'])
1405                self.redirect(self.url(self.context))
1406                return
1407        # Rlease old bed if exists
1408        if self.context.bed != None:
1409            self.context.bed.owner = NOT_OCCUPIED
1410            notify(grok.ObjectModifiedEvent(self.context.bed))
1411        # Alocate new bed
1412        self.context.bed_type = acc_details['bt']
1413        self.context.bed = new_bed
1414        hall_title = new_bed.__parent__.hostel_name
1415        coordinates = new_bed.getBedCoordinates()[1:]
1416        block, room_nr, bed_nr = coordinates
1417        self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s (%s)' % (
1418            hall_title, block, room_nr, bed_nr, new_bed.bed_type)
1419        self.flash('Student relocated: %s' % self.context.bed_coordinates)
1420        self.redirect(self.url(self.context))
1421        return
1422
1423    def render(self):
1424        #self.redirect(self.url(self.context, '@@index'))
1425        return
1426
1427class StudentHistoryPage(SIRPPage):
1428    """ Page to display student clearance data
1429    """
1430    grok.context(IStudent)
1431    grok.name('history')
1432    grok.require('waeup.viewStudent')
1433    grok.template('studenthistory')
1434    pnav = 4
1435
1436    @property
1437    def label(self):
1438        return '%s: History' % self.context.display_fullname
1439
1440# Pages for students only
1441
1442class StudentBaseEditFormPage(SIRPEditFormPage):
1443    """ View to edit student base data
1444    """
1445    grok.context(IStudent)
1446    grok.name('edit_base')
1447    grok.require('waeup.handleStudent')
1448    form_fields = grok.AutoFields(IStudentBase).select(
1449        'email', 'phone')
1450    label = 'Edit base data'
1451    pnav = 4
1452
1453    @action('Save', style='primary')
1454    def save(self, **data):
1455        msave(self, **data)
1456        return
1457
1458class StudentChangePasswordPage(SIRPEditFormPage):
1459    """ View to manage student base data
1460    """
1461    grok.context(IStudent)
1462    grok.name('change_password')
1463    grok.require('waeup.handleStudent')
1464    grok.template('change_password')
1465    label = 'Change password'
1466    pnav = 4
1467
1468    @action('Save', style='primary')
1469    def save(self, **data):
1470        form = self.request.form
1471        password = form.get('change_password', None)
1472        password_ctl = form.get('change_password_repeat', None)
1473        if password:
1474            validator = getUtility(IPasswordValidator)
1475            errors = validator.validate_password(password, password_ctl)
1476            if not errors:
1477                IUserAccount(self.context).setPassword(password)
1478                write_log_message(self, 'saved: password')
1479                self.flash('Password changed.')
1480            else:
1481                self.flash( ' '.join(errors))
1482        return
1483
1484class StudentFilesUploadPage(SIRPPage):
1485    """ View to upload files by student
1486    """
1487    grok.context(IStudent)
1488    grok.name('change_portrait')
1489    grok.require('waeup.uploadStudentFile')
1490    grok.template('filesuploadpage')
1491    label = 'Upload portrait'
1492    pnav = 4
1493
1494    def update(self):
1495        if self.context.getStudent().state != ADMITTED:
1496            emit_lock_message(self)
1497            return
1498        super(StudentFilesUploadPage, self).update()
1499        return
1500
1501class StartClearancePage(SIRPPage):
1502    grok.context(IStudent)
1503    grok.name('start_clearance')
1504    grok.require('waeup.handleStudent')
1505    grok.template('enterpin')
1506    label = 'Start clearance'
1507    ac_prefix = 'CLR'
1508    notice = ''
1509    pnav = 4
1510    buttonname = 'Start clearance now'
1511
1512    @property
1513    def all_required_fields_filled(self):
1514        if self.context.email and self.context.phone:
1515            return True
1516        return False
1517
1518    @property
1519    def portrait_uploaded(self):
1520        store = getUtility(IExtFileStore)
1521        if store.getFileByContext(self.context, attr=u'passport.jpg'):
1522            return True
1523        return False
1524
1525    def update(self, SUBMIT=None):
1526        if not self.context.state == 'admitted':
1527            self.flash("Wrong state.")
1528            self.redirect(self.url(self.context))
1529            return
1530        if not self.portrait_uploaded:
1531            self.flash("No portrait uploaded.")
1532            self.redirect(self.url(self.context, 'change_portrait'))
1533            return
1534        if not self.all_required_fields_filled:
1535            self.flash("Not all required fields filled.")
1536            self.redirect(self.url(self.context, 'edit_base'))
1537            return
1538        self.ac_series = self.request.form.get('ac_series', None)
1539        self.ac_number = self.request.form.get('ac_number', None)
1540
1541        if SUBMIT is None:
1542            return
1543        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1544        code = get_access_code(pin)
1545        if not code:
1546            self.flash('Activation code is invalid.')
1547            return
1548        # Mark pin as used (this also fires a pin related transition)
1549        # and fire transition start_clearance
1550        if code.state == USED:
1551            self.flash('Activation code has already been used.')
1552            return
1553        else:
1554            comment = u"AC invalidated for %s" % self.context.student_id
1555            # Here we know that the ac is in state initialized so we do not
1556            # expect an exception, but the owner might be different
1557            if not invalidate_accesscode(pin,comment,self.context.student_id):
1558                self.flash('You are not the owner of this access code.')
1559                return
1560            self.context.clr_code = pin
1561        IWorkflowInfo(self.context).fireTransition('start_clearance')
1562        self.flash('Clearance process has been started.')
1563        self.redirect(self.url(self.context,'cedit'))
1564        return
1565
1566class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
1567    """ View to edit student clearance data by student
1568    """
1569    grok.context(IStudent)
1570    grok.name('cedit')
1571    grok.require('waeup.handleStudent')
1572    form_fields = grok.AutoFields(
1573        IStudentClearance).omit('clearance_locked')
1574    label = 'Edit clearance data'
1575    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
1576
1577    def update(self):
1578        if self.context.clearance_locked:
1579            emit_lock_message(self)
1580            return
1581        return super(StudentClearanceEditFormPage, self).update()
1582
1583    @action('Save', style='primary')
1584    def save(self, **data):
1585        self.applyData(self.context, **data)
1586        self.flash('Clearance form has been saved.')
1587        return
1588
1589    # To be specified in the customisation package
1590    def dataNotComplete(self):
1591        #store = getUtility(IExtFileStore)
1592        #if not store.getFileByContext(self.context, attr=u'xyz.jpg'):
1593        #    return 'No xyz scan uploaded.'
1594        return False
1595
1596    @action('Save and request clearance', style='primary')
1597    def requestClearance(self, **data):
1598        self.applyData(self.context, **data)
1599        #self.context._p_changed = True
1600        if self.dataNotComplete():
1601            self.flash(self.dataNotComplete())
1602            return
1603        self.flash('Clearance form has been saved.')
1604        self.redirect(self.url(self.context,'request_clearance'))
1605        return
1606
1607class RequestClearancePage(SIRPPage):
1608    grok.context(IStudent)
1609    grok.name('request_clearance')
1610    grok.require('waeup.handleStudent')
1611    grok.template('enterpin')
1612    label = 'Request clearance'
1613    notice = 'Enter the CLR access code used for starting clearance.'
1614    ac_prefix = 'CLR'
1615    pnav = 4
1616    buttonname = 'Request clearance now'
1617
1618    def update(self, SUBMIT=None):
1619        self.ac_series = self.request.form.get('ac_series', None)
1620        self.ac_number = self.request.form.get('ac_number', None)
1621        if SUBMIT is None:
1622            return
1623        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1624        if self.context.clr_code != pin:
1625            self.flash("This isn't your CLR access code.")
1626            return
1627        state = IWorkflowState(self.context).getState()
1628        # This shouldn't happen, but the application officer
1629        # might have forgotten to lock the form after changing the state
1630        if state != CLEARANCE:
1631            self.flash('This form cannot be submitted. Wrong state!')
1632            return
1633        IWorkflowInfo(self.context).fireTransition('request_clearance')
1634        self.flash('Clearance has been requested.')
1635        self.redirect(self.url(self.context))
1636        return
1637
1638class StartCourseRegistrationPage(SIRPPage):
1639    grok.context(IStudentStudyCourse)
1640    grok.name('start_course_registration')
1641    grok.require('waeup.handleStudent')
1642    grok.template('enterpin')
1643    label = 'Start course registration'
1644    ac_prefix = 'SFE'
1645    notice = ''
1646    pnav = 4
1647    buttonname = 'Start course registration now'
1648
1649    def update(self, SUBMIT=None):
1650        if not self.context.getStudent().state in (CLEARED,RETURNING):
1651            self.flash("Wrong state.")
1652            self.redirect(self.url(self.context))
1653            return
1654        self.ac_series = self.request.form.get('ac_series', None)
1655        self.ac_number = self.request.form.get('ac_number', None)
1656
1657        if SUBMIT is None:
1658            return
1659        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1660        code = get_access_code(pin)
1661        if not code:
1662            self.flash('Activation code is invalid.')
1663            return
1664        # Mark pin as used (this also fires a pin related transition)
1665        # and fire transition start_clearance
1666        if code.state == USED:
1667            self.flash('Activation code has already been used.')
1668            return
1669        else:
1670            comment = u"AC invalidated for %s" % self.context.getStudent().student_id
1671            # Here we know that the ac is in state initialized so we do not
1672            # expect an exception, but the owner might be different
1673            if not invalidate_accesscode(
1674                pin,comment,self.context.getStudent().student_id):
1675                self.flash('You are not the owner of this access code.')
1676                return
1677        if self.context.getStudent().state == CLEARED:
1678            IWorkflowInfo(self.context.getStudent()).fireTransition(
1679                'pay_first_school_fee')
1680        elif self.context.getStudent().state == RETURNING:
1681            IWorkflowInfo(self.context.getStudent()).fireTransition(
1682                'pay_school_fee')
1683        self.flash('Course registration has been started.')
1684        self.redirect(self.url(self.context))
1685        return
1686
1687class AddStudyLevelFormPage(SIRPEditFormPage):
1688    """ Page for students to add current study levels
1689    """
1690    grok.context(IStudentStudyCourse)
1691    grok.name('add')
1692    grok.require('waeup.handleStudent')
1693    grok.template('studyleveladdpage')
1694    form_fields = grok.AutoFields(IStudentStudyCourse)
1695    pnav = 4
1696
1697    @property
1698    def label(self):
1699        studylevelsource = StudyLevelSource().factory
1700        code = self.context.current_level
1701        title = studylevelsource.getTitle(self.context, code)
1702        return 'Add current level %s' % title
1703
1704    def update(self):
1705        if self.context.getStudent().state != PAID:
1706            emit_lock_message(self)
1707            return
1708        super(AddStudyLevelFormPage, self).update()
1709        return
1710
1711    @action('Create course list now', style='primary')
1712    def addStudyLevel(self, **data):
1713        studylevel = StudentStudyLevel()
1714        studylevel.level = self.context.current_level
1715        studylevel.level_session = self.context.current_session
1716        try:
1717            self.context.addStudentStudyLevel(
1718                self.context.certificate,studylevel)
1719        except KeyError:
1720            self.flash('This level exists.')
1721        self.redirect(self.url(self.context))
1722        return
1723
1724class StudyLevelEditFormPage(SIRPEditFormPage):
1725    """ Page to edit the student study level data by students
1726    """
1727    grok.context(IStudentStudyLevel)
1728    grok.name('edit')
1729    grok.require('waeup.handleStudent')
1730    grok.template('studyleveleditpage')
1731    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1732        'level_session', 'level_verdict')
1733    pnav = 4
1734
1735    def update(self):
1736        if self.context.getStudent().state != PAID:
1737            emit_lock_message(self)
1738            return
1739        super(StudyLevelEditFormPage, self).update()
1740        datatable.need()
1741        warning.need()
1742        return
1743
1744    @property
1745    def label(self):
1746        return 'Add and remove course tickets of study level %s' % self.context.level_title
1747
1748    @property
1749    def total_credits(self):
1750        total_credits = 0
1751        for key, val in self.context.items():
1752            total_credits += val.credits
1753        return total_credits
1754
1755    @action('Add course ticket')
1756    def addCourseTicket(self, **data):
1757        self.redirect(self.url(self.context, 'ctadd'))
1758
1759    @jsaction('Remove selected tickets')
1760    def delCourseTicket(self, **data):
1761        form = self.request.form
1762        if form.has_key('val_id'):
1763            child_id = form['val_id']
1764        else:
1765            self.flash('No ticket selected.')
1766            self.redirect(self.url(self.context, '@@edit'))
1767            return
1768        if not isinstance(child_id, list):
1769            child_id = [child_id]
1770        deleted = []
1771        for id in child_id:
1772            # Students are not allowed to remove core tickets
1773            if not self.context[id].core_or_elective:
1774                try:
1775                    del self.context[id]
1776                    deleted.append(id)
1777                except:
1778                    self.flash('Could not delete %s: %s: %s' % (
1779                            id, sys.exc_info()[0], sys.exc_info()[1]))
1780        if len(deleted):
1781            self.flash('Successfully removed: %s' % ', '.join(deleted))
1782        self.redirect(self.url(self.context, u'@@edit'))
1783        return
1784
1785    @action('Register course list', style='primary')
1786    def RegisterCourses(self, **data):
1787        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1788        self.flash('Course list has been registered.')
1789        self.redirect(self.url(self.context))
1790        return
1791
1792class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1793    """Add a course ticket by student.
1794    """
1795    grok.name('ctadd')
1796    grok.require('waeup.handleStudent')
1797    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1798        'grade', 'score', 'core_or_elective', 'automatic')
1799
1800    def update(self):
1801        if self.context.getStudent().state != PAID:
1802            emit_lock_message(self)
1803            return
1804        super(CourseTicketAddFormPage2, self).update()
1805        return
1806
1807    @action('Add course ticket')
1808    def addCourseTicket(self, **data):
1809        # Double bottom ...
1810        if self.context.getStudent().state != PAID:
1811            return
1812        ticket = CourseTicket()
1813        course = data['course']
1814        ticket.automatic = False
1815        ticket.code = course.code
1816        ticket.title = course.title
1817        ticket.faculty = course.__parent__.__parent__.__parent__.title
1818        ticket.department = course.__parent__.__parent__.title
1819        ticket.credits = course.credits
1820        ticket.passmark = course.passmark
1821        ticket.semester = course.semester
1822        try:
1823            self.context.addCourseTicket(ticket)
1824        except KeyError:
1825            self.flash('The ticket exists.')
1826            return
1827        self.flash('Successfully added %s.' % ticket.code)
1828        self.redirect(self.url(self.context, u'@@edit'))
1829        return
1830
1831class ChangePasswordRequestPage(SIRPForm):
1832    """Captcha'd page for students to request a password change.
1833    """
1834    grok.context(IUniversity)
1835    grok.name('changepw')
1836    grok.require('waeup.Anonymous')
1837    grok.template('changepw')
1838    label = 'Change my password'
1839    form_fields = grok.AutoFields(IStudentChangePassword)
1840
1841    def update(self):
1842        # Handle captcha
1843        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1844        self.captcha_result = self.captcha.verify(self.request)
1845        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1846        return
1847
1848    @action('Get new login credentials', style='primary')
1849    def request(self, **data):
1850        if not self.captcha_result.is_valid:
1851            # Captcha will display error messages automatically.
1852            # No need to flash something.
1853            return
1854        # Search student
1855        cat = queryUtility(ICatalog, name='students_catalog')
1856        reg_number = data['reg_number']
1857        email = data['email']
1858        results = cat.searchResults(
1859            reg_number=(reg_number, reg_number),
1860            email=(email,email))
1861        if len(results) == 0:
1862            self.flash('No student record found.')
1863            return
1864        student = list(results)[0]
1865        # Change password
1866        sirp_utils = getUtility(ISIRPUtils)
1867        pwd = sirp_utils.genPassword()
1868        IUserAccount(student).setPassword(pwd)
1869        # Send email with new redentials
1870        msg = 'You have successfully changed your password for the'
1871        login_url = self.url(grok.getSite(), 'login')
1872        success = sirp_utils.sendCredentials(
1873            IUserAccount(student),pwd,login_url,msg)
1874        if success:
1875            self.flash('An email with your user name and password ' +
1876                'has been sent to %s.' % email)
1877        else:
1878            self.flash('An smtp server error occurred.')
1879        return
Note: See TracBrowser for help on using the repository browser.