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

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

Add ReindexPaymentsPage?. This view can be called through the URL and works perfectly for all kinds of catalogs.

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