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

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

Turn all functions subject to customization into methods of a global utility component called StudentsUtils?. This is really an incredible improvement and eases customization a lot.

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