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

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

study modes "ug_dsh" and "de_dsh" and "ug_pt", "de_pt" use the same Academic calendar.

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