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

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

This my recent version of the createStudent method.

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