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

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

Page titles are deprecated. We only have labels.

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