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

Last change on this file since 7146 was 7145, checked in by Henrik Bettermann, 14 years ago

Change some function names according to the style guide:

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

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