source: main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py @ 15284

Last change on this file since 15284 was 15247, checked in by Henrik Bettermann, 6 years ago

Print note on slip.

  • Property svn:keywords set to Id
File size: 47.2 KB
RevLine 
[8911]1## $Id: browser.py 15247 2018-11-16 16:13:48Z 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
[13937]19import csv
[13964]20import textwrap
[14981]21import pytz
[13937]22from cStringIO import StringIO
[14981]23from datetime import datetime
[8911]24from zope.i18n import translate
[13900]25from zope.component import getUtility, queryUtility
[14113]26from zope.schema.interfaces import TooBig, TooSmall
[13523]27from zope.security import checkPermission
[13900]28from zope.catalog.interfaces import ICatalog
[13351]29from zope.formlib.textwidgets import BytesDisplayWidget
[14981]30from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[11597]31from waeup.kofa.browser.layout import UtilityView
[8911]32from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
[14288]33from waeup.kofa.interfaces import (
[14306]34    IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject)
35from waeup.kofa.students.interfaces import (
36    IStudentsUtils, IStudent, IStudentRequestPW)
[14000]37from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
[9914]38from waeup.kofa.students.browser import (
[11846]39    StartClearancePage,
[9914]40    StudentBasePDFFormPage,
41    CourseTicketAddFormPage,
42    StudyLevelDisplayFormPage,
[13834]43    StudyLevelManageFormPage,
44    StudyLevelEditFormPage,
[13059]45    ExportPDFTranscriptSlip,
46    ExportPDFAdmissionSlip,
[13353]47    BedTicketAddPage,
[13380]48    StudentFilesUploadPage,
[13523]49    PaymentsManageFormPage,
[13770]50    CourseTicketDisplayFormPage,
51    CourseTicketManageFormPage,
[13900]52    EditScoresPage,
[14165]53    ExportPDFScoresSlip,
54    StudyCourseTranscriptPage,
[14288]55    DownloadScoresView,
[14534]56    StudentRequestPasswordPage,
[14981]57    StudyCourseManageFormPage,
58    UnregisterCoursesView
[10269]59    )
[8911]60from kofacustom.nigeria.students.browser import (
61    NigeriaOnlinePaymentDisplayFormPage,
62    NigeriaOnlinePaymentAddFormPage,
[13059]63    NigeriaExportPDFPaymentSlip,
64    NigeriaExportPDFCourseRegistrationSlip,
[13351]65    NigeriaStudentPersonalDisplayFormPage,
66    NigeriaStudentPersonalEditFormPage,
67    NigeriaStudentPersonalManageFormPage,
[14084]68    NigeriaStudentClearanceDisplayFormPage,
69    NigeriaExportPDFClearanceSlip,
70    NigeriaStudentClearanceManageFormPage,
[13362]71    NigeriaStudentClearanceEditFormPage,
[13462]72    NigeriaAccommodationManageFormPage,
[13795]73    NigeriaStudentBaseDisplayFormPage,
[14165]74    NigeriaStudentBaseManageFormPage
[10269]75    )
[9496]76from waeup.aaue.students.interfaces import (
[9914]77    ICustomStudentOnlinePayment,
[11607]78    ICustomStudentStudyLevel,
[13351]79    ICustomStudent,
80    ICustomStudentPersonal,
[13362]81    ICustomStudentPersonalEdit,
[13770]82    ICustomUGStudentClearance,
[14298]83    ICustomUGStudentClearanceEdit,
[14084]84    ICustomPGStudentClearance,
[13795]85    ICustomCourseTicket,
[14534]86    ICustomStudentBase,
87    ICustomStudentStudyCourse)
[13414]88from waeup.aaue.interswitch.browser import gateway_net_amt
[9914]89from waeup.aaue.interfaces import MessageFactory as _
[8911]90
[14306]91grok.context(IKofaObject)  # Make IKofaObject the default context
92
[15228]93def translated_values(view):
94    """Translate course ticket attribute values to be displayed on
95    studylevel pages.
96    """
97    lang = view.request.cookies.get('kofa.language')
98    for value in view.context.values():
99        value._p_activate()
100        value_dict = dict([i for i in value.__dict__.items()])
101        value_dict['url'] = view.url(value)
102        value_dict['removable_by_student'] = value.removable_by_student
103        value_dict['mandatory'] = translate(str(value.mandatory), 'zope',
104            target_language=lang)
105        value_dict['carry_over'] = translate(str(value.carry_over), 'zope',
106            target_language=lang)
107        value_dict['outstanding'] = translate(str(value.outstanding), 'zope',
108            target_language=lang)
109        value_dict['automatic'] = translate(str(value.automatic), 'zope',
110            target_language=lang)
111        value_dict['grade'] = value.grade
112        value_dict['weight'] = value.weight
113        value_dict['course_category'] = value.course_category
114        value_dict['total_score'] = value.total_score
115        semester_dict = getUtility(IKofaUtils).SEMESTER_DICT
116        value_dict['semester'] = semester_dict[
117            value.semester].replace('mester', 'm.')
118        # AAUE specific
119        value_dict['formatted_total_score'] = value.total_score
120        if getattr(value, 'imported_ts', None):
121            value_dict['formatted_total_score'] = "<strong>%s</strong>" % value.imported_ts
122        yield value_dict
123
[13795]124class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
125    """ Page to display student base data
126    """
127    form_fields = grok.AutoFields(ICustomStudentBase).omit(
128        'password', 'suspended', 'suspended_comment', 'flash_notice')
129    form_fields[
130        'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
131
132class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
133    """ View to manage student base data
134    """
135    form_fields = grok.AutoFields(ICustomStudentBase).omit(
136        'student_id', 'adm_code', 'suspended',
137        'financially_cleared_by', 'financial_clearance_date')
138
[13351]139class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
140    """ Page to display student personal data
141    """
142    form_fields = grok.AutoFields(ICustomStudentPersonal)
143    form_fields['perm_address'].custom_widget = BytesDisplayWidget
144    form_fields['father_address'].custom_widget = BytesDisplayWidget
145    form_fields['mother_address'].custom_widget = BytesDisplayWidget
146    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
147    form_fields[
148        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
149
150class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
151    """ Page to edit personal data
152    """
153    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated')
154
155class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
156    """ Page to edit personal data
157    """
158    form_fields = grok.AutoFields(ICustomStudentPersonal)
159    form_fields['personal_updated'].for_display = True
160    form_fields[
161        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
162
[14661]163
164class ExportExaminationScheduleSlip(UtilityView, grok.View):
165    """Deliver a examination schedule slip.
166
167    This form page is available only in Uniben and AAUE.
168    """
169    grok.context(ICustomStudent)
170    grok.name('examination_schedule_slip.pdf')
171    grok.require('waeup.viewStudent')
172    prefix = 'form'
173
174    label = u'Examination Schedule Slip'
175
176    omit_fields = (
177        'suspended', 'phone', 'email',
178        'adm_code', 'suspended_comment',
179        'date_of_birth', 'current_level',
180        'current_mode',
181        'entry_session',
182        'flash_notice')
183
184    form_fields = []
185
186    @property
187    def note(self):
188        return """
[14674]189 <br /><br />
190 <strong>Instructions on CBT Venue Allocation Slip (VAS)</strong>
191 <br /><br />
192 You should login with your student id from Kofa and surname as password.
[14677]193 Download and print two copies of this slip and bring them to the
194 allocated CBT examination center.
195 The copies <strong>MUST</strong> be shown to the invigilators
196 before being admitted into the examination hall.
[14674]197 <br /><br />
[14677]198 How to start examination:<br /><br />
199  * Username:  "student id" from Kofa e.g E1000000<br />
200  * Password: "surname" as shown on this slip in capital letters<br />
201  * Click the course and click "start exam".
202 <br /><br />
203 <strong>WARNING:</strong> Electronic devices (phones, tablets, laptops etc.)
[14674]204 are not allowed in the examination hall. Any electronics seized will not
205 be returned. Any student caught charging his/her mobile phone at the CBT
[14677]206 centers will be penalized and the exam of such a student will be cancelled.
[14674]207 Bags and any foreign materials are not allowed at the venue of
208 the CBT exams. Any omission/other complaints should be reported to the CBT
209 committee through the HoD before the date of examination.
210 <br /><br />
211 Your examination date, time and venue is scheduled as follows:
212 <br /><br />
213 <strong>%s</strong>
[14661]214""" % self.context.flash_notice
215        return
216
217
218    def update(self):
219        if not self.context.flash_notice \
220            or not 'exam' in self.context.flash_notice.lower():
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, 'examination_schedule_slip',
230            self.context.student, studentview,
231            omit_fields=self.omit_fields,
232            note=self.note)
233
[14084]234class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage):
235    """ Page to display student clearance data
236    """
237
238    @property
239    def form_fields(self):
240        if self.context.is_postgrad:
241            form_fields = grok.AutoFields(
242                ICustomPGStudentClearance).omit('clearance_locked')
243        else:
244            form_fields = grok.AutoFields(
245                ICustomUGStudentClearance).omit('clearance_locked')
246        if not getattr(self.context, 'officer_comment'):
247            form_fields = form_fields.omit('officer_comment')
248        else:
249            form_fields['officer_comment'].custom_widget = BytesDisplayWidget
[14104]250        form_fields = form_fields.omit('def_adm')
[14084]251        return form_fields
252
253class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage):
254    """ Page to edit student clearance data
255    """
256
257    @property
258    def form_fields(self):
259        if self.context.is_postgrad:
260            form_fields = grok.AutoFields(
261                ICustomPGStudentClearance).omit('clr_code')
262        else:
263            form_fields = grok.AutoFields(
264                ICustomUGStudentClearance).omit('clr_code')
[14104]265        form_fields = form_fields.omit('def_adm')
[14084]266        return form_fields
267
[13362]268class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
269    """ View to edit student clearance data by student
270    """
271
272    @property
273    def form_fields(self):
274        if self.context.is_postgrad:
275            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
276            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
277            'physical_clearance_date')
278        else:
[14298]279            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
[13362]280            'clearance_locked', 'clr_code', 'officer_comment',
[14104]281            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
282        form_fields = form_fields.omit('def_adm')
[13362]283        return form_fields
284
[11846]285class CustomStartClearancePage(StartClearancePage):
[13360]286    with_ac = False
[11846]287
[13351]288    @property
289    def all_required_fields_filled(self):
290        if not self.context.email:
291            return _("Email address is missing."), 'edit_base'
292        if not self.context.phone:
293            return _("Phone number is missing."), 'edit_base'
294        if not self.context.father_name:
295            return _("Personal data form is not properly filled."), 'edit_personal'
296        return
297
[8911]298class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
299    """ Page to view an online payment ticket
300    """
301    grok.context(ICustomStudentOnlinePayment)
[9853]302    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]303        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]304    form_fields[
305        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
306    form_fields[
307        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
308
309class CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
310    """ Page to add an online payment ticket
311    """
312    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
313        'p_category')
314
[14527]315    ALUMNI_PAYMENT_CATS =  {
[14296]316        'transcript_local': 'Transcript Fee Local',
317        'transcript_inter': 'Transcript Fee International',
318        }
319
[14533]320    REDUCED_PAYMENT_CATS =  {
[14527]321        'clearance': 'Acceptance Fee',
322        'schoolfee': 'School Fee',
323        }
324
[14986]325    IJMBE_PAYMENT_CATS =  {
326        'clearance': 'Acceptance Fee',
327        'schoolfee': 'School Fee',
[14987]328        'schoolfee_1': 'School Fee (1st instalment)',
[14986]329        'schoolfee_2': 'School Fee (2nd instalment)',
330        }
331
[14296]332    @property
333    def selectable_categories(self):
334        if 'alumni' in self.application_url():
[14527]335            return self.ALUMNI_PAYMENT_CATS.items()
[14573]336        if self.context.student.current_mode in (
[15182]337            'special_pg_ft', 'special_pg_pt', 'found'):
[14533]338            return self.REDUCED_PAYMENT_CATS.items()
[14986]339        if self.context.student.current_mode == 'ijmbe':
340            return sorted(self.IJMBE_PAYMENT_CATS.items())
[14296]341        categories = getUtility(IKofaUtils).SELECTABLE_PAYMENT_CATEGORIES
342        return sorted(categories.items())
343
[13523]344class CustomPaymentsManageFormPage(PaymentsManageFormPage):
345    """ Page to manage the student payments.
346
347    This manage form page is for both students and students officers.
348    """
349    @property
350    def manage_payments_allowed(self):
351        return checkPermission('waeup.manageStudent', self.context)
352
[13059]353class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
[8911]354    """Deliver a PDF slip of the context.
355    """
356    grok.context(ICustomStudentOnlinePayment)
[9853]357    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]358        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]359    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9496]360    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9914]361
[11625]362    @property
363    def note(self):
[13408]364        p_session = self.context.p_session
[13405]365        try:
[13408]366            academic_session = grok.getSite()['configuration'][str(p_session)]
[13405]367        except KeyError:
368            academic_session = None
[13425]369        text =  '\n\n The Amount Authorized is inclusive of: '
[13512]370        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
371            and academic_session:
[14385]372            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
373            #union_fee = gateway_net_amt(academic_session.union_fee)
[14244]374            if self.context.student.entry_session == 2016 \
375                and self.context.student.entry_mode == 'ug_ft' \
376                and self.context.p_session == 2016:
377                # Add student id card fee to first school fee payment.
[14385]378
379                ## Attention: The payment slip does not contain any information
380                ## whether the fee was added or not.
381                ## We can only draw conclusions from from the student's entry
382                ## session whether the fee had been included.
383                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
384                #text += ('School Fee, '
385                #         '%s Naira Student ID Card Fee, '
386                #         '%s Naira Student Union Dues, '
387                #         '%s Naira Student Welfare Assurance Fee and '
388                #         % (id_card_fee, union_fee, welfare_fee))
389
[14244]390                text += ('School Fee, '
[14385]391                         'Student ID Card Fee, '
392                         'Student Union Dues, '
393                         'Student Welfare Assurance Fee and ')
[14244]394            else:
[14385]395
396                #text += ('School Fee, '
397                #         '%s Naira Student Union Dues, '
398                #         '%s Naira Student Welfare Assurance Fee and '
399                #         % (union_fee, welfare_fee))
400
[14244]401                text += ('School Fee, '
[14385]402                         'Student Union Dues, '
403                         'Student Welfare Assurance Fee and ')
[13410]404        elif self.context.p_category in (
405            'clearance_incl', 'clearance_medical_incl') and academic_session:
[14385]406
407            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
408            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
409            #text += ('Acceptance Fee, '
410            #         '%s Naira Matriculation Gown Fee, '
411            #         '%s Naira Lapel/File Fee and '
412            #         % (matric_gown_fee, lapel_fee))
413
[13437]414            text += ('Acceptance Fee, '
[14385]415                     'Matriculation Gown Fee, '
416                     'Lapel/File Fee and ')
[11625]417
[14385]418        #return text + '250.0 Naira Transaction Charge.'
419
420        return text + 'Transaction Charge.'
421
[14534]422class CustomStudyCourseManageFormPage(StudyCourseManageFormPage):
423    """ Page to edit the student study course data
424    """
425    grok.context(ICustomStudentStudyCourse)
426
427    @property
428    def form_fields(self):
429        if self.context.is_postgrad:
430            form_fields = grok.AutoFields(ICustomStudentStudyCourse).omit(
431                'previous_verdict')
432        else:
433            form_fields = grok.AutoFields(ICustomStudentStudyCourse)
434        form_fields['imported_cgpa'].for_display = True
435        return form_fields
436
[14981]437class CustomUnregisterCoursesView(UnregisterCoursesView):
438    """Unregister course list by student
439    """
440    grok.context(ICustomStudentStudyLevel)
441
442    def update(self):
443        if not self.context.__parent__.is_current:
444            emit_lock_message(self)
445            return
446        try:
447            deadline = grok.getSite()['configuration'][
448                str(self.context.level_session)].coursereg_deadline
449        except (TypeError, KeyError):
450            deadline = None
451        # In AAUE fresh students are allowed to "unregister their course"
452        # aside the deadline
453        if deadline and not self.context.student.is_fresh \
454            and deadline < datetime.now(pytz.utc):
455            self.flash(_(
456                "Course registration has ended. "
457                "Unregistration is disabled."), type="warning")
458        elif str(self.context.__parent__.current_level) != self.context.__name__:
459            self.flash(_('This is not your current level.'), type="danger")
460        elif self.context.student.state == REGISTERED:
461            IWorkflowInfo(self.context.student).fireTransition('reset7')
462            message = _('Course list has been unregistered.')
463            self.flash(message)
464        else:
465            self.flash(_('You are in the wrong state.'), type="warning")
466        self.redirect(self.url(self.context))
467        return
468
[9914]469class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
470    """ Page to display student study levels
471    """
472    grok.context(ICustomStudentStudyLevel)
[10480]473    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[14206]474        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
[9914]475    form_fields[
476        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
477
[14000]478    @property
[15228]479    def translated_values(self):
480        return translated_values(self)
481
482    @property
[14000]483    def show_results(self):
484        isStudent = getattr(
485            self.request.principal, 'user_type', None) == 'student'
[14944]486        try:
487            show_results = grok.getSite()[
488                'configuration'][str(self.context.level_session)].show_results
489        except KeyError:
[14000]490            return False
[14944]491        if isStudent and self.context.student.current_mode not in show_results:
492            return False
493        if isStudent and self.context.student.state != RETURNING \
494            and self.context.student.current_level == self.context.level:
495            return False
[14000]496        return True
497
[13834]498class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
499    """ Page to edit the student study level data
500    """
501    grok.context(ICustomStudentStudyLevel)
502
[14534]503    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
504        'validation_date', 'validated_by', 'total_credits', 'gpa', 'level',
505        'total_credits_s1', 'total_credits_s2')
506
507    form_fields['imported_gpa'].for_display = True
508    form_fields['imported_cgpa'].for_display = True
509
[13834]510class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
511    """ Page to edit the student study level data by students
512    """
513    grok.context(ICustomStudentStudyLevel)
514
[13059]515class CustomExportPDFCourseRegistrationSlip(
516    NigeriaExportPDFCourseRegistrationSlip):
[9914]517    """Deliver a PDF slip of the context.
518    """
519    grok.context(ICustomStudentStudyLevel)
520    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[10102]521        'level_session', 'level_verdict',
[14206]522        'validated_by', 'validation_date', 'gpa', 'level',
523        'imported_gpa', 'imported_cgpa')
[9914]524
[10269]525    omit_fields = ('password', 'suspended', 'suspended_comment',
[10689]526        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
[13713]527        'department', 'current_mode', 'current_level', 'flash_notice')
[10269]528
[14000]529    @property
530    def show_results(self):
531        isStudent = getattr(
532            self.request.principal, 'user_type', None) == 'student'
[14944]533        try:
534            show_results = grok.getSite()[
535                'configuration'][str(self.context.level_session)].show_results
536        except KeyError:
[14302]537            return False
[14944]538        if isStudent and self.context.student.current_mode not in show_results:
539            return False
540        if isStudent and self.context.student.state != RETURNING \
541            and self.context.student.current_level == self.context.level:
542            return False
[14302]543        return True
[14000]544
[13038]545    def update(self):
546        if self.context.student.state != REGISTERED \
[13051]547            and self.context.student.current_level == self.context.level:
[13038]548            self.flash(_('Forbidden'), type="warning")
549            self.redirect(self.url(self.context))
[14000]550            return
[13038]551
[9914]552    @property
553    def label(self):
554        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
555        lang = self.request.cookies.get('kofa.language', portal_language)
556        level_title = translate(self.context.level_title, 'waeup.kofa',
557            target_language=lang)
558        line0 = ''
[13788]559        if self.context.student.is_postgrad:
560            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
561        elif self.context.student.current_mode.endswith('_pt'):
[9914]562            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
[13866]563        line1 = translate(_('Course Registration Slip'),
564            target_language=portal_language) \
[9914]565            + ' %s' % level_title
[13866]566        line2 = translate(_('Session'),
567            target_language=portal_language) \
[9914]568            + ' %s' % self.context.getSessionString
569        return '%s%s\n%s' % (line0, line1, line2)
570
571    @property
572    def title(self):
573        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]574        return translate(_('Units Registered'), target_language=portal_language)
[9914]575
576    def _signatures(self):
[13647]577        if self.context.student.current_mode.endswith('_pt') \
578            or self.context.student.current_mode == 'found':
579            return (
580                [('I have selected the course on the advise of my Head of '
581                 'Department. <br>', _('Student\'s Signature'), '<br>')],
582                [('This student has satisfied the department\'s requirements. '
583                 'I recommend to approve the course registration. <br>',
584                 _('Head of Department\'s Signature'), '<br>')],
[13946]585                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
[13647]586                [('', _('Director\'s Signature'))]
587                )
588        if self.context.student.current_mode in (
589            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
[13649]590            return ([_('Academic Adviser\'s Signature'),
591                _('Faculty Officer\'s Signature'),
592                _('Student\'s Signature')],)
593
[13647]594        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
595            return (
[13676]596                [('I declare that all items of information supplied above are correct:' ,
[13680]597                    _('Student\'s Signature'), '<br>')],
[13676]598                [('We approved the above registration:',
[13680]599                    _('Major Supervisor (Name / Signature)'), '')],
600                [('', _('Co-Supervisor (Name / Signature)'), '')],
[13676]601                [('', _('Head of Department'), '<br>')],
602                [('The student has satisfied the conditions for renewal of '
603                  'registration for graduate school programme in this university:',
[13680]604                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
605                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
[13647]606                )
607        return None
[9914]608
[13647]609
[9914]610    def render(self):
611        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]612        Sem = translate(_('Sem.'), target_language=portal_language)
613        Code = translate(_('Code'), target_language=portal_language)
614        Title = translate(_('Title'), target_language=portal_language)
615        Cred = translate(_('Cred.'), target_language=portal_language)
[14650]616        CC = translate(_('Cat.'), target_language=portal_language)
[14000]617        if self.show_results:
618            Score = translate(_('Score'), target_language=portal_language)
619            #CA = translate(_('CA'), target_language=portal_language)
620            Grade = translate(_('Grade'), target_language=portal_language)
[13866]621        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
[9914]622            target_language=portal_language)
623        studentview = StudentBasePDFFormPage(self.context.student,
624            self.request, self.omit_fields)
625        students_utils = getUtility(IStudentsUtils)
[10442]626
627        tabledata = []
628        tableheader = []
629        contenttitle = []
630        for i in range(1,7):
631            tabledata.append(sorted(
632                [value for value in self.context.values() if value.semester == i],
633                key=lambda value: str(value.semester) + value.code))
[14000]634            if self.show_results:
635                tableheader.append([(Code,'code', 2.0),
636                                   (Title,'title', 7),
[14650]637                                   (Cred, 'credits', 1.4),
638                                   (CC, 'course_category', 1.2),
[14000]639                                   (Score, 'score', 1.4),
640                                   #(CA, 'ca', 1.4),
641                                   (Grade, 'grade', 1.4),
642                                   (Signature, 'dummy', 3),
643                                   ])
644            else:
645                tableheader.append([(Code,'code', 2.0),
646                                   (Title,'title', 7),
647                                   (Cred, 'credits', 1.5),
[14650]648                                   (CC, 'course_category', 1.2),
[14000]649                                   (Signature, 'dummy', 3),
650                                   ])
[9914]651        if len(self.label.split('\n')) == 3:
652            topMargin = 1.9
653        elif len(self.label.split('\n')) == 2:
654            topMargin = 1.7
655        else:
656            topMargin = 1.5
657        return students_utils.renderPDF(
658            self, 'course_registration_slip.pdf',
659            self.context.student, studentview,
[10442]660            tableheader=tableheader,
661            tabledata=tabledata,
[9914]662            signatures=self._signatures(),
[10269]663            topMargin=topMargin,
664            omit_fields=self.omit_fields
[9914]665            )
[10566]666
[14165]667class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
668    """ Page to display the student's transcript.
669    """
670    grok.require('waeup.viewStudent')
671
[13059]672class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
[10566]673    """Deliver a PDF slip of the context.
674    """
[14315]675#    grok.require('waeup.viewStudent')
[10566]676
[14267]677    note = _("""
678<br /><br /><br /><br />
679<font size='10'>
680<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
681</font>
682""")
683
[10566]684    def _sigsInFooter(self):
685        return []
686
687    def _signatures(self):
688        return ([(
[14999]689            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
[11555]690            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
[10922]691
[13834]692    def render(self):
693        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]694        Term = translate(_('Sem.'), target_language=portal_language)
695        Code = translate(_('Code'), target_language=portal_language)
696        Title = translate(_('Title'), target_language=portal_language)
697        Cred = translate(_('Credits'), target_language=portal_language)
698        Score = translate(_('Score'), target_language=portal_language)
699        Grade = translate(_('Grade'), target_language=portal_language)
[13834]700        studentview = StudentBasePDFFormPage(self.context.student,
701            self.request, self.omit_fields)
702        students_utils = getUtility(IStudentsUtils)
703
704        tableheader = [(Code,'code', 2.5),
705                         (Title,'title', 7),
706                         (Term, 'semester', 1.5),
707                         (Cred, 'credits', 1.5),
[14136]708                         (Score, 'total_score', 1.5),
[13834]709                         (Grade, 'grade', 1.5),
710                         ]
711
[15165]712        pdfstream = students_utils.renderPDFTranscript(
[13834]713            self, 'transcript.pdf',
714            self.context.student, studentview,
715            omit_fields=self.omit_fields,
716            tableheader=tableheader,
717            signatures=self._signatures(),
718            sigs_in_footer=self._sigsInFooter(),
[15165]719            digital_sigs=self._digital_sigs(),
720            save_file=self._save_file(),
[13834]721            )
[15165]722        if not pdfstream:
723            self.redirect(self.url(self.context.student))
724            return
725        return pdfstream
[13834]726
[13059]727class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
[10922]728    """Deliver a PDF Admission slip.
729    """
730
731    @property
732    def label(self):
733        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]734        return translate(_('e-Admission Slip \n'),
735            target_language=portal_language) \
[10922]736            + ' %s' % self.context.display_fullname
[11597]737
[13059]738class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
[11606]739    """Deliver a PDF slip of the context.
740    """
741
742    @property
743    def label(self):
744        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[14114]745        return translate(_('Verification/Clearance Slip\n'),
[13866]746            target_language=portal_language) \
[11606]747            + ' %s' % self.context.display_fullname
748
[14084]749    @property
750    def form_fields(self):
751        if self.context.is_postgrad:
752            form_fields = grok.AutoFields(
753                ICustomPGStudentClearance).omit('clearance_locked')
754        else:
755            form_fields = grok.AutoFields(
756                ICustomUGStudentClearance).omit('clearance_locked')
757        if not getattr(self.context, 'officer_comment'):
758            form_fields = form_fields.omit('officer_comment')
[14104]759        form_fields = form_fields.omit('def_adm')
[14084]760        return form_fields
761
[11597]762class StudentGetMatricNumberPage(UtilityView, grok.View):
763    """ Construct and set the matriculation number.
764    """
765    grok.context(IStudent)
766    grok.name('get_matric_number')
767    grok.require('waeup.viewStudent')
768
769    def update(self):
770        students_utils = getUtility(IStudentsUtils)
771        msg, mnumber = students_utils.setMatricNumber(self.context)
772        if msg:
773            self.flash(msg, type="danger")
774        else:
775            self.flash(_('Matriculation number %s assigned.' % mnumber))
[11602]776            self.context.writeLogMessage(self, '%s assigned' % mnumber)
[11597]777        self.redirect(self.url(self.context))
778        return
779
780    def render(self):
[11607]781        return
782
[13059]783class ExportPDFMatricNumberSlip(UtilityView, grok.View):
[11607]784    """Deliver a PDF notification slip.
785    """
786    grok.context(ICustomStudent)
787    grok.name('matric_number_slip.pdf')
788    grok.require('waeup.viewStudent')
789    prefix = 'form'
790
791    form_fields = grok.AutoFields(ICustomStudent).select(
792        'student_id', 'matric_number')
[13713]793    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
[11607]794
795    @property
[13489]796    def title(self):
797        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]798        return translate(_('Matriculation Number'), 'waeup.kofa',
[13489]799            target_language=portal_language)
800
801    @property
[11607]802    def label(self):
803        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]804        return translate(_('Matriculation Number Slip\n'),
805            target_language=portal_language) \
[11607]806            + ' %s' % self.context.display_fullname
807
808    def render(self):
809        if self.context.state not in (PAID,) or not self.context.is_fresh \
810            or not self.context.matric_number:
811            self.flash('Not allowed.', type="danger")
812            self.redirect(self.url(self.context))
813            return
814        students_utils = getUtility(IStudentsUtils)
[11609]815        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
816                     'payments have been received and your matriculation ' +
817                     'number generated with details as follows.')
[11607]818        return students_utils.renderPDFAdmissionLetter(self,
819            self.context.student, omit_fields=self.omit_fields,
[13353]820            pre_text=pre_text, post_text='')
821
[13489]822class ExportPersonalDataSlip(UtilityView, grok.View):
823    """Deliver a PDF notification slip.
824    """
825    grok.context(ICustomStudent)
826    grok.name('personal_data_slip.pdf')
827    grok.require('waeup.viewStudent')
828    prefix = 'form'
829    note = None
830
831    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
[13713]832    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
833                   'certificate', 'flash_notice')
[13489]834
835    @property
836    def title(self):
837        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]838        return translate(_('Personal Data'), 'waeup.kofa',
[13489]839            target_language=portal_language)
840
841    @property
842    def label(self):
843        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]844        return translate(_('Personal Data Slip\n'),
845            target_language=portal_language) \
[13489]846            + ' %s' % self.context.display_fullname
847
848    def render(self):
849        studentview = StudentBasePDFFormPage(self.context.student,
850            self.request, self.omit_fields)
851        students_utils = getUtility(IStudentsUtils)
852        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
853            self.context.student, studentview, note=self.note,
854            omit_fields=self.omit_fields)
855
[13462]856class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
857    """ Page to manage bed tickets.
858    This manage form page is for both students and students officers.
859    """
860    with_hostel_selection = True
861
[13353]862class CustomBedTicketAddPage(BedTicketAddPage):
[13360]863    with_ac = False
[13380]864
865class CustomStudentFilesUploadPage(StudentFilesUploadPage):
866    """ View to upload files by student. Inherit from same class in
867    base package, not from kofacustom.nigeria which
868    requires that no application slip exists.
[13770]869    """
870
871class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
872    """ Page to display course tickets
873    """
874
[14453]875    @property
876    def show_results(self):
877        isStudent = getattr(
878            self.request.principal, 'user_type', None) == 'student'
879        if isStudent:
880            return False
881        return True
882
883    @property
884    def form_fields(self):
885        if self.show_results:
886            return grok.AutoFields(ICustomCourseTicket)
887        else:
888            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
889
[13770]890class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
891    """ Page to manage course tickets
892    """
893    form_fields = grok.AutoFields(ICustomCourseTicket)
894    form_fields['title'].for_display = True
895    form_fields['fcode'].for_display = True
896    form_fields['dcode'].for_display = True
897    form_fields['semester'].for_display = True
898    form_fields['passmark'].for_display = True
899    form_fields['credits'].for_display = True
900    form_fields['mandatory'].for_display = False
901    form_fields['automatic'].for_display = True
902    form_fields['carry_over'].for_display = True
[15205]903    form_fields['ticket_session'].for_display = True
[15226]904    form_fields['imported_ts'].for_display = True
[13770]905
906class CustomEditScoresPage(EditScoresPage):
907    """Page that filters and lists students.
908    """
909    grok.template('editscorespage')
910
[14700]911    def _searchCatalog(self, session):
912        cat = queryUtility(ICatalog, name='coursetickets_catalog')
913        coursetickets = cat.searchResults(
914            session=(session, session),
915            code=(self.context.code, self.context.code)
916            )
917        try:
918            score_editing_enabled = grok.getSite()[
919                'configuration'][str(session)].score_editing_enabled
920        except KeyError:
921            return []
[14707]922        coursetickets_list = [courseticket for courseticket in coursetickets
923            if courseticket.student.current_mode in score_editing_enabled]
[14700]924        return coursetickets_list
[13937]925
926    def _extract_uploadfile(self, uploadfile):
927        """Get a mapping of student-ids to scores.
928
929        The mapping is constructed by reading contents from `uploadfile`.
930
931        We expect uploadfile to be a regular CSV file with columns
932        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
933        """
934        result = dict()
935        data = StringIO(uploadfile.read())  # ensure we have something seekable
936        reader = csv.DictReader(data)
937        for row in reader:
938            if not ('student_id' in row and 'score' in row and 'ca' in row):
939                continue
940            result[row['student_id']] = (row['score'], row['ca'])
941        return result
942
[14288]943    def _update_scores(self, form):
[13937]944        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
[14288]945        error = ''
[13937]946        if 'UPDATE_FILE' in form:
947            if form['uploadfile']:
948                try:
949                    formvals = self._extract_uploadfile(form['uploadfile'])
950                except:
951                    self.flash(
952                        _('Uploaded file contains illegal data. Ignored'),
953                        type="danger")
[14288]954                    return False
[13937]955            else:
956                self.flash(
957                    _('No file provided.'), type="danger")
[14288]958                return False
[13937]959        else:
960            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
[14149]961        for ticket in self.editable_tickets:
[13937]962            ticket_error = False
963            score = ticket.score
964            ca = ticket.ca
965            sid = ticket.student.student_id
966            if formvals[sid][0] == '':
967                score = None
968            if formvals[sid][1] == '':
969                ca = None
970            try:
971                if formvals[sid][0]:
972                    score = int(formvals[sid][0])
973                if formvals[sid][1]:
974                    ca = int(formvals[sid][1])
975            except ValueError:
976                error += '%s, ' % ticket.student.display_fullname
977                ticket_error = True
978            if not ticket_error and ticket.score != score:
[14113]979                try:
980                    ticket.score = score
981                except TooBig:
982                    error += '%s, ' % ticket.student.display_fullname
983                    ticket_error = True
984                    pass
[13937]985                ticket.student.__parent__.logger.info(
986                    '%s - %s %s/%s score updated (%s)' %
987                    (ob_class, ticket.student.student_id,
988                     ticket.level, ticket.code, score))
989            if not ticket_error and ticket.ca != ca:
[14113]990                try:
991                    ticket.ca = ca
992                except TooBig:
993                    error += '%s, ' % ticket.student.display_fullname
994                    pass
[13937]995                ticket.student.__parent__.logger.info(
996                    '%s - %s %s/%s ca updated (%s)' %
997                    (ob_class, ticket.student.student_id,
998                     ticket.level, ticket.code, ca))
999        if error:
1000            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
[14113]1001              % error.strip(', ')), type="danger")
[14288]1002        return True
1003
1004class EditPreviousSessionScoresPage(CustomEditScoresPage):
1005
1006    grok.name('edit_prev_scores')
1007
1008    def update(self,  *args, **kw):
1009        form = self.request.form
1010        self.current_academic_session = grok.getSite()[
1011            'configuration'].current_academic_session
1012        if self.context.__parent__.__parent__.score_editing_disabled:
1013            self.flash(_('Score editing disabled.'), type="warning")
1014            self.redirect(self.url(self.context))
[13939]1015            return
[14288]1016        if not self.current_academic_session:
1017            self.flash(_('Current academic session not set.'), type="warning")
1018            self.redirect(self.url(self.context))
1019            return
1020        previous_session = self.current_academic_session - 1
1021        self.session_title = academic_sessions_vocab.getTerm(
1022            previous_session).title
1023        self.tickets = self._searchCatalog(previous_session)
1024        if not self.tickets:
1025            self.flash(_('No student found.'), type="warning")
1026            self.redirect(self.url(self.context))
1027            return
1028        self.editable_tickets = [
1029            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1030        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1031            return
1032        if not self.editable_tickets:
1033            return
1034        success = self._update_scores(form)
1035        if success:
1036            self.flash(_('You successfully updated course results.'))
[13900]1037        return
1038
1039class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1040    """Deliver a PDF slip of course tickets for a lecturer.
1041    """
1042
[15247]1043    note = u'\nUpgraded scores are in asterisks.'
1044
[14315]1045    def data(self, session):
[14704]1046        #site = grok.getSite()
[13900]1047        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1048        coursetickets = cat.searchResults(
1049            session=(session, session),
1050            code=(self.context.code, self.context.code)
1051            )
[14707]1052        # Apply filter
1053        try:
1054            score_editing_enabled = grok.getSite()[
1055                'configuration'][str(session)].score_editing_enabled
1056            coursetickets_filtered = [courseticket
1057                for courseticket in coursetickets
[15006]1058                if courseticket.student.current_mode in score_editing_enabled
[15236]1059                and courseticket.total_score is not None
1060                and courseticket.__parent__.__parent__.is_current]
[14707]1061        except KeyError:
1062            coursetickets_filtered = coursetickets
[14149]1063        # In AAUE only editable tickets can be printed
1064        editable_tickets = [
[14707]1065            ticket for ticket in coursetickets_filtered
1066            if ticket.editable_by_lecturer]
[13963]1067        header = [[_(''),
[15006]1068                   _('Student Id'),
[13963]1069                   _('Matric No.'),
[14703]1070                   #_('Reg. No.'),
1071                   #_('Fullname'),
1072                   #_('Status'),
1073                   #_('Course of\nStudies'),
[14704]1074                   _('Department'),
[13900]1075                   _('Level'),
[15201]1076                   _(' CA  '),
1077                   _('Exam\nScore'),
[13964]1078                   _('Total '),
[13963]1079                   _('Grade'),
1080                   ],]
[14288]1081        sorted_tickets = sorted(editable_tickets,
[15199]1082            key=lambda ticket: ticket.student.depcode
1083                               + ticket.student.faccode
[14704]1084                               + ticket.student.matric_number)
[14288]1085        no = 1
[13907]1086        tickets = []
[14315]1087        passed = 0
1088        failed = 0
[15229]1089        with_ca = False
[14149]1090        # In AAUE only editable tickets can be printed
[14288]1091        for ticket in sorted_tickets:
[15229]1092            if ticket.ca > 0:
1093                with_ca = True
[15006]1094            total = ticket.total_score
[15232]1095            if getattr(ticket, 'imported_ts', None):
[15245]1096                total = "**%s**" % ticket.imported_ts
[15006]1097            grade = ticket._getGradeWeightFromScore[0]
1098            if grade in ('F', '-'):
1099                failed += 1
[13963]1100            else:
[15006]1101                passed += 1
[13964]1102            fullname = textwrap.fill(ticket.student.display_fullname, 30)
[14704]1103            #deptitle = site['faculties'][ticket.student.faccode][
1104            #    ticket.student.depcode].longtitle
[15240]1105            row = [str(no),
[15006]1106                  ticket.student.student_id,
[13963]1107                  ticket.student.matric_number,
[14703]1108                  #ticket.student.reg_number,
1109                  #fullname,
1110                  #ticket.student.translated_state,
1111                  #ticket.student.certcode,
[14704]1112                  ticket.student.faccode + ' / ' + ticket.student.depcode,
[13900]1113                  ticket.level,
[15201]1114                  ticket.ca,
1115                  ticket.score,
[13963]1116                  total,
[13964]1117                  grade,
[13963]1118                  ]
[13907]1119            tickets.append(row)
[13963]1120            no += 1
[14317]1121        total = passed + failed
[14320]1122        passed_perc = 0
1123        failed_perc = 0
[14317]1124        if total:
[14320]1125            passed_perc = 100 * passed / total
1126            failed_perc = 100 * failed / total
[14710]1127        dep = self.context.__parent__.__parent__.longtitle
1128        fac = self.context.__parent__.__parent__.__parent__.longtitle
[15229]1129        # remove CA column if not necessary
1130        if not with_ca:
1131            header = [[_(''),
1132                       _('Student Id'),
1133                       _('Matric No.'),
1134                       #_('Reg. No.'),
1135                       #_('Fullname'),
1136                       #_('Status'),
1137                       #_('Course of\nStudies'),
1138                       _('Department'),
1139                       _('Level'),
1140                       #_(' CA  '),
1141                       _('Exam\nScore'),
1142                       _('Total '),
1143                       _('Grade'),
1144                       ],]
1145            for ticket in tickets:
1146                del(ticket[5])
[14320]1147        return header + tickets, [
[14710]1148            dep, fac, total, passed, passed_perc, failed, failed_perc]
[14288]1149
[14703]1150    def render(self):
1151        session = grok.getSite()['configuration'].current_academic_session
1152        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1153                     if i['local_role'] == 'waeup.local.Lecturer']
1154        lecturers =  ', '.join(lecturers)
1155        students_utils = getUtility(IStudentsUtils)
1156        # only orientation is different
1157        return students_utils.renderPDFCourseticketsOverview(
[15247]1158            self, session, self.data(session), lecturers, '', 45, self.note)
[14703]1159
[14288]1160class DownloadPreviousSessionScoresView(DownloadScoresView):
1161    """View that exports scores.
1162    """
1163    grok.name('download_prev_scores')
1164
1165    def update(self):
1166        self.current_academic_session = grok.getSite()[
1167            'configuration'].current_academic_session
1168        if self.context.__parent__.__parent__.score_editing_disabled:
1169            self.flash(_('Score editing disabled.'), type="warning")
1170            self.redirect(self.url(self.context))
1171            return
1172        if not self.current_academic_session:
1173            self.flash(_('Current academic session not set.'), type="warning")
1174            self.redirect(self.url(self.context))
1175            return
1176        site = grok.getSite()
1177        exporter = getUtility(ICSVExporter, name='lecturer')
1178        self.csv = exporter.export_filtered(site, filepath=None,
1179                                 catalog='coursetickets',
1180                                 session=self.current_academic_session-1,
1181                                 level=None,
1182                                 code=self.context.code)
[14306]1183        return
1184
1185class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1186    """Captcha'd request password page for students.
1187    """
1188    grok.name('alumni_requestpw')
1189    grok.require('waeup.Anonymous')
1190    grok.template('alumni_requestpw')
1191    form_fields = grok.AutoFields(IStudentRequestPW).select(
1192        'lastname','number','email')
1193    label = _('Search student record and send password for first-time login')
1194
1195    def _redirect_no_student(self):
1196        self.flash(_('No student record found.'), type="warning")
1197        self.redirect(self.application_url() + '/applicants/trans2017/register')
[14288]1198        return
Note: See TracBrowser for help on using the repository browser.