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

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

Replace student's name attribute by fullname attributes to avoid confusion.

  • Property svn:keywords set to Id
File size: 39.0 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 grok
19
20from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
21from zope.component import createObject
22from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
23from waeup.sirp.accesscodes.workflow import USED
24from waeup.sirp.browser import (
25    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
26from waeup.sirp.browser.breadcrumbs import Breadcrumb
27from waeup.sirp.browser.resources import datepicker, datatable, tabs
28from waeup.sirp.browser.viewlets import (
29    ManageActionButton, PrimaryNavTab, AddActionButton)
30from waeup.sirp.interfaces import IWAeUPObject, IUserAccount
31from waeup.sirp.widgets.datewidget import (
32    FriendlyDateWidget, FriendlyDateDisplayWidget)
33from waeup.sirp.students.interfaces import (
34    IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting,
35    IStudentPersonal, IStudentBase, IStudentStudyCourse, IStudentPayments,
36    IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel,
37    ICourseTicket, ICourseTicketAdd,
38    )
39from waeup.sirp.students.catalog import search
40from waeup.sirp.students.workflow import CLEARANCE
41from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket
42from waeup.sirp.students.vocabularies import StudyLevelSource
43
44# Save function used for save methods in manager pages
45def msave(view, **data):
46    form = view.request.form
47    ob_class = view.__implemented__.__name__.replace('waeup.sirp.','')
48    changed_fields = view.applyData(view.context, **data)
49    # Turn list of lists into single list
50    if changed_fields:
51        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
52    fields_string = ' + '.join(changed_fields)
53    view.context._p_changed = True
54    view.flash('Form has been saved.')
55    if fields_string:
56        view.context.getStudent().loggerInfo(ob_class, 'saved: % s' % fields_string)
57    return
58
59class StudentsTab(PrimaryNavTab):
60    """Students tab in primary navigation.
61    """
62
63    grok.context(IWAeUPObject)
64    grok.order(3)
65    grok.require('waeup.viewStudent')
66    grok.template('primarynavtab')
67
68    pnav = 4
69    tab_title = u'Students'
70
71    @property
72    def link_target(self):
73        return self.view.application_url('students')
74
75class StudentsBreadcrumb(Breadcrumb):
76    """A breadcrumb for the students container.
77    """
78    grok.context(IStudentsContainer)
79    title = u'Students'
80
81class StudentBreadcrumb(Breadcrumb):
82    """A breadcrumb for the student container.
83    """
84    grok.context(IStudent)
85
86    def title(self):
87        return self.context.fullname
88
89class SudyCourseBreadcrumb(Breadcrumb):
90    """A breadcrumb for the student study course.
91    """
92    grok.context(IStudentStudyCourse)
93    title = u'Study Course'
94
95class PaymentsBreadcrumb(Breadcrumb):
96    """A breadcrumb for the student payments folder.
97    """
98    grok.context(IStudentPayments)
99    title = u'Payments'
100
101class AccommodationBreadcrumb(Breadcrumb):
102    """A breadcrumb for the student accommodation folder.
103    """
104    grok.context(IStudentAccommodation)
105    title = u'Accommodation'
106
107class StudyLevelBreadcrumb(Breadcrumb):
108    """A breadcrumb for course lists.
109    """
110    grok.context(IStudentStudyLevel)
111
112    @property
113    def title(self):
114        return self.context.level_title
115
116class StudentsContainerPage(WAeUPPage):
117    """The standard view for student containers.
118    """
119    grok.context(IStudentsContainer)
120    grok.name('index')
121    grok.require('waeup.viewStudent')
122    grok.template('containerpage')
123    label = 'Student Section'
124    title = 'Students'
125    pnav = 4
126
127    def update(self, *args, **kw):
128        datatable.need()
129        form = self.request.form
130        self.hitlist = []
131        if 'searchterm' in form and form['searchterm']:
132            self.searchterm = form['searchterm']
133            self.searchtype = form['searchtype']
134        elif 'old_searchterm' in form:
135            self.searchterm = form['old_searchterm']
136            self.searchtype = form['old_searchtype']
137        else:
138            if 'search' in form:
139                self.flash('Empty search string.')
140            return
141        self.hitlist = search(query=self.searchterm,
142            searchtype=self.searchtype, view=self)
143        if not self.hitlist:
144            self.flash('No student found.')
145        return
146
147class SetPasswordPage(WAeUPPage):
148    grok.context(IWAeUPObject)
149    grok.name('setpassword')
150    grok.require('waeup.Public')
151    grok.template('setpassword')
152    title = ''
153    label = 'Set password for first-time login'
154    ac_prefix = 'PWD'
155    pnav = 0
156
157    def update(self, SUBMIT=None):
158        self.reg_number = self.request.form.get('reg_number', None)
159        self.ac_series = self.request.form.get('ac_series', None)
160        self.ac_number = self.request.form.get('ac_number', None)
161
162        if SUBMIT is None:
163            return
164        hitlist = search(query=self.reg_number,
165            searchtype='reg_number', view=self)
166        if not hitlist:
167            self.flash('No student found.')
168            return
169        if len(hitlist) != 1:   # Cannot happen but anyway
170            self.flash('More than one student found.')
171            return
172        student = hitlist[0].context
173        self.student_id = student.student_id
174        student_pw = student.password
175        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
176        code = get_access_code(pin)
177        if not code:
178            self.flash('Access code is invalid.')
179            return
180        if student_pw and pin == student.adm_code:
181            self.flash('Password has already been set. Your Student Id is %s'
182                % self.student_id)
183            return
184        elif student_pw:
185            self.flash('Password has already been set. You are using the wrong Access Code.')
186            return
187        # Mark pin as used (this also fires a pin related transition)
188        # and set student password
189        if code.state == USED:
190            self.flash('Access code has already been used.')
191            return
192        else:
193            comment = u"AC invalidated for %s" % self.student_id
194            # Here we know that the ac is in state initialized so we do not
195            # expect an exception
196            #import pdb; pdb.set_trace()
197            invalidate_accesscode(pin,comment)
198            IUserAccount(student).setPassword(self.ac_number)
199            student.adm_code = pin
200        self.flash('Password has been set. Your Student Id is %s'
201            % self.student_id)
202        return
203
204class StudentsContainerManageActionButton(ManageActionButton):
205    grok.order(1)
206    grok.context(IStudentsContainer)
207    grok.view(StudentsContainerPage)
208    grok.require('waeup.manageStudents')
209    text = 'Manage student section'
210
211
212class StudentsContainerManagePage(WAeUPPage):
213    """The manage page for student containers.
214    """
215    grok.context(IStudentsContainer)
216    grok.name('manage')
217    grok.require('waeup.manageStudents')
218    grok.template('containermanagepage')
219    pnav = 4
220    title = 'Manage student section'
221
222    @property
223    def label(self):
224        return self.title
225
226    def update(self, *args, **kw):
227        datatable.need()
228        form = self.request.form
229        self.hitlist = []
230        if 'searchterm' in form and form['searchterm']:
231            self.searchterm = form['searchterm']
232            self.searchtype = form['searchtype']
233        elif 'old_searchterm' in form:
234            self.searchterm = form['old_searchterm']
235            self.searchtype = form['old_searchtype']
236        else:
237            if 'search' in form:
238                self.flash('Empty search string.')
239            return
240        if not 'entries' in form:
241            self.hitlist = search(query=self.searchterm,
242                searchtype=self.searchtype, view=self)
243            if not self.hitlist:
244                self.flash('No student found.')
245            return
246        entries = form['entries']
247        if isinstance(entries, basestring):
248            entries = [entries]
249        deleted = []
250        for entry in entries:
251            if 'remove' in form:
252                del self.context[entry]
253                deleted.append(entry)
254        self.hitlist = search(query=self.searchterm,
255            searchtype=self.searchtype, view=self)
256        if len(deleted):
257            self.flash('Successfully removed: %s' % ', '.join(deleted))
258        return
259
260class StudentsContainerAddActionButton(AddActionButton):
261    grok.order(1)
262    grok.context(IStudentsContainer)
263    grok.view(StudentsContainerManagePage)
264    grok.require('waeup.manageStudents')
265    text = 'Add student'
266    target = 'addstudent'
267
268class StudentAddFormPage(WAeUPAddFormPage):
269    """Add-form to add a student.
270    """
271    grok.context(IStudentsContainer)
272    grok.require('waeup.manageStudents')
273    grok.name('addstudent')
274    grok.template('studentaddpage')
275    form_fields = grok.AutoFields(IStudent)
276    title = 'Students'
277    label = 'Add student'
278    pnav = 4
279
280    @grok.action('Create student record')
281    def addStudent(self, **data):
282        student = createObject(u'waeup.Student')
283        self.applyData(student, **data)
284        self.context.addStudent(student)
285        self.flash('Student record created.')
286        self.redirect(self.url(self.context[student.student_id], 'index'))
287        return
288
289class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
290    """ Page to display student base data
291    """
292    grok.context(IStudent)
293    grok.name('index')
294    grok.require('waeup.viewStudent')
295    grok.template('basepage')
296    form_fields = grok.AutoFields(IStudentBase).omit('password')
297    pnav = 4
298    title = 'Base Data'
299
300    @property
301    def label(self):
302        return '%s: Base Data' % self.context.fullname
303
304    @property
305    def hasPassword(self):
306        if self.context.password:
307            return 'set'
308        return 'unset'
309
310class StudentBaseManageActionButton(ManageActionButton):
311    grok.order(1)
312    grok.context(IStudent)
313    grok.view(StudentBaseDisplayFormPage)
314    grok.require('waeup.manageStudents')
315    text = 'Manage'
316    target = 'edit_base'
317
318class StudentBaseManageFormPage(WAeUPEditFormPage):
319    """ View to edit student base data
320    """
321    grok.context(IStudent)
322    grok.name('edit_base')
323    grok.require('waeup.manageStudents')
324    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
325    grok.template('basemanagepage')
326    label = 'Manage base data'
327    title = 'Base Data'
328    pnav = 4
329
330    def update(self):
331        datepicker.need() # Enable jQuery datepicker in date fields.
332        super(StudentBaseManageFormPage, self).update()
333        self.wf_info = IWorkflowInfo(self.context)
334        return
335
336    def getTransitions(self):
337        """Return a list of dicts of allowed transition ids and titles.
338
339        Each list entry provides keys ``name`` and ``title`` for
340        internal name and (human readable) title of a single
341        transition.
342        """
343        allowed_transitions = self.wf_info.getManualTransitions()
344        return [dict(name='', title='No transition')] +[
345            dict(name=x, title=y) for x, y in allowed_transitions]
346
347    @grok.action('Save')
348    def save(self, **data):
349        form = self.request.form
350        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
351        password = form.get('password', None)
352        password_ctl = form.get('control_password', None)
353        if password:
354            if (password != password_ctl):
355                self.flash('Passwords do not match.')
356            else:
357                # XXX: This is too early. PW should only be saved if there
358                #      are no (other) errors left in form.
359                IUserAccount(self.context).setPassword(password)
360                self.context.loggerInfo(ob_class, 'password changed')
361
362        #self.reg_number = form.get('form.reg_number', None)
363        #if self.reg_number:
364        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
365        #    if hitlist and hitlist[0].student_id != self.context.student_id:
366        #        self.flash('Registration number exists.')
367        #        return
368        #self.matric_number = form.get('form.matric_number', None)
369        #if self.matric_number:
370        #    hitlist = search(query=self.matric_number,
371        #        searchtype='matric_number', view=self)
372        #    if hitlist and hitlist[0].student_id != self.context.student_id:
373        #        self.flash('Matriculation number exists.')
374        #        return
375
376        # Turn list of lists into single list
377        changed_fields = self.applyData(self.context, **data)
378        if changed_fields:
379            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
380        fields_string = ' + '.join(changed_fields)
381        self.context._p_changed = True
382        if form.has_key('transition') and form['transition']:
383            transition_id = form['transition']
384            self.wf_info.fireTransition(transition_id)
385        self.flash('Form has been saved.')
386        if fields_string:
387            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
388        return
389
390class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
391    """ Page to display student clearance data
392    """
393    grok.context(IStudent)
394    grok.name('view_clearance')
395    grok.require('waeup.viewStudent')
396    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
397    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
398    title = 'Clearance Data'
399    pnav = 4
400
401    @property
402    def label(self):
403        return '%s: Clearance Data' % self.context.fullname
404
405class StudentClearanceManageActionButton(ManageActionButton):
406    grok.order(1)
407    grok.context(IStudent)
408    grok.view(StudentClearanceDisplayFormPage)
409    grok.require('waeup.manageStudents')
410    text = 'Manage'
411    target = 'edit_clearance'
412
413class StudentClearanceManageFormPage(WAeUPEditFormPage):
414    """ Page to edit student clearance data
415    """
416    grok.context(IStudent)
417    grok.name('edit_clearance')
418    grok.require('waeup.manageStudents')
419    form_fields = grok.AutoFields(IStudentClearance)
420    label = 'Manage clearance data'
421    title = 'Clearance Data'
422    pnav = 4
423
424    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
425
426    def update(self):
427        datepicker.need() # Enable jQuery datepicker in date fields.
428        return super(StudentClearanceManageFormPage, self).update()
429
430    @grok.action('Save')
431    def save(self, **data):
432        msave(self, **data)
433        return
434
435class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
436    """ Page to display student personal data
437    """
438    grok.context(IStudent)
439    grok.name('view_personal')
440    grok.require('waeup.viewStudent')
441    form_fields = grok.AutoFields(IStudentPersonal)
442    title = 'Personal Data'
443    pnav = 4
444
445    @property
446    def label(self):
447        return '%s: Personal Data' % self.context.fullname
448
449class StudentPersonalManageActionButton(ManageActionButton):
450    grok.order(1)
451    grok.context(IStudent)
452    grok.view(StudentPersonalDisplayFormPage)
453    grok.require('waeup.manageStudents')
454    text = 'Manage'
455    target = 'edit_personal'
456
457class StudentPersonalManageFormPage(WAeUPEditFormPage):
458    """ Page to edit student clearance data
459    """
460    grok.context(IStudent)
461    grok.name('edit_personal')
462    grok.require('waeup.viewStudent')
463    form_fields = grok.AutoFields(IStudentPersonal)
464    label = 'Manage personal data'
465    title = 'Personal Data'
466    pnav = 4
467
468    @grok.action('Save')
469    def save(self, **data):
470        msave(self, **data)
471        return
472
473class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
474    """ Page to display the student study course data
475    """
476    grok.context(IStudentStudyCourse)
477    grok.name('index')
478    grok.require('waeup.viewStudent')
479    form_fields = grok.AutoFields(IStudentStudyCourse)
480    grok.template('studycoursepage')
481    title = 'Study Course'
482    pnav = 4
483
484    @property
485    def label(self):
486        return '%s: Study Course' % self.context.__parent__.fullname
487
488class StudyCourseManageActionButton(ManageActionButton):
489    grok.order(1)
490    grok.context(IStudentStudyCourse)
491    grok.view(StudyCourseDisplayFormPage)
492    grok.require('waeup.manageStudents')
493    text = 'Manage'
494    target = 'manage'
495
496class StudyCourseManageFormPage(WAeUPEditFormPage):
497    """ Page to edit the student study course data
498    """
499    grok.context(IStudentStudyCourse)
500    grok.name('manage')
501    grok.require('waeup.manageStudents')
502    grok.template('studycoursemanagepage')
503    form_fields = grok.AutoFields(IStudentStudyCourse)
504    title = 'Study Course'
505    label = 'Manage study course'
506    pnav = 4
507    taboneactions = ['Save','Cancel']
508    tabtwoactions = ['Remove selected levels','Cancel']
509    tabthreeactions = ['Add study level']
510
511    def update(self):
512        super(StudyCourseManageFormPage, self).update()
513        tabs.need()
514        datatable.need()
515        return
516
517    @grok.action('Save')
518    def save(self, **data):
519        msave(self, **data)
520        return
521
522    @property
523    def level_dict(self):
524        studylevelsource = StudyLevelSource().factory
525        for code in studylevelsource.getValues(self.context):
526            title = studylevelsource.getTitle(self.context, code)
527            yield(dict(code=code, title=title))
528
529    @grok.action('Add study level')
530    def addStudyLevel(self, **data):
531        level_code = self.request.form.get('addlevel', None)
532        studylevel = StudentStudyLevel()
533        studylevel.level = int(level_code)
534        try:
535            self.context.addStudentStudyLevel(
536                self.context.certificate,studylevel)
537        except KeyError:
538            self.flash('This level exists.')
539        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
540        return
541
542    @grok.action('Remove selected levels')
543    def delStudyLevels(self, **data):
544        form = self.request.form
545        if form.has_key('val_id'):
546            child_id = form['val_id']
547        else:
548            self.flash('No study level selected.')
549            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
550            return
551        if not isinstance(child_id, list):
552            child_id = [child_id]
553        deleted = []
554        for id in child_id:
555            try:
556                del self.context[id]
557                deleted.append(id)
558            except:
559                self.flash('Could not delete %s: %s: %s' % (
560                        id, sys.exc_info()[0], sys.exc_info()[1]))
561        if len(deleted):
562            self.flash('Successfully removed: %s' % ', '.join(deleted))
563        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
564        return
565
566class StudyLevelDisplayFormPage(WAeUPDisplayFormPage):
567    """ Page to display student study levels
568    """
569    grok.context(IStudentStudyLevel)
570    grok.name('index')
571    grok.require('waeup.viewStudent')
572    form_fields = grok.AutoFields(IStudentStudyLevel)
573    grok.template('studylevelpage')
574    pnav = 4
575
576    @property
577    def title(self):
578        return 'Study Level %s' % self.context.level_title
579
580    @property
581    def label(self):
582        return '%s: Study Level %s' % (
583            self.context.getStudent().fullname,self.context.level_title)
584
585    @property
586    def total_credits(self):
587        total_credits = 0
588        for key, val in self.context.items():
589            total_credits += val.credits
590        return total_credits
591
592class StudyLevelManageActionButton(ManageActionButton):
593    grok.order(1)
594    grok.context(IStudentStudyLevel)
595    grok.view(StudyLevelDisplayFormPage)
596    grok.require('waeup.manageStudents')
597    text = 'Manage'
598    target = 'manage'
599
600class StudyLevelManageFormPage(WAeUPEditFormPage):
601    """ Page to edit the student study level data
602    """
603    grok.context(IStudentStudyLevel)
604    grok.name('manage')
605    grok.require('waeup.manageStudents')
606    grok.template('studylevelmanagepage')
607    form_fields = grok.AutoFields(IStudentStudyLevel)
608    pnav = 4
609    taboneactions = ['Save','Cancel']
610    tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel']
611
612    def update(self):
613        super(StudyLevelManageFormPage, self).update()
614        tabs.need()
615        datatable.need()
616        return
617
618    @property
619    def title(self):
620        return 'Study Level %s' % self.context.level_title
621
622    @property
623    def label(self):
624        return 'Manage study level %s' % self.context.level_title
625
626    @grok.action('Save')
627    def save(self, **data):
628        msave(self, **data)
629        return
630
631    @grok.action('Add course ticket')
632    def addCourseTicket(self, **data):
633        self.redirect(self.url(self.context, '@@add'))
634
635    @grok.action('Remove selected tickets')
636    def delCourseTicket(self, **data):
637        form = self.request.form
638        if form.has_key('val_id'):
639            child_id = form['val_id']
640        else:
641            self.flash('No ticket selected.')
642            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
643            return
644        if not isinstance(child_id, list):
645            child_id = [child_id]
646        deleted = []
647        for id in child_id:
648            try:
649                del self.context[id]
650                deleted.append(id)
651            except:
652                self.flash('Could not delete %s: %s: %s' % (
653                        id, sys.exc_info()[0], sys.exc_info()[1]))
654        if len(deleted):
655            self.flash('Successfully removed: %s' % ', '.join(deleted))
656        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
657        return
658
659class CourseTicketAddFormPage(WAeUPAddFormPage):
660    """Add a course ticket.
661    """
662    grok.context(IStudentStudyLevel)
663    grok.name('add')
664    grok.require('waeup.manageStudents')
665    label = 'Add course ticket'
666    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
667        'grade', 'score', 'automatic')
668    pnav = 4
669
670    @property
671    def title(self):
672        return 'Study Level %s' % self.context.level_title
673
674    @grok.action('Add course ticket')
675    def addCourseTicket(self, **data):
676        ticket = CourseTicket()
677        course = data['course']
678        ticket.core_or_elective = data['core_or_elective']
679        ticket.automatic = False
680        ticket.code = course.code
681        ticket.title = course.title
682        ticket.faculty = course.__parent__.__parent__.__parent__.title
683        ticket.department = course.__parent__.__parent__.title
684        ticket.credits = course.credits
685        ticket.passmark = course.passmark
686        ticket.semester = course.semester
687        try:
688            self.context.addCourseTicket(ticket)
689        except KeyError:
690            self.flash('The ticket exists.')
691            return
692        self.flash('Successfully added %s.' % ticket.code)
693        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
694        return
695
696    @grok.action('Cancel')
697    def cancel(self, **data):
698        self.redirect(self.url(self.context))
699
700class CourseTicketDisplayFormPage(WAeUPDisplayFormPage):
701    """ Page to display course tickets
702    """
703    grok.context(ICourseTicket)
704    grok.name('index')
705    grok.require('waeup.viewStudent')
706    form_fields = grok.AutoFields(ICourseTicket)
707    grok.template('courseticketpage')
708    pnav = 4
709
710    @property
711    def title(self):
712        return 'Course Ticket %s' % self.context.code
713
714    @property
715    def label(self):
716        return '%s: Course Ticket %s' % (
717            self.context.getStudent().fullname,self.context.code)
718
719class CourseTicketManageActionButton(ManageActionButton):
720    grok.order(1)
721    grok.context(ICourseTicket)
722    grok.view(CourseTicketDisplayFormPage)
723    grok.require('waeup.manageStudents')
724    text = 'Manage'
725    target = 'manage'
726
727class CourseTicketManageFormPage(WAeUPEditFormPage):
728    """ Page to manage course tickets
729    """
730    grok.context(ICourseTicket)
731    grok.name('manage')
732    grok.require('waeup.manageStudents')
733    form_fields = grok.AutoFields(ICourseTicket)
734    grok.template('courseticketmanagepage')
735    pnav = 4
736
737    @property
738    def title(self):
739        return 'Course Ticket %s' % self.context.code
740
741    @property
742    def label(self):
743        return 'Manage course ticket %s' % self.context.code
744
745    @grok.action('Save')
746    def save(self, **data):
747        msave(self, **data)
748        return
749
750class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
751    """ Page to display the student payments
752    """
753    grok.context(IStudentPayments)
754    grok.name('index')
755    grok.require('waeup.viewStudent')
756    form_fields = grok.AutoFields(IStudentPayments)
757    #grok.template('paymentspage')
758    title = 'Payments'
759    pnav = 4
760
761    @property
762    def label(self):
763        return '%s: Payments' % self.context.__parent__.fullname
764
765class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
766    """ Page to display the student accommodation data
767    """
768    grok.context(IStudentAccommodation)
769    grok.name('index')
770    grok.require('waeup.viewStudent')
771    form_fields = grok.AutoFields(IStudentAccommodation)
772    #grok.template('accommodationpage')
773    title = 'Accommodation'
774    pnav = 4
775
776    @property
777    def label(self):
778        return '%s: Accommodation Data' % self.context.__parent__.fullname
779
780class StudentHistoryPage(WAeUPPage):
781    """ Page to display student clearance data
782    """
783    grok.context(IStudent)
784    grok.name('history')
785    grok.require('waeup.viewStudent')
786    grok.template('studenthistory')
787    title = 'History'
788    pnav = 4
789
790    @property
791    def label(self):
792        return '%s: History' % self.context.fullname
793
794# Pages for students only
795
796class StudentBaseEditActionButton(ManageActionButton):
797    grok.order(1)
798    grok.context(IStudent)
799    grok.view(StudentBaseDisplayFormPage)
800    grok.require('waeup.handleStudent')
801    text = 'Change password'
802    target = 'bedit'
803
804class StudentPasswordSetting(grok.Adapter):
805    """Adapt IStudent to data needed for password settings.
806
807    We provide password getters/setters for the attached context (an
808    IStudent object) that cooperate seamless with the usual
809    formlib/form techniques.
810    """
811    grok.context(IStudent)
812    grok.provides(IStudentPasswordSetting)
813
814    def __init__(self, context):
815        self.name = context.fullname
816        self.password_repeat = context.password
817        self.context = context
818        return
819
820    def getPassword(self):
821        return self.context.password
822
823    def setPassword(self, password):
824        IUserAccount(self.context).setPassword(password)
825        return
826
827    password = property(getPassword, setPassword)
828
829class StudentBaseEditFormPage(WAeUPEditFormPage):
830    """ View to edit student base data by student
831    """
832    grok.context(IStudent)
833    grok.name('bedit')
834    grok.require('waeup.handleStudent')
835    #form_fields = grok.AutoFields(IStudentBaseEdit).omit(
836    #    'student_id', 'reg_number', 'matric_number')
837    form_fields = grok.AutoFields(IStudentPasswordSetting)
838    grok.template('baseeditpage')
839    label = 'Change password'
840    title = 'Base Data'
841    pnav = 4
842
843    def update(self):
844        super(StudentBaseEditFormPage, self).update()
845        self.wf_info = IWorkflowInfo(self.context)
846        return
847
848    def onFailure(self, action, data, errors):
849        new_status = []
850        other_errors = False
851        for error in errors:
852            msg = getattr(error, 'message', '')
853            if isinstance(msg, basestring) and msg != '':
854                new_status.append(msg)
855            else:
856                other_errors = True
857        if other_errors:
858            if new_status:
859                new_status.append('see below for further errors')
860            else:
861                new_status.append('See below for details.')
862        if new_status:
863            self.status = u'There were errors: %s' % ', '.join(new_status)
864        return
865
866    @grok.action('Save', failure=onFailure)
867    def save(self, **data):
868        self.applyData(self.context, **data)
869        self.flash('Form has been saved.')
870        return
871
872class StudentClearanceStartActionButton(ManageActionButton):
873    grok.order(1)
874    grok.context(IStudent)
875    grok.view(StudentClearanceDisplayFormPage)
876    grok.require('waeup.handleStudent')
877    icon = 'actionicon_start.png'
878    text = 'Start clearance'
879    target = 'start_clearance'
880
881    @property
882    def target_url(self):
883        if self.context.state != 'admitted':
884            return ''
885        return self.view.url(self.view.context, self.target)
886
887class StartClearancePage(WAeUPPage):
888    grok.context(IStudent)
889    grok.name('start_clearance')
890    grok.require('waeup.handleStudent')
891    grok.template('enterpin')
892    title = 'Start clearance'
893    label = 'Start clearance'
894    ac_prefix = 'CLR'
895    notice = ''
896    pnav = 4
897    buttonname = 'Start clearance now'
898
899    def update(self, SUBMIT=None):
900        self.ac_series = self.request.form.get('ac_series', None)
901        self.ac_number = self.request.form.get('ac_number', None)
902
903        if SUBMIT is None:
904            return
905        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
906        code = get_access_code(pin)
907        if not code:
908            self.flash('Access code is invalid.')
909            return
910        # Mark pin as used (this also fires a pin related transition)
911        # and fire transition start_clearance
912        if code.state == USED:
913            self.flash('Access code has already been used.')
914            return
915        else:
916            comment = u"AC invalidated for %s" % self.context.student_id
917            # Here we know that the ac is in state initialized so we do not
918            # expect an exception
919            invalidate_accesscode(pin,comment)
920            self.context.clr_code = pin
921        IWorkflowInfo(self.context).fireTransition('start_clearance')
922        self.flash('Clearance process has been started.')
923        self.redirect(self.url(self.context,'cedit'))
924        return
925
926class StudentClearanceEditActionButton(ManageActionButton):
927    grok.order(1)
928    grok.context(IStudent)
929    grok.view(StudentClearanceDisplayFormPage)
930    grok.require('waeup.handleStudent')
931    text = 'Edit'
932    target = 'cedit'
933
934    @property
935    def target_url(self):
936        if self.context.clearance_locked:
937            return ''
938        return self.view.url(self.view.context, self.target)
939
940class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
941    """ View to edit student clearance data by student
942    """
943    grok.context(IStudent)
944    grok.name('cedit')
945    grok.require('waeup.handleStudent')
946    form_fields = grok.AutoFields(
947        IStudentClearanceEdit).omit('clearance_locked')
948    label = 'Edit clearance data'
949    title = 'Clearance Data'
950    pnav = 4
951    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
952
953    def emitLockMessage(self):
954        self.flash('The requested form is locked (read-only).')
955        self.redirect(self.url(self.context))
956        return
957
958    def update(self):
959        if self.context.clearance_locked:
960            self.emitLockMessage()
961            return
962        datepicker.need()
963        return super(StudentClearanceEditFormPage, self).update()
964
965    @grok.action('Save')
966    def save(self, **data):
967        self.applyData(self.context, **data)
968        self.flash('Clearance form has been saved.')
969        return
970
971    @grok.action('Save and request clearance')
972    def requestclearance(self, **data):
973        self.applyData(self.context, **data)
974        self.context._p_changed = True
975        #if self.dataNotComplete():
976        #    self.flash(self.dataNotComplete())
977        #    return
978        self.flash('Clearance form has been saved.')
979        self.redirect(self.url(self.context,'request_clearance'))
980        return
981
982class RequestClearancePage(WAeUPPage):
983    grok.context(IStudent)
984    grok.name('request_clearance')
985    grok.require('waeup.handleStudent')
986    grok.template('enterpin')
987    title = 'Request clearance'
988    label = 'Request clearance'
989    notice = 'Enter the CLR access code used for starting clearance.'
990    ac_prefix = 'CLR'
991    pnav = 4
992    buttonname = 'Request clearance now'
993
994    def update(self, SUBMIT=None):
995        self.ac_series = self.request.form.get('ac_series', None)
996        self.ac_number = self.request.form.get('ac_number', None)
997        if SUBMIT is None:
998            return
999        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
1000        if self.context.clr_code != pin:
1001            self.flash("This isn't your CLR access code.")
1002            return
1003        state = IWorkflowState(self.context).getState()
1004        # This shouldn't happen, but the application officer
1005        # might have forgotten to lock the form after changing the state
1006        if state != CLEARANCE:
1007            self.flash('This form cannot be submitted. Wrong state!')
1008            return
1009        IWorkflowInfo(self.context).fireTransition('request_clearance')
1010        self.flash('Clearance has been requested.')
1011        self.redirect(self.url(self.context))
1012        return
1013
1014class AddStudyLevelActionButton(AddActionButton):
1015    grok.order(1)
1016    grok.context(IStudentStudyCourse)
1017    grok.view(StudyCourseDisplayFormPage)
1018    grok.require('waeup.handleStudent')
1019    text = 'Add course list'
1020    target = 'add'
1021
1022    @property
1023    def target_url(self):
1024        student = self.view.context.getStudent()
1025        condition1 = student.state != 'school fee paid'
1026        condition2 = str(student['studycourse'].current_level) in \
1027            self.view.context.keys()
1028        if condition1 or condition2:
1029            return ''
1030        return self.view.url(self.view.context, self.target)
1031
1032class AddStudyLevelFormPage(WAeUPEditFormPage):
1033    """ Page for students to add current study levels
1034    """
1035    grok.context(IStudentStudyCourse)
1036    grok.name('add')
1037    grok.require('waeup.handleStudent')
1038    grok.template('studyleveladdpage')
1039    form_fields = grok.AutoFields(IStudentStudyCourse)
1040    title = 'Study Course'
1041    pnav = 4
1042
1043    @property
1044    def label(self):
1045        studylevelsource = StudyLevelSource().factory
1046        code = self.context.current_level
1047        title = studylevelsource.getTitle(self.context, code)
1048        return 'Add current level %s' % title
1049
1050    def emitLockMessage(self):
1051        self.flash('The requested form is locked (read-only).')
1052        self.redirect(self.url(self.context))
1053        return
1054
1055    def update(self):
1056        if self.context.getStudent().state != 'school fee paid':
1057            self.emitLockMessage()
1058            return
1059        super(AddStudyLevelFormPage, self).update()
1060        return
1061
1062    @grok.action('Create course list now')
1063    def addStudyLevel(self, **data):
1064        studylevel = StudentStudyLevel()
1065        studylevel.level = self.context.current_level
1066        studylevel.level_session = self.context.current_session
1067        try:
1068            self.context.addStudentStudyLevel(
1069                self.context.certificate,studylevel)
1070        except KeyError:
1071            self.flash('This level exists.')
1072        self.redirect(self.url(self.context))
1073        return
1074
1075class StudyLevelEditActionButton(ManageActionButton):
1076    grok.order(1)
1077    grok.context(IStudentStudyLevel)
1078    grok.view(StudyLevelDisplayFormPage)
1079    grok.require('waeup.handleStudent')
1080    text = 'Add and remove courses'
1081    target = 'edit'
1082
1083    @property
1084    def target_url(self):
1085        student = self.view.context.getStudent()
1086        condition1 = student.state != 'school fee paid'
1087        condition2 = student[
1088            'studycourse'].current_level != self.view.context.level
1089        if condition1 or condition2:
1090            return ''
1091        return self.view.url(self.view.context, self.target)
1092
1093class StudyLevelEditFormPage(WAeUPEditFormPage):
1094    """ Page to edit the student study level data by students
1095    """
1096    grok.context(IStudentStudyLevel)
1097    grok.name('edit')
1098    grok.require('waeup.handleStudent')
1099    grok.template('studyleveleditpage')
1100    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
1101        'level_session', 'level_verdict')
1102    pnav = 4
1103
1104    def update(self):
1105        super(StudyLevelEditFormPage, self).update()
1106    #    tabs.need()
1107        datatable.need()
1108        return
1109
1110    @property
1111    def title(self):
1112        return 'Study Level %s' % self.context.level_title
1113
1114    @property
1115    def label(self):
1116        return 'Add and remove course tickets of study level %s' % self.context.level_title
1117
1118    @property
1119    def total_credits(self):
1120        total_credits = 0
1121        for key, val in self.context.items():
1122            total_credits += val.credits
1123        return total_credits
1124
1125    @grok.action('Add course ticket')
1126    def addCourseTicket(self, **data):
1127        self.redirect(self.url(self.context, 'ctadd'))
1128
1129    @grok.action('Remove selected tickets')
1130    def delCourseTicket(self, **data):
1131        form = self.request.form
1132        if form.has_key('val_id'):
1133            child_id = form['val_id']
1134        else:
1135            self.flash('No ticket selected.')
1136            self.redirect(self.url(self.context, '@@edit'))
1137            return
1138        if not isinstance(child_id, list):
1139            child_id = [child_id]
1140        deleted = []
1141        for id in child_id:
1142            # Student are not allowed to remove core tickets
1143            if not self.context[id].core_or_elective:
1144                try:
1145                    del self.context[id]
1146                    deleted.append(id)
1147                except:
1148                    self.flash('Could not delete %s: %s: %s' % (
1149                            id, sys.exc_info()[0], sys.exc_info()[1]))
1150        if len(deleted):
1151            self.flash('Successfully removed: %s' % ', '.join(deleted))
1152        self.redirect(self.url(self.context, u'@@edit'))
1153        return
1154
1155    @grok.action('Register course list')
1156    def register_courses(self, **data):
1157        state = IWorkflowState(self.context.getStudent()).getState()
1158        IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses')
1159        self.flash('Course list has been registered.')
1160        self.redirect(self.url(self.context))
1161        return
1162
1163class CourseTicketAddFormPage2(CourseTicketAddFormPage):
1164    """Add a course ticket by student.
1165    """
1166    grok.name('ctadd')
1167    grok.require('waeup.handleStudent')
1168    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
1169        'grade', 'score', 'core_or_elective', 'automatic')
1170
1171    @grok.action('Add course ticket')
1172    def addCourseTicket(self, **data):
1173        ticket = CourseTicket()
1174        course = data['course']
1175        ticket.automatic = False
1176        ticket.code = course.code
1177        ticket.title = course.title
1178        ticket.faculty = course.__parent__.__parent__.__parent__.title
1179        ticket.department = course.__parent__.__parent__.title
1180        ticket.credits = course.credits
1181        ticket.passmark = course.passmark
1182        ticket.semester = course.semester
1183        try:
1184            self.context.addCourseTicket(ticket)
1185        except KeyError:
1186            self.flash('The ticket exists.')
1187            return
1188        self.flash('Successfully added %s.' % ticket.code)
1189        self.redirect(self.url(self.context, u'@@edit'))
1190        return
Note: See TracBrowser for help on using the repository browser.