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

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

Add first version of ExportPDFClearanceSlipPage.

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