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

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

Use BytesDisplayWidget? for permanent address field.

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