source: main/waeup.uniben/trunk/src/waeup/uniben/students/browser.py @ 14902

Last change on this file since 14902 was 14902, checked in by Henrik Bettermann, 7 years ago

Add ´is_student´ property attribute to student objects.

  • Property svn:keywords set to Id
File size: 23.6 KB
Line 
1## $Id: browser.py 14902 2017-11-17 16:12:06Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19from zope.i18n import translate
20from zope.security import checkPermission
21from zope.schema.interfaces import ConstraintNotSatisfied
22from zope.formlib.textwidgets import BytesDisplayWidget
23from zope.component import getUtility
24from hurry.workflow.interfaces import IWorkflowInfo
25from waeup.kofa.interfaces import (
26    REQUESTED, IExtFileStore, IKofaUtils, IObjectHistory)
27from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
28from waeup.kofa.browser.layout import action, KofaEditFormPage, UtilityView
29from waeup.kofa.students.browser import (
30    StudyLevelEditFormPage, StudyLevelDisplayFormPage,
31    StudentBasePDFFormPage, ExportPDFCourseRegistrationSlip,
32    CourseTicketDisplayFormPage, StudentTriggerTransitionFormPage,
33    msave, emit_lock_message,
34    StudentActivateView, StudentDeactivateView,
35    ExportPDFTranscriptSlip,
36    PaymentsManageFormPage)
37from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID,
38    CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED,
39    GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS)
40from waeup.kofa.students.interfaces import IStudentsUtils, ICourseTicket
41from waeup.kofa.students.workflow import FORBIDDEN_POSTGRAD_TRANS
42from kofacustom.nigeria.students.browser import (
43    NigeriaOnlinePaymentDisplayFormPage,
44    NigeriaStudentBaseManageFormPage,
45    NigeriaStudentClearanceEditFormPage,
46    NigeriaOnlinePaymentAddFormPage,
47    NigeriaExportPDFPaymentSlip,
48    NigeriaExportPDFClearanceSlip,
49    NigeriaExportPDFBedTicketSlip,
50    NigeriaStudentPersonalDisplayFormPage,
51    NigeriaStudentPersonalManageFormPage,
52    NigeriaStudentPersonalEditFormPage
53    )
54
55from waeup.uniben.students.interfaces import (
56    ICustomStudent,
57    ICustomStudentOnlinePayment,
58    ICustomStudentStudyCourse,
59    ICustomStudentStudyLevel,
60    ICustomUGStudentClearance,
61    ICustomPGStudentClearance,
62    ICustomStudentPersonal,
63    ICustomStudentPersonalEdit)
64from waeup.uniben.interfaces import MessageFactory as _
65
66class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
67    """ Page to view an online payment ticket
68    """
69    grok.context(ICustomStudentOnlinePayment)
70    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
71        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
72    form_fields[
73        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
74    form_fields[
75        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
76
77class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
78    """ View to edit student clearance data by student
79    """
80
81    @property
82    def form_fields(self):
83        if self.context.is_postgrad:
84            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
85            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
86            'physical_clearance_date')
87        else:
88            form_fields = grok.AutoFields(ICustomUGStudentClearance).omit(
89            'clearance_locked', 'clr_code', 'officer_comment',
90            'physical_clearance_date')
91            form_fields['date_of_birth'].for_display = True
92            form_fields['nationality'].for_display = True
93            form_fields['lga'].for_display = True
94        return form_fields
95
96    def dataNotComplete(self):
97        store = getUtility(IExtFileStore)
98        if not store.getFileByContext(self.context, attr=u'birth_certificate.jpg'):
99            return _('No birth certificate uploaded.')
100        if not store.getFileByContext(self.context, attr=u'ref_let.jpg'):
101            return _('No guarantor/referee letter uploaded.')
102        if not store.getFileByContext(self.context, attr=u'acc_let.jpg'):
103            return _('No acceptance letter uploaded.')
104        if not store.getFileByContext(self.context, attr=u'fst_sit_scan.jpg'):
105            return _('No first sitting result uploaded.')
106        #if not store.getFileByContext(self.context, attr=u'scd_sit_scan.jpg'):
107        #    return _('No second sitting result uploaded.')
108        if not store.getFileByContext(self.context, attr=u'secr_cults.jpg'):
109            return _('No affidavit of non-membership of secret cults uploaded.')
110        return False
111
112class CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
113    """ Page to add an online payment ticket
114    """
115    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
116        'p_category')
117
118class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
119    """Deliver a PDF slip of the context.
120    """
121    grok.context(ICustomStudentOnlinePayment)
122    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
123        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
124    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
125    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
126
127
128class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
129    """Deliver a PDF slip of the context.
130    """
131
132    @property
133    def omit_fields(self):
134        omit_fields = ('password', 'suspended', 'suspended_comment',
135                       'phone', 'adm_code', 'email', 'date_of_birth',
136                       'flash_notice')
137        if self.context.is_jupeb:
138            omit_fields += ('faculty', 'department')
139        return omit_fields
140
141    @property
142    def label(self):
143        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
144        line0 = ''
145        if self.context.is_jupeb:
146            line0 = 'Joint Universities Preliminary Examinations Board (JUPEB)\n'
147        line1 = translate(_('Clearance Slip of'),
148            'waeup.kofa', target_language=portal_language) \
149            + ' %s' % self.context.display_fullname
150        return '%s%s' % (line0, line1)
151
152    def _sigsInFooter(self):
153        isStudent = getattr(
154            self.request.principal, 'user_type', None) == 'student'
155        if not isStudent and self.context.state in (CLEARED, RETURNING):
156            return (_('Date, Student Signature'),
157                    _('Date, Clearance Officer Signature'),
158                    )
159        return ()
160
161    def render(self):
162        studentview = StudentBasePDFFormPage(self.context.student,
163            self.request, self.omit_fields)
164        students_utils = getUtility(IStudentsUtils)
165        return students_utils.renderPDF(
166            self, 'clearance_slip.pdf',
167            self.context.student, studentview, signatures=self._signatures(),
168            sigs_in_footer=self._sigsInFooter(), show_scans=False,
169            omit_fields=self.omit_fields)
170
171
172class ExportClearanceInvitationSlip(UtilityView, grok.View):
173    """Deliver an invitation letter to physical clearance.
174
175    This form page is available only in Uniben.
176    """
177    grok.context(ICustomStudent)
178    grok.name('clearance_invitation_slip.pdf')
179    grok.require('waeup.viewStudent')
180    prefix = 'form'
181
182    label = u'Invitation Letter for Physical Clearance'
183
184    omit_fields = (
185        'suspended', 'phone', 'email',
186        'adm_code', 'suspended_comment',
187        'date_of_birth', 'current_level',
188        'department', 'current_mode',
189        'entry_session', 'matric_number', 'sex',
190        'flash_notice')
191
192    form_fields = []
193
194    @property
195    def note(self):
196        if self.context.physical_clearance_date:
197            return """
198<br /><br /><br /><br /><font size='12'>
199Dear %s,
200<br /><br /><br />
201You are invited for your physical clearance on:
202<br /><br />
203<strong>%s</strong>.
204<br /><br />
205Please bring along this letter of invitation to the University Main Auditorium
206<br /><br />
207on your clearance date.
208<br /><br /><br />
209Signed,
210<br /><br />
211The Registrar<br />
212</font>
213
214""" % (self.context.display_fullname, self.context.physical_clearance_date)
215        return
216
217
218    def update(self):
219        if self.context.student.state != REQUESTED \
220            or not  self.context.student.physical_clearance_date:
221            self.flash(_('Forbidden'), type="warning")
222            self.redirect(self.url(self.context))
223
224    def render(self):
225        studentview = StudentBasePDFFormPage(self.context.student,
226            self.request, self.omit_fields)
227        students_utils = getUtility(IStudentsUtils)
228        return students_utils.renderPDF(
229            self, 'clearance_invitation_slip',
230            self.context.student, studentview,
231            omit_fields=self.omit_fields,
232            note=self.note)
233
234class ExportExaminationScheduleSlip(UtilityView, grok.View):
235    """Deliver a examination schedule slip.
236
237    This form page is available only in Uniben and AAUE.
238    """
239    grok.context(ICustomStudent)
240    grok.name('examination_schedule_slip.pdf')
241    grok.require('waeup.viewStudent')
242    prefix = 'form'
243
244    label = u'Examination Schedule Slip'
245
246    omit_fields = (
247        'suspended', 'phone', 'email',
248        'adm_code', 'suspended_comment',
249        'date_of_birth', 'current_level',
250        'current_mode',
251        'entry_session',
252        'flash_notice')
253
254    form_fields = []
255
256    @property
257    def note(self):
258        return """
259<br /><br /><br /><br /><font size='12'>
260Your examination date, time and venue is scheduled as follows:
261<br /><br />
262<strong>%s</strong>
263</font>
264
265""" % self.context.flash_notice
266        return
267
268
269    def update(self):
270        if not self.context.flash_notice \
271            or not 'exam' in self.context.flash_notice.lower():
272            self.flash(_('Forbidden'), type="warning")
273            self.redirect(self.url(self.context))
274
275    def render(self):
276        studentview = StudentBasePDFFormPage(self.context.student,
277            self.request, self.omit_fields)
278        students_utils = getUtility(IStudentsUtils)
279        return students_utils.renderPDF(
280            self, 'examination_schedule_slip',
281            self.context.student, studentview,
282            omit_fields=self.omit_fields,
283            note=self.note)
284
285class ExportJUPEBResultSlip(ExportExaminationScheduleSlip):
286    """Deliver a JUPEB result slip.
287
288    This form page is available only in Uniben.
289    """
290
291    grok.name('jupeb_result_slip.pdf')
292    label = u'JUPEB Result Slip'
293
294    @property
295    def note(self):
296        return """
297<br /><br /><br /><br /><font size='12'>
298<strong>%s</strong>
299</font>
300<br /><br /><br /><br />
301<font size='8'>
302Key: A = 5, B = 4, C = 3, D = 2, E = 1, F = 0, X = Absent, Q = Cancelled
303</font>
304
305""" % self.context.flash_notice
306        return
307
308    def update(self):
309        if not self.context.flash_notice or not self.context.is_jupeb \
310            or not 'results' in self.context.flash_notice.lower():
311            self.flash(_('Forbidden'), type="warning")
312            self.redirect(self.url(self.context))
313
314class CustomStudentPersonalDisplayFormPage(
315    NigeriaStudentPersonalDisplayFormPage):
316    """ Page to display student personal data
317    """
318
319    form_fields = grok.AutoFields(ICustomStudentPersonal)
320    form_fields['perm_address'].custom_widget = BytesDisplayWidget
321    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
322    form_fields[
323        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
324
325class CustomStudentPersonalManageFormPage(
326    NigeriaStudentPersonalManageFormPage):
327    """ Page to manage personal data
328    """
329
330    form_fields = grok.AutoFields(ICustomStudentPersonal)
331    form_fields['personal_updated'].for_display = True
332    form_fields[
333        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
334
335class CstomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
336    """ Page to edit personal data
337    """
338    form_fields = grok.AutoFields(
339        ICustomStudentPersonalEdit).omit('personal_updated')
340
341class StudyCourseCOEditFormPage(KofaEditFormPage):
342    """ Page to edit the student study course data by clearance officers.
343
344    This form page is available only in Uniben.
345    """
346    grok.context(ICustomStudentStudyCourse)
347    grok.name('edit_level')
348    grok.require('waeup.clearStudent')
349    label = _('Edit current level')
350    pnav = 4
351    form_fields = grok.AutoFields(
352        ICustomStudentStudyCourse).select('current_level')
353
354    def update(self):
355        if not (self.context.is_current and
356            self.context.student.state == REQUESTED):
357            emit_lock_message(self)
358            return
359        super(StudyCourseCOEditFormPage, self).update()
360        return
361
362    @action(_('Save'), style='primary')
363    def save(self, **data):
364        try:
365            msave(self, **data)
366        except ConstraintNotSatisfied:
367            # The selected level might not exist in certificate
368            self.flash(_('Current level not available for certificate.'))
369            return
370        #notify(grok.ObjectModifiedEvent(self.context.__parent__))
371        return
372
373class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
374    """ Page to edit the student study level data by students.
375
376    """
377    grok.template('studyleveleditpage')
378
379class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
380    """ Page to display student study levels
381    """
382    grok.template('studylevelpage')
383
384class CustomExportPDFCourseRegistrationSlip(
385    ExportPDFCourseRegistrationSlip):
386    """Deliver a PDF slip of the context.
387    """
388
389    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
390        'level_verdict', 'gpa', 'level')
391
392    def update(self):
393        if self.context.student.state != REGISTERED \
394            or self.context.student.current_level != self.context.level:
395            self.flash(_('Forbidden'), type="warning")
396            self.redirect(self.url(self.context))
397
398    @property
399    def tabletitle(self):
400        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
401        tabletitle = []
402        tabletitle.append(translate(_('1st Semester Courses'), 'waeup.kofa',
403            target_language=portal_language))
404        tabletitle.append(translate(_('2nd Semester Courses'), 'waeup.kofa',
405            target_language=portal_language))
406        tabletitle.append(translate(_('Level Courses'), 'waeup.kofa',
407            target_language=portal_language))
408        tabletitle.append(translate(_('1st Trimester Courses'), 'waeup.kofa',
409            target_language=portal_language))
410        tabletitle.append(translate(_('2nd Trimester Courses'), 'waeup.kofa',
411            target_language=portal_language))
412        tabletitle.append(translate(_('3rd Trimester Courses'), 'waeup.kofa',
413            target_language=portal_language))
414        return tabletitle
415
416    def render(self):
417        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
418        Code = translate('Code', 'waeup.kofa', target_language=portal_language)
419        Title = translate('Title', 'waeup.kofa', target_language=portal_language)
420        Dept = translate('Dept.', 'waeup.kofa', target_language=portal_language)
421        Faculty = translate('Faculty', 'waeup.kofa', target_language=portal_language)
422        Cred = translate(_('Credits'), 'waeup.uniben', target_language=portal_language)
423        studentview = StudentBasePDFFormPage(self.context.student,
424            self.request, self.omit_fields)
425        students_utils = getUtility(IStudentsUtils)
426
427        tabledata = []
428        tableheader = []
429        for i in range(1,7):
430            tabledata.append(sorted(
431                [value for value in self.context.values() if value.semester == i],
432                key=lambda value: str(value.semester) + value.code))
433            tableheader.append([(Code,'code', 2.5),
434                             (Title,'title', 5),
435                             (Dept,'dcode', 1.5), (Faculty,'fcode', 1.5),
436                             (Cred, 'credits', 1.5),
437                             ])
438        return students_utils.renderPDF(
439            self, 'course_registration_slip.pdf',
440            self.context.student, studentview,
441            tableheader=tableheader,
442            tabledata=tabledata,
443            omit_fields=self.omit_fields
444            )
445
446class UnibenExportPDFCourseResultSlip(ExportPDFCourseRegistrationSlip):
447    """Deliver a PDF slip of the context.
448    """
449
450    grok.name('course_result_slip.pdf')
451
452    @property
453    def tabletitle(self):
454        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
455        tabletitle = []
456        tabletitle.append(translate(_('1st Semester Courses'), 'waeup.kofa',
457            target_language=portal_language))
458        tabletitle.append(translate(_('2nd Semester Courses'), 'waeup.kofa',
459            target_language=portal_language))
460        tabletitle.append(translate(_('Level Courses'), 'waeup.kofa',
461            target_language=portal_language))
462        tabletitle.append(translate(_('1st Trimester Courses'), 'waeup.kofa',
463            target_language=portal_language))
464        tabletitle.append(translate(_('2nd Trimester Courses'), 'waeup.kofa',
465            target_language=portal_language))
466        tabletitle.append(translate(_('3rd Trimester Courses'), 'waeup.kofa',
467            target_language=portal_language))
468        return tabletitle
469
470    @property
471    def label(self):
472        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
473        lang = self.request.cookies.get('kofa.language', portal_language)
474        level_title = translate(self.context.level_title, 'waeup.kofa',
475            target_language=lang)
476        return translate(_('Course Result Slip'),
477            'waeup.uniben', target_language=portal_language) \
478            + ' %s' % level_title
479
480    def render(self):
481        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
482        Code = translate('Code', 'waeup.kofa', target_language=portal_language)
483        Title = translate('Title', 'waeup.kofa', target_language=portal_language)
484        Dept = translate('Dept.', 'waeup.kofa', target_language=portal_language)
485        Faculty = translate('Faculty', 'waeup.kofa', target_language=portal_language)
486        Cred = translate(_('Credits'), 'waeup.uniben', target_language=portal_language)
487        Grade = translate('Grade', 'waeup.kofa', target_language=portal_language)
488        studentview = StudentBasePDFFormPage(self.context.student,
489            self.request, self.omit_fields)
490        students_utils = getUtility(IStudentsUtils)
491
492        tabledata = []
493        tableheader = []
494        for i in range(1,7):
495            tabledata.append(sorted(
496                [value for value in self.context.values() if value.semester == i],
497                key=lambda value: str(value.semester) + value.code))
498            tableheader.append([(Code,'code', 2.5),
499                             (Title,'title', 5),
500                             (Dept,'dcode', 1.5), (Faculty,'fcode', 1.5),
501                             (Cred, 'credits', 1.5),
502                             (Grade, 'grade', 1.5),
503                             ])
504        return students_utils.renderPDF(
505            self, 'course_registration_slip.pdf',
506            self.context.student, studentview,
507            tableheader=tableheader,
508            tabledata=tabledata,
509            omit_fields=self.omit_fields
510            )
511
512class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
513    """ Page to display course tickets
514    """
515    form_fields = grok.AutoFields(ICourseTicket).omit('score')
516
517class CustomStudentActivateView(StudentActivateView):
518    """ Activate student account
519    """
520
521    def update(self):
522        self.context.suspended = False
523        self.context.writeLogMessage(self, 'account activated')
524        history = IObjectHistory(self.context)
525        history.addMessage('Student account activated', user='undisclosed')
526        self.flash(_('Student account has been activated.'))
527        self.redirect(self.url(self.context))
528        return
529
530class CustomStudentDeactivateView(StudentDeactivateView):
531    """ Deactivate student account
532    """
533    def update(self):
534        self.context.suspended = True
535        self.context.writeLogMessage(self, 'account deactivated')
536        history = IObjectHistory(self.context)
537        history.addMessage('Student account deactivated', user='undisclosed')
538        self.flash(_('Student account has been deactivated.'))
539        self.redirect(self.url(self.context))
540        return
541
542class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
543    """Deliver a PDF slip of the context.
544    """
545
546    def _sigsInFooter(self):
547        isStudent = getattr(
548            self.request.principal, 'user_type', None) == 'student'
549        if not isStudent:
550            return (_('D. R. (Exams & Records)'),_('Current Dean of Faculty'),)
551        return ()
552
553    #def _signatures(self):
554    #    return ([(
555    #        'Current HD<br /> D. R. (Exams & Records)<br /> '
556    #        'For: Registrar')],)
557
558    def render(self):
559        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
560        Term = translate(_('Term'), 'waeup.kofa', target_language=portal_language)
561        Code = translate(_('Code'), 'waeup.kofa', target_language=portal_language)
562        Title = translate(_('Title'), 'waeup.kofa', target_language=portal_language)
563        Cred = translate(_('Credits'), 'waeup.kofa', target_language=portal_language)
564        #Score = translate(_('Score'), 'waeup.kofa', target_language=portal_language)
565        Grade = translate(_('Grade'), 'waeup.kofa', target_language=portal_language)
566        studentview = StudentBasePDFFormPage(self.context.student,
567            self.request, self.omit_fields)
568        students_utils = getUtility(IStudentsUtils)
569
570        tableheader = [(Code,'code', 2.5),
571                         (Title,'title', 8.5),
572                         (Term, 'semester', 1.5),
573                         (Cred, 'credits', 1.5),
574                         #(Score, 'score', 1.5),
575                         (Grade, 'grade', 1.5),
576                         ]
577
578        return students_utils.renderPDFTranscript(
579            self, 'transcript.pdf',
580            self.context.student, studentview,
581            omit_fields=self.omit_fields,
582            tableheader=tableheader,
583            signatures=self._signatures(),
584            sigs_in_footer=self._sigsInFooter(),
585            )
586
587class CustomExportPDFBedTicketSlip(NigeriaExportPDFBedTicketSlip):
588    """Deliver a PDF slip of the context.
589    """
590
591    omit_fields = ('password', 'suspended', 'suspended_comment',
592        'phone', 'adm_code', 'email', 'date_of_birth', 'flash_notice')
593
594    def render(self):
595        studentview = StudentBasePDFFormPage(self.context.student,
596            self.request, self.omit_fields)
597        students_utils = getUtility(IStudentsUtils)
598
599        note = """
600<br /><br /><br /><br /><br /><font size="14">
601Please endeavour to pay your hostel maintenance charge within <br /><br />
6024 days of being allocated a space or else you are deemed to have <br /><br />
603voluntarily forfeited it and it goes back into circulation to be <br /><br />
604available for booking afresh!</font>
605"""
606
607        return students_utils.renderPDF(
608            self, 'bed_allocation_slip.pdf',
609            self.context.student, studentview,
610            omit_fields=self.omit_fields,
611            note=note)
612
613class CustomPaymentsManageFormPage(PaymentsManageFormPage):
614    """ Page to manage the student payments. This manage form page is for
615    both students and students officers. Uniben does not allow students
616    to remove any payment ticket.
617    """
618    @property
619    def manage_payments_allowed(self):
620        return checkPermission('waeup.manageStudent', self.context)
Note: See TracBrowser for help on using the repository browser.