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

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

Implement password control field.

  • Property svn:keywords set to Id
File size: 20.0 KB
RevLine 
[6621]1## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
2## This program is free software; you can redistribute it and/or modify
3## it under the terms of the GNU General Public License as published by
4## the Free Software Foundation; either version 2 of the License, or
5## (at your option) any later version.
6##
7## This program is distributed in the hope that it will be useful,
8## but WITHOUT ANY WARRANTY; without even the implied warranty of
9## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10## GNU General Public License for more details.
11##
12## You should have received a copy of the GNU General Public License
13## along with this program; if not, write to the Free Software
14## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15##
16"""UI components for students and related components.
17"""
18import sys
19import grok
20
21from datetime import datetime
22from zope.formlib.widget import CustomWidgetFactory
23from zope.formlib.form import setUpEditWidgets
24from zope.securitypolicy.interfaces import IPrincipalRoleManager
25from zope.traversing.browser import absoluteURL
[6622]26from zope.component import (
27    createObject,)
[6621]28
29from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
30from reportlab.pdfgen import canvas
31from reportlab.lib.units import cm
32from reportlab.lib.pagesizes import A4
33from reportlab.lib.styles import getSampleStyleSheet
34from reportlab.platypus import (Frame, Paragraph, Image,
35    Table, Spacer)
36from reportlab.platypus.tables import TableStyle
37
38from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
39from waeup.sirp.accesscodes.workflow import USED
40from waeup.sirp.browser import (
41    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
42from waeup.sirp.browser.breadcrumbs import Breadcrumb
43from waeup.sirp.browser.layout import NullValidator
44from waeup.sirp.browser.pages import add_local_role, del_local_roles
45from waeup.sirp.browser.resources import datepicker, tabs, datatable
46from waeup.sirp.browser.viewlets import (
[6642]47    ManageActionButton, PrimaryNavTab,
[6635]48    AddActionButton, ActionButton, PlainActionButton,
[6621]49    )
50from waeup.sirp.image.browser.widget import (
51    ThumbnailWidget, EncodingImageFileWidget,
52    )
53from waeup.sirp.image.image import createWAeUPImageFile
[6682]54from waeup.sirp.interfaces import (
55    IWAeUPObject, ILocalRolesAssignable, IUserAccount,
56    )
[6621]57from waeup.sirp.permissions import get_users_with_local_roles
58from waeup.sirp.university.interfaces import ICertificate
59from waeup.sirp.widgets.datewidget import (
60    FriendlyDateWidget, FriendlyDateDisplayWidget)
61from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
62from waeup.sirp.widgets.objectwidget import (
63    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
64from waeup.sirp.students.interfaces import (
[6631]65    IStudentsContainer, IStudent, IStudentClearance,
[6635]66    IStudentPersonal, IStudentBase, IStudentStudyCourse,
[6694]67    IStudentPayments, IStudentAccommodation, IStudentNavigation,
[6695]68    IStudentBaseEdit, IStudentClearanceEdit,
[6621]69    )
[6651]70from waeup.sirp.students.student import Student
[6626]71from waeup.sirp.students.catalog import search
[6699]72from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
73from waeup.sirp.accesscodes.workflow import USED
[6621]74
75class StudentsTab(PrimaryNavTab):
76    """Students tab in primary navigation.
77    """
78
79    grok.context(IWAeUPObject)
80    grok.order(3)
[6660]81    grok.require('waeup.viewStudent')
[6621]82    grok.template('primarynavtab')
83
84    pnav = 4
85    tab_title = u'Students'
86
87    @property
88    def link_target(self):
89        return self.view.application_url('students')
90
[6629]91class StudentsBreadcrumb(Breadcrumb):
92    """A breadcrumb for the students container.
93    """
94    grok.context(IStudentsContainer)
95    title = u'Students'
96
[6635]97class SudyCourseBreadcrumb(Breadcrumb):
98    """A breadcrumb for the student study course.
99    """
100    grok.context(IStudentStudyCourse)
101    title = u'Study Course'
102
103class PaymentsBreadcrumb(Breadcrumb):
104    """A breadcrumb for the student payments folder.
105    """
106    grok.context(IStudentPayments)
107    title = u'Payments'
108
109class AccommodationBreadcrumb(Breadcrumb):
110    """A breadcrumb for the student accommodation folder.
111    """
112    grok.context(IStudentAccommodation)
113    title = u'Accommodation'
114
[6626]115class StudentsContainerPage(WAeUPPage):
116    """The standard view for student containers.
[6621]117    """
118    grok.context(IStudentsContainer)
119    grok.name('index')
[6660]120    grok.require('waeup.viewStudent')
[6695]121    grok.template('containerpage')
[6654]122    label = 'Student Section'
123    title = 'Students'
[6642]124    pnav = 4
[6621]125
[6626]126    def update(self, *args, **kw):
127        datatable.need()
128        form = self.request.form
129        self.hitlist = []
130        if 'searchterm' in form and form['searchterm']:
131            self.searchterm = form['searchterm']
132            self.searchtype = form['searchtype']
133        elif 'old_searchterm' in form:
134            self.searchterm = form['old_searchterm']
135            self.searchtype = form['old_searchtype']
136        else:
137            if 'search' in form:
138                self.flash('Empty search string.')
139            return
140        self.hitlist = search(query=self.searchterm,
141            searchtype=self.searchtype, view=self)
142        if not self.hitlist:
143            self.flash('No student found.')
144        return
145
[6699]146class SetPassword(WAeUPPage):
147    grok.context(IWAeUPObject)
148    grok.name('setpassword')
149    grok.require('waeup.Public')
150    title = ''
151    label = 'Set password for first-time login'
152    acprefix = 'PWD'
153    pnav = 4
154
155    def update(self, SUBMIT=None):
156        self.reg_number = self.request.form.get('form.reg_number', None)
157        # We must not use form.ac_series and form.ac_number in forms since these
158        # are interpreted by applicant credentials
159        self.acseries = self.request.form.get('form.acseries', None)
160        self.acnumber = self.request.form.get('form.acnumber', 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        self.student_id = hitlist[0].student_id
173        student_pw = hitlist[0].context.password
174        if student_pw:
175            self.flash('Password has already been set. Please request a password reset.')
176            return
177        pin = '%s-%s-%s' % (self.acprefix,self.acseries,self.acnumber)
178        code = get_access_code(pin)
179        if not code:
180            self.flash('Access code is invalid.')
181            return
182        # Mark pin as used (this also fires a pin related transition)
183        # and set student password
184        if code.state == USED:
185            self.flash('Access code has already been used.')
186            return
187        else:
188            comment = u"AC invalidated"
189            # Here we know that the ac is in state initialized so we do not
190            # expect an exception
191            #import pdb; pdb.set_trace()
192            invalidate_accesscode(pin,comment)
193            IUserAccount(hitlist[0].context).setPassword(self.acnumber)
194        return
195
[6626]196class StudentsContainerManageActionButton(ManageActionButton):
[6622]197    grok.order(1)
198    grok.context(IStudentsContainer)
199    grok.view(StudentsContainerPage)
200    grok.require('waeup.manageStudents')
[6647]201    text = 'Manage student section'
[6622]202
[6626]203
204class StudentsContainerManagePage(WAeUPPage):
205    """The manage page for student containers.
[6622]206    """
207    grok.context(IStudentsContainer)
208    grok.name('manage')
209    grok.require('waeup.manageStudents')
[6695]210    grok.template('containermanagepage')
[6642]211    pnav = 4
[6647]212    title = 'Manage student section'
[6622]213
214    @property
215    def label(self):
216        return self.title
217
[6626]218    def update(self, *args, **kw):
219        datatable.need()
220        form = self.request.form
221        self.hitlist = []
222        if 'searchterm' in form and form['searchterm']:
223            self.searchterm = form['searchterm']
224            self.searchtype = form['searchtype']
225        elif 'old_searchterm' in form:
226            self.searchterm = form['old_searchterm']
227            self.searchtype = form['old_searchtype']
228        else:
229            if 'search' in form:
230                self.flash('Empty search string.')
231            return
232        if not 'entries' in form:
233            self.hitlist = search(query=self.searchterm,
234                searchtype=self.searchtype, view=self)
235            if not self.hitlist:
236                self.flash('No student found.')
237            return
238        entries = form['entries']
239        if isinstance(entries, basestring):
240            entries = [entries]
241        deleted = []
242        for entry in entries:
243            if 'remove' in form:
244                del self.context[entry]
245                deleted.append(entry)
246        self.hitlist = search(query=self.searchterm,
247            searchtype=self.searchtype, view=self)
248        if len(deleted):
249            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6622]250        return
251
[6626]252class StudentsContainerAddActionButton(AddActionButton):
253    grok.order(1)
254    grok.context(IStudentsContainer)
255    grok.view(StudentsContainerManagePage)
256    grok.require('waeup.manageStudents')
257    text = 'Add student'
258    target = 'addstudent'
259
[6622]260class StudentAddFormPage(WAeUPAddFormPage):
261    """Add-form to add a student.
262    """
263    grok.context(IStudentsContainer)
264    grok.require('waeup.manageStudents')
265    grok.name('addstudent')
266    grok.template('studentaddpage')
267    form_fields = grok.AutoFields(IStudent)
268    title = 'Students'
269    label = 'Add student'
[6642]270    pnav = 4
[6622]271
272    @grok.action('Create student record')
273    def addStudent(self, **data):
274        student = createObject(u'waeup.Student')
275        self.applyData(student, **data)
[6652]276        self.context.addStudent(student)
[6626]277        self.flash('Student record created.')
[6651]278        self.redirect(self.url(self.context[student.student_id], 'index'))
[6622]279        return
280
[6631]281class StudentBaseDisplayFormPage(WAeUPDisplayFormPage):
282    """ Page to display student base data
283    """
[6622]284    grok.context(IStudent)
285    grok.name('index')
[6660]286    grok.require('waeup.viewStudent')
[6695]287    grok.template('basepage')
[6699]288    form_fields = grok.AutoFields(IStudentBase)  #.omit('password')
[6642]289    pnav = 4
290    title = 'Base Data'
[6622]291
292    @property
293    def label(self):
[6631]294        return '%s: Base Data' % self.context.name
295
[6699]296    @property
297    def hasPassword(self):
298        if self.context.password:
299            return 'set'
300        return 'unset'
301
[6631]302class StudentBaseManageActionButton(ManageActionButton):
303    grok.order(1)
304    grok.context(IStudent)
305    grok.view(StudentBaseDisplayFormPage)
306    grok.require('waeup.manageStudents')
[6695]307    text = 'Manage'
[6631]308    target = 'edit_base'
309
310class StudentBaseManageFormPage(WAeUPEditFormPage):
311    """ View to edit student base data
312    """
313    grok.context(IStudent)
314    grok.name('edit_base')
315    grok.require('waeup.manageStudents')
316    form_fields = grok.AutoFields(IStudentBase).omit('student_id')
[6695]317    grok.template('basemanagepage')
318    label = 'Manage base data'
[6642]319    title = 'Base Data'
320    pnav = 4
[6631]321
[6638]322    def update(self):
323        datepicker.need() # Enable jQuery datepicker in date fields.
324        super(StudentBaseManageFormPage, self).update()
325        self.wf_info = IWorkflowInfo(self.context)
326        return
327
328    def getTransitions(self):
329        """Return a list of dicts of allowed transition ids and titles.
330
331        Each list entry provides keys ``name`` and ``title`` for
332        internal name and (human readable) title of a single
333        transition.
334        """
335        allowed_transitions = self.wf_info.getManualTransitions()
336        return [dict(name='', title='No transition')] +[
337            dict(name=x, title=y) for x, y in allowed_transitions]
338
339    @grok.action('Save')
340    def save(self, **data):
[6701]341        form = self.request.form
342        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
343        if form.has_key('password') and form['password']:
344            if form['password'] != form['control_password']:
345                self.flash('Passwords do not match.')
346                return
347            IUserAccount(self.context).setPassword(form['password'])
348            self.context.loggerInfo(ob_class, 'password changed')
[6638]349        changed_fields = self.applyData(self.context, **data)
350        changed_fields = changed_fields.values()
351        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
352        self.context._p_changed = True
353        if form.has_key('transition') and form['transition']:
354            transition_id = form['transition']
355            self.wf_info.fireTransition(transition_id)
356        self.flash('Form has been saved.')
[6644]357        if fields_string:
358            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
[6638]359        return
360
[6631]361class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage):
362    """ Page to display student clearance data
363    """
364    grok.context(IStudent)
365    grok.name('view_clearance')
[6660]366    grok.require('waeup.viewStudent')
[6695]367    form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked')
[6650]368    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[6642]369    title = 'Clearance Data'
370    pnav = 4
[6631]371
372    @property
373    def label(self):
374        return '%s: Clearance Data' % self.context.name
375
376class StudentClearanceManageActionButton(ManageActionButton):
377    grok.order(1)
378    grok.context(IStudent)
379    grok.view(StudentClearanceDisplayFormPage)
380    grok.require('waeup.manageStudents')
[6695]381    text = 'Manage'
[6631]382    target = 'edit_clearance'
383
384class StudentClearanceManageFormPage(WAeUPEditFormPage):
385    """ Page to edit student clearance data
386    """
387    grok.context(IStudent)
388    grok.name('edit_clearance')
[6649]389    grok.require('waeup.manageStudents')
[6631]390    form_fields = grok.AutoFields(IStudentClearance)
[6695]391    label = 'Manage clearance data'
[6642]392    title = 'Clearance Data'
393    pnav = 4
[6631]394
[6650]395    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
396
397    def update(self):
398        datepicker.need() # Enable jQuery datepicker in date fields.
399        return super(StudentClearanceManageFormPage, self).update()
400
[6695]401    @grok.action('Save')
402    def save(self, **data):
403        changed_fields = self.applyData(self.context, **data)
404        changed_fields = changed_fields.values()
405        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
406        self.context._p_changed = True
407        form = self.request.form
408        self.flash('Form has been saved.')
409        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
410        if fields_string:
411            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
412        return
413
[6631]414class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage):
415    """ Page to display student personal data
416    """
417    grok.context(IStudent)
418    grok.name('view_personal')
[6660]419    grok.require('waeup.viewStudent')
[6631]420    form_fields = grok.AutoFields(IStudentPersonal)
[6642]421    title = 'Personal Data'
422    pnav = 4
[6631]423
424    @property
425    def label(self):
426        return '%s: Personal Data' % self.context.name
427
428class StudentPersonalManageActionButton(ManageActionButton):
429    grok.order(1)
430    grok.context(IStudent)
431    grok.view(StudentPersonalDisplayFormPage)
432    grok.require('waeup.manageStudents')
[6695]433    text = 'Manage'
[6631]434    target = 'edit_personal'
435
436class StudentPersonalManageFormPage(WAeUPEditFormPage):
437    """ Page to edit student clearance data
438    """
439    grok.context(IStudent)
440    grok.name('edit_personal')
[6660]441    grok.require('waeup.viewStudent')
[6631]442    form_fields = grok.AutoFields(IStudentPersonal)
[6695]443    label = 'Manage personal data'
[6642]444    title = 'Personal Data'
445    pnav = 4
[6631]446
[6635]447class StudyCourseDisplayFormPage(WAeUPDisplayFormPage):
448    """ Page to display the student study course data
449    """
450    grok.context(IStudentStudyCourse)
451    grok.name('index')
[6660]452    grok.require('waeup.viewStudent')
[6635]453    form_fields = grok.AutoFields(IStudentStudyCourse)
454    #grok.template('studycoursepage')
[6642]455    title = 'Study Course'
456    pnav = 4
[6635]457
458    @property
459    def label(self):
[6642]460        return '%s: Study Course' % self.context.__parent__.name
[6635]461
[6649]462class StudyCourseManageActionButton(ManageActionButton):
463    grok.order(1)
464    grok.context(IStudentStudyCourse)
465    grok.view(StudyCourseDisplayFormPage)
466    grok.require('waeup.manageStudents')
[6695]467    text = 'Manage'
[6649]468    target = 'edit'
469
470class StudyCourseManageFormPage(WAeUPEditFormPage):
471    """ Page to edit the student study course data
472    """
473    grok.context(IStudentStudyCourse)
474    grok.name('edit')
475    grok.require('waeup.manageStudents')
476    form_fields = grok.AutoFields(IStudentStudyCourse)
477    title = 'Study Course'
[6695]478    label = 'Manage study course'
[6649]479    pnav = 4
480
[6635]481class PaymentsDisplayFormPage(WAeUPDisplayFormPage):
482    """ Page to display the student payments
483    """
484    grok.context(IStudentPayments)
485    grok.name('index')
[6660]486    grok.require('waeup.viewStudent')
[6635]487    form_fields = grok.AutoFields(IStudentPayments)
488    #grok.template('paymentspage')
[6642]489    title = 'Payments'
490    pnav = 4
[6635]491
492    @property
493    def label(self):
494        return '%s: Payments' % self.context.__parent__.name
495
496class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
497    """ Page to display the student accommodation data
498    """
499    grok.context(IStudentAccommodation)
500    grok.name('index')
[6660]501    grok.require('waeup.viewStudent')
[6635]502    form_fields = grok.AutoFields(IStudentAccommodation)
503    #grok.template('accommodationpage')
[6642]504    title = 'Accommodation'
505    pnav = 4
[6635]506
507    @property
508    def label(self):
509        return '%s: Accommodation Data' % self.context.__parent__.name
[6637]510
511class StudentHistoryPage(WAeUPPage):
512    """ Page to display student clearance data
513    """
514    grok.context(IStudent)
515    grok.name('history')
[6660]516    grok.require('waeup.viewStudent')
[6637]517    grok.template('studenthistory')
[6642]518    title = 'History'
519    pnav = 4
[6637]520
521    @property
522    def label(self):
523        return '%s: History' % self.context.name
[6694]524
525# Pages for students only
526
527class StudentBaseEditActionButton(ManageActionButton):
528    grok.order(1)
529    grok.context(IStudent)
530    grok.view(StudentBaseDisplayFormPage)
531    grok.require('waeup.handleStudent')
532    text = 'Change password'
533    target = 'bedit'
534
535class StudentBaseEditFormPage(WAeUPEditFormPage):
536    """ View to edit student base data by student
537    """
538    grok.context(IStudent)
539    grok.name('bedit')
540    grok.require('waeup.handleStudent')
[6696]541    form_fields = grok.AutoFields(IStudentBaseEdit).omit(
542        'student_id', 'reg_number')
[6695]543    grok.template('baseeditpage')
[6694]544    label = 'Change password'
545    title = 'Base Data'
546    pnav = 4
547
548    def update(self):
549        datepicker.need() # Enable jQuery datepicker in date fields.
550        super(StudentBaseEditFormPage, self).update()
551        self.wf_info = IWorkflowInfo(self.context)
552        return
553
554    @grok.action('Save')
555    def save(self, **data):
[6701]556        form = self.request.form
557        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
558        if form.has_key('password') and form['password']:
559            if form['password'] != form['control_password']:
560                self.flash('Passwords do not match.')
561                return
562            IUserAccount(self.context).setPassword(form['password'])
563            self.context.loggerInfo(ob_class, 'password changed')
[6694]564        changed_fields = self.applyData(self.context, **data)
565        changed_fields = changed_fields.values()
566        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
567        self.context._p_changed = True
568        self.flash('Form has been saved.')
569        if fields_string:
570            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
571        return
[6695]572
573class StudentClearanceEditActionButton(ManageActionButton):
574    grok.order(1)
575    grok.context(IStudent)
576    grok.view(StudentClearanceDisplayFormPage)
577    grok.require('waeup.handleStudent')
578    text = 'Edit and submit'
579    target = 'cedit'
580
581class StudentClearanceEditFormPage(StudentClearanceManageFormPage):
582    """ View to edit student clearance data by student
583    """
584    grok.context(IStudent)
585    grok.name('cedit')
586    grok.require('waeup.handleStudent')
587    form_fields = grok.AutoFields(IStudentClearanceEdit).omit('clearance_locked')
588    #grok.template('clearanceeditpage')
589    label = 'Edit clerance data'
590    title = 'Clearance Data'
591    pnav = 4
592    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
Note: See TracBrowser for help on using the repository browser.