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

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

The StudentApplication? class is deprecated. We want to store the application_slip pdf file file instead.

Prepare everything in the students package for downloading such a pdf file.

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