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

Last change on this file since 15461 was 15461, checked in by Henrik Bettermann, 5 years ago

Use deadlines also for course unregistration.

  • Property svn:keywords set to Id
File size: 50.9 KB
Line 
1## $Id: browser.py 15461 2019-06-19 06:03:06Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
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    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
327    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
328
329    @property
330    def note(self):
331        p_session = self.context.p_session
332        try:
333            academic_session = grok.getSite()['configuration'][str(p_session)]
334        except KeyError:
335            academic_session = None
336        text =  '\n\n The Amount Authorized is inclusive of: '
337        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
338            and academic_session:
339            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
340            #union_fee = gateway_net_amt(academic_session.union_fee)
341            if self.context.student.entry_session == 2016 \
342                and self.context.student.entry_mode == 'ug_ft' \
343                and self.context.p_session == 2016:
344                # Add student id card fee to first school fee payment.
345
346                ## Attention: The payment slip does not contain any information
347                ## whether the fee was added or not.
348                ## We can only draw conclusions from from the student's entry
349                ## session whether the fee had been included.
350                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
351                #text += ('School Fee, '
352                #         '%s Naira Student ID Card Fee, '
353                #         '%s Naira Student Union Dues, '
354                #         '%s Naira Student Welfare Assurance Fee and '
355                #         % (id_card_fee, union_fee, welfare_fee))
356
357                text += ('School Fee, '
358                         'Student ID Card Fee, '
359                         'Student Union Dues, '
360                         'Sports Fee, '
361                         'Library Levy, '
362                         'Student Welfare Assurance Fee and ')
363            else:
364
365                #text += ('School Fee, '
366                #         '%s Naira Student Union Dues, '
367                #         '%s Naira Student Welfare Assurance Fee and '
368                #         % (union_fee, welfare_fee))
369
370                text += ('School Fee, '
371                         'Student Union Dues, '
372                         'Sports Development Fee, '
373                         'Library Development Levy, '
374                         'Student Welfare Assurance Fee and ')
375        elif self.context.p_category in (
376            'clearance_incl', 'clearance_medical_incl') and academic_session:
377
378            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
379            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
380            #text += ('Acceptance Fee, '
381            #         '%s Naira Matriculation Gown Fee, '
382            #         '%s Naira Lapel/File Fee and '
383            #         % (matric_gown_fee, lapel_fee))
384
385            text += ('Acceptance Fee, '
386                     'Matriculation Gown Fee, '
387                     'Lapel/File Fee and ')
388
389        #return text + '250.0 Naira Transaction Charge.'
390
391        return text + 'Transaction Charge.'
392
393class CustomStudyCourseManageFormPage(StudyCourseManageFormPage):
394    """ Page to edit the student study course data
395    """
396    grok.context(ICustomStudentStudyCourse)
397
398    @property
399    def form_fields(self):
400        if self.context.is_postgrad:
401            form_fields = grok.AutoFields(ICustomStudentStudyCourse).omit(
402                'previous_verdict')
403        else:
404            form_fields = grok.AutoFields(ICustomStudentStudyCourse)
405        form_fields['imported_cgpa'].for_display = True
406        return form_fields
407
408class CustomUnregisterCoursesView(UnregisterCoursesView):
409    """Unregister course list by student
410    """
411    grok.context(ICustomStudentStudyLevel)
412
413    def update(self):
414        if not self.context.__parent__.is_current:
415            emit_lock_message(self)
416            return
417        try:
418            academic_session = grok.getSite()['configuration'][
419                str(self.context.level_session)]
420            if self.context.student.is_postgrad:
421                deadline = academic_session.coursereg_deadline_pg
422            elif self.context.student.current_mode.startswith('dp'):
423                deadline = academic_session.coursereg_deadline_dp
424            elif self.context.student.current_mode.endswith('_pt'):
425                deadline = academic_session.coursereg_deadline_pt
426            elif self.context.student.current_mode == 'found':
427                deadline = academic_session.coursereg_deadline_found
428            elif self.context.student.current_mode == 'bridge':
429                deadline = academic_session.coursereg_deadline_bridge
430            else:
431                deadline = academic_session.coursereg_deadline
432        except (TypeError, KeyError):
433            deadline = None
434        if deadline and deadline < datetime.now(pytz.utc):
435            self.flash(_(
436                "Course registration has ended. "
437                "Unregistration is disabled."), type="danger")
438        elif str(self.context.__parent__.current_level) != self.context.__name__:
439            self.flash(_('This is not your current level.'), type="danger")
440        elif self.context.student.state == REGISTERED:
441            IWorkflowInfo(self.context.student).fireTransition('reset7')
442            message = _('Course list has been unregistered.')
443            self.flash(message)
444        else:
445            self.flash(_('You are in the wrong state.'), type="warning")
446        self.redirect(self.url(self.context))
447        return
448
449class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
450    """ Page to display student study levels
451    """
452    grok.context(ICustomStudentStudyLevel)
453    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
454        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
455    form_fields[
456        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
457
458    @property
459    def translated_values(self):
460        return translated_values(self)
461
462    @property
463    def show_results(self):
464        isStudent = getattr(
465            self.request.principal, 'user_type', None) == 'student'
466        try:
467            show_results = grok.getSite()[
468                'configuration'][str(self.context.level_session)].show_results
469        except KeyError:
470            return False
471        if isStudent and self.context.student.current_mode not in show_results:
472            return False
473        #if isStudent and self.context.student.state != RETURNING \
474        #    and self.context.student.current_level == self.context.level:
475        #    return False
476        return True
477
478class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
479    """ Page to edit the student study level data
480    """
481    grok.context(ICustomStudentStudyLevel)
482
483    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
484        'validation_date', 'validated_by', 'total_credits', 'gpa', 'level',
485        'total_credits_s1', 'total_credits_s2')
486
487    form_fields['imported_gpa'].for_display = True
488    form_fields['imported_cgpa'].for_display = True
489
490class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
491    """ Page to edit the student study level data by students
492    """
493    grok.context(ICustomStudentStudyLevel)
494
495class StudyLevelRepairFormPage(KofaEditFormPage):
496    """ Page to repair the student study level data by students
497    """
498    grok.context(IStudentStudyLevel)
499    grok.name('repair')
500    grok.require('waeup.editStudyLevel')
501    grok.template('studylevelrepairpage')
502    pnav = 4
503    placeholder = _('Enter valid course code')
504
505    def update(self, ADD=None, course=None):
506        if not self.context.__parent__.is_current \
507            or self.context.student.studycourse_locked:
508            emit_lock_message(self)
509            return
510        try:
511            studylevel_repair_enabled = grok.getSite()['configuration'][
512                str(self.context.level_session)].studylevel_repair_enabled
513        except KeyError:
514            emit_lock_message(self)
515            return
516        if not studylevel_repair_enabled:
517            emit_lock_message(self)
518            return
519        super(StudyLevelRepairFormPage, self).update()
520        if ADD is not None:
521            if not course:
522                self.flash(_('No valid course code entered.'), type="warning")
523                return
524            cat = queryUtility(ICatalog, name='courses_catalog')
525            result = cat.searchResults(code=(course, course))
526            if len(result) != 1:
527                self.flash(_('Course not found.'), type="warning")
528                return
529            course = list(result)[0]
530            addCourseTicket(self, course)
531        return
532
533    @property
534    def label(self):
535        # Here we know that the cookie has been set
536        lang = self.request.cookies.get('kofa.language')
537        level_title = translate(self.context.level_title, 'waeup.kofa',
538            target_language=lang)
539        return _('Repair course list of ${a}',
540            mapping = {'a':level_title})
541
542    @property
543    def translated_values(self):
544        return translated_values(self)
545
546    def _delCourseTicket(self, **data):
547        form = self.request.form
548        if 'val_id' in form:
549            child_id = form['val_id']
550        else:
551            self.flash(_('No ticket selected.'), type="warning")
552            self.redirect(self.url(self.context, '@@edit'))
553            return
554        if not isinstance(child_id, list):
555            child_id = [child_id]
556        deleted = []
557        for id in child_id:
558            # Students are not allowed to remove core tickets
559            if id in self.context and \
560                self.context[id].removable_by_student:
561                del self.context[id]
562                deleted.append(id)
563        if len(deleted):
564            self.flash(_('Successfully removed: ${a}',
565                mapping = {'a':', '.join(deleted)}))
566            self.context.writeLogMessage(
567                self,'removed: %s at %s' %
568                (', '.join(deleted), self.context.level))
569        self.redirect(self.url(self.context, u'@@repair'))
570        return
571
572    @jsaction(_('Remove selected tickets'))
573    def delCourseTicket(self, **data):
574        self._delCourseTicket(**data)
575        return
576
577class CustomExportPDFCourseRegistrationSlip(
578    NigeriaExportPDFCourseRegistrationSlip):
579    """Deliver a PDF slip of the context.
580    """
581    grok.context(ICustomStudentStudyLevel)
582    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
583        'level_session', 'level_verdict',
584        'validated_by', 'validation_date', 'gpa', 'level',
585        'imported_gpa', 'imported_cgpa')
586
587    omit_fields = ('password', 'suspended', 'suspended_comment',
588        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
589        'department', 'current_mode', 'current_level', 'flash_notice',
590        'transcript_remark')
591
592    @property
593    def show_results(self):
594        isStudent = getattr(
595            self.request.principal, 'user_type', None) == 'student'
596        try:
597            show_results = grok.getSite()[
598                'configuration'][str(self.context.level_session)].show_results
599        except KeyError:
600            return False
601        if isStudent and self.context.student.current_mode not in show_results:
602            return False
603        if isStudent and self.context.student.state != RETURNING \
604            and self.context.student.current_level == self.context.level:
605            return False
606        return True
607
608    def update(self):
609        if self.context.student.state != REGISTERED \
610            and self.context.student.current_level == self.context.level:
611            self.flash(_('Forbidden'), type="warning")
612            self.redirect(self.url(self.context))
613            return
614
615    @property
616    def label(self):
617        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
618        lang = self.request.cookies.get('kofa.language', portal_language)
619        level_title = translate(self.context.level_title, 'waeup.kofa',
620            target_language=lang)
621        line0 = ''
622        if self.context.student.is_postgrad:
623            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
624        elif self.context.student.current_mode.endswith('_pt'):
625            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
626        line1 = translate(_('Course Registration Slip'),
627            target_language=portal_language) \
628            + ' %s' % level_title
629        line2 = translate(_('Session'),
630            target_language=portal_language) \
631            + ' %s' % self.context.getSessionString
632        return '%s%s\n%s' % (line0, line1, line2)
633
634    @property
635    def title(self):
636        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
637        return translate(_('Units Registered'), target_language=portal_language)
638
639    def _signatures(self):
640        if self.context.student.current_mode.endswith('_pt') \
641            or self.context.student.current_mode == 'found':
642            return (
643                [('I have selected the course on the advise of my Head of '
644                 'Department. <br>', _('Student\'s Signature'), '<br>')],
645                [('This student has satisfied the department\'s requirements. '
646                 'I recommend to approve the course registration. <br>',
647                 _('Head of Department\'s Signature'), '<br>')],
648                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
649                [('', _('Director\'s Signature'))]
650                )
651        if self.context.student.current_mode in (
652            'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'):
653            return ([_('Academic Adviser\'s Signature'),
654                _('Faculty Officer\'s Signature'),
655                _('Student\'s Signature')],)
656
657        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
658            return (
659                [('I declare that all items of information supplied above are correct:' ,
660                    _('Student\'s Signature'), '<br>')],
661                [('We approved the above registration:',
662                    _('Major Supervisor (Name / Signature)'), '')],
663                [('', _('Co-Supervisor (Name / Signature)'), '')],
664                [('', _('Head of Department'), '<br>')],
665                [('The student has satisfied the conditions for renewal of '
666                  'registration for graduate school programme in this university:',
667                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
668                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
669                )
670        return None
671
672
673    def render(self):
674        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
675        Sem = translate(_('Sem.'), target_language=portal_language)
676        Code = translate(_('Code'), target_language=portal_language)
677        Title = translate(_('Title'), target_language=portal_language)
678        Cred = translate(_('Cred.'), target_language=portal_language)
679        CC = translate(_('Cat.'), target_language=portal_language)
680        if self.show_results:
681            TotalScore = translate(_('Total Score'), target_language=portal_language)
682            #CA = translate(_('CA'), target_language=portal_language)
683            Grade = translate(_('Grade'), target_language=portal_language)
684        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
685            target_language=portal_language)
686        studentview = StudentBasePDFFormPage(self.context.student,
687            self.request, self.omit_fields)
688        students_utils = getUtility(IStudentsUtils)
689
690        tabledata = []
691        tableheader = []
692        contenttitle = []
693        for i in range(1,7):
694            tabledata.append(sorted(
695                [value for value in self.context.values() if value.semester == i],
696                key=lambda value: str(value.semester) + value.code))
697            if self.show_results:
698                tableheader.append([(Code,'code', 2.0),
699                                   (Title,'title', 7),
700                                   (Cred, 'credits', 1.4),
701                                   (CC, 'course_category', 1.2),
702                                   (TotalScore, 'total_score', 1.4),
703                                   #(CA, 'ca', 1.4),
704                                   (Grade, 'grade', 1.4),
705                                   (Signature, 'dummy', 3),
706                                   ])
707            else:
708                tableheader.append([(Code,'code', 2.0),
709                                   (Title,'title', 7),
710                                   (Cred, 'credits', 1.5),
711                                   (CC, 'course_category', 1.2),
712                                   (Signature, 'dummy', 3),
713                                   ])
714        if len(self.label.split('\n')) == 3:
715            topMargin = 1.9
716        elif len(self.label.split('\n')) == 2:
717            topMargin = 1.7
718        else:
719            topMargin = 1.5
720        return students_utils.renderPDF(
721            self, 'course_registration_slip.pdf',
722            self.context.student, studentview,
723            tableheader=tableheader,
724            tabledata=tabledata,
725            signatures=self._signatures(),
726            topMargin=topMargin,
727            omit_fields=self.omit_fields
728            )
729
730class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
731    """ Page to display the student's transcript.
732    """
733    grok.require('waeup.viewStudent')
734
735class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
736    """Deliver a PDF slip of the context.
737    """
738#    grok.require('waeup.viewStudent')
739
740    note = _("""
741<br /><br /><br /><br />
742<font size='10'>
743<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
744</font>
745""")
746
747    def _sigsInFooter(self):
748        return []
749
750    def _signatures(self):
751        return ([(
752            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
753            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
754
755    def render(self):
756        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
757        Term = translate(_('Sem.'), target_language=portal_language)
758        Code = translate(_('Code'), target_language=portal_language)
759        Title = translate(_('Title'), target_language=portal_language)
760        Cred = translate(_('Credits'), target_language=portal_language)
761        Score = translate(_('Score'), target_language=portal_language)
762        Grade = translate(_('Grade'), target_language=portal_language)
763        studentview = StudentBasePDFFormPage(self.context.student,
764            self.request, self.omit_fields)
765        students_utils = getUtility(IStudentsUtils)
766
767        tableheader = [(Code,'code', 2.5),
768                         (Title,'title', 7),
769                         (Term, 'semester', 1.5),
770                         (Cred, 'credits', 1.5),
771                         (Score, 'total_score', 1.5),
772                         (Grade, 'grade', 1.5),
773                         ]
774
775        pdfstream = students_utils.renderPDFTranscript(
776            self, 'transcript.pdf',
777            self.context.student, studentview,
778            omit_fields=self.omit_fields,
779            tableheader=tableheader,
780            signatures=self._signatures(),
781            sigs_in_footer=self._sigsInFooter(),
782            digital_sigs=self._digital_sigs(),
783            save_file=self._save_file(),
784            )
785        if not pdfstream:
786            self.redirect(self.url(self.context.student))
787            return
788        return pdfstream
789
790class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
791    """Deliver a PDF Admission slip.
792    """
793
794    @property
795    def label(self):
796        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
797        return translate(_('e-Admission Slip \n'),
798            target_language=portal_language) \
799            + ' %s' % self.context.display_fullname
800
801class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
802    """Deliver a PDF slip of the context.
803    """
804
805    @property
806    def label(self):
807        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
808        return translate(_('Verification/Clearance Slip\n'),
809            target_language=portal_language) \
810            + ' %s' % self.context.display_fullname
811
812    @property
813    def form_fields(self):
814        if self.context.is_postgrad:
815            form_fields = grok.AutoFields(
816                ICustomPGStudentClearance).omit('clearance_locked')
817        else:
818            form_fields = grok.AutoFields(
819                ICustomUGStudentClearance).omit('clearance_locked')
820        if not getattr(self.context, 'officer_comment'):
821            form_fields = form_fields.omit('officer_comment')
822        form_fields = form_fields.omit('def_adm')
823        return form_fields
824
825class StudentGetMatricNumberPage(UtilityView, grok.View):
826    """ Construct and set the matriculation number.
827    """
828    grok.context(IStudent)
829    grok.name('get_matric_number')
830    grok.require('waeup.viewStudent')
831
832    def update(self):
833        students_utils = getUtility(IStudentsUtils)
834        msg, mnumber = students_utils.setMatricNumber(self.context)
835        if msg:
836            self.flash(msg, type="danger")
837        else:
838            self.flash(_('Matriculation number %s assigned.' % mnumber))
839            self.context.writeLogMessage(self, '%s assigned' % mnumber)
840        self.redirect(self.url(self.context))
841        return
842
843    def render(self):
844        return
845
846class ExportPDFMatricNumberSlip(UtilityView, grok.View):
847    """Deliver a PDF notification slip.
848    """
849    grok.context(ICustomStudent)
850    grok.name('matric_number_slip.pdf')
851    grok.require('waeup.viewStudent')
852    prefix = 'form'
853
854    form_fields = grok.AutoFields(ICustomStudent).select(
855        'student_id', 'matric_number')
856    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
857
858    @property
859    def title(self):
860        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
861        return translate(_('Matriculation Number'), 'waeup.kofa',
862            target_language=portal_language)
863
864    @property
865    def label(self):
866        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
867        return translate(_('Matriculation Number Slip\n'),
868            target_language=portal_language) \
869            + ' %s' % self.context.display_fullname
870
871    def render(self):
872        if self.context.state not in (PAID,) or not self.context.is_fresh \
873            or not self.context.matric_number:
874            self.flash('Not allowed.', type="danger")
875            self.redirect(self.url(self.context))
876            return
877        students_utils = getUtility(IStudentsUtils)
878        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
879                     'payments have been received and your matriculation ' +
880                     'number generated with details as follows.')
881        return students_utils.renderPDFAdmissionLetter(self,
882            self.context.student, omit_fields=self.omit_fields,
883            pre_text=pre_text, post_text='')
884
885class ExportPersonalDataSlip(UtilityView, grok.View):
886    """Deliver a PDF notification slip.
887    """
888    grok.context(ICustomStudent)
889    grok.name('personal_data_slip.pdf')
890    grok.require('waeup.viewStudent')
891    prefix = 'form'
892    note = None
893
894    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
895    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
896                   'certificate', 'flash_notice')
897
898    @property
899    def title(self):
900        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
901        return translate(_('Personal Data'), 'waeup.kofa',
902            target_language=portal_language)
903
904    @property
905    def label(self):
906        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
907        return translate(_('Personal Data Slip\n'),
908            target_language=portal_language) \
909            + ' %s' % self.context.display_fullname
910
911    def render(self):
912        studentview = StudentBasePDFFormPage(self.context.student,
913            self.request, self.omit_fields)
914        students_utils = getUtility(IStudentsUtils)
915        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
916            self.context.student, studentview, note=self.note,
917            omit_fields=self.omit_fields)
918
919class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
920    """ Page to manage bed tickets.
921    This manage form page is for both students and students officers.
922    """
923    with_hostel_selection = True
924
925class CustomBedTicketAddPage(BedTicketAddPage):
926    with_ac = False
927
928class CustomStudentFilesUploadPage(StudentFilesUploadPage):
929    """ View to upload files by student. Inherit from same class in
930    base package, not from kofacustom.nigeria which
931    requires that no application slip exists.
932    """
933
934class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
935    """ Page to display course tickets
936    """
937
938    @property
939    def show_results(self):
940        isStudent = getattr(
941            self.request.principal, 'user_type', None) == 'student'
942        if isStudent:
943            return False
944        return True
945
946    @property
947    def form_fields(self):
948        if self.show_results:
949            return grok.AutoFields(ICustomCourseTicket)
950        else:
951            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
952
953class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
954    """ Page to manage course tickets
955    """
956    form_fields = grok.AutoFields(ICustomCourseTicket)
957    form_fields['title'].for_display = True
958    form_fields['fcode'].for_display = True
959    form_fields['dcode'].for_display = True
960    form_fields['semester'].for_display = True
961    form_fields['passmark'].for_display = True
962    form_fields['credits'].for_display = True
963    form_fields['mandatory'].for_display = False
964    form_fields['automatic'].for_display = True
965    form_fields['carry_over'].for_display = True
966    form_fields['ticket_session'].for_display = True
967    form_fields['imported_ts'].for_display = True
968
969class CustomEditScoresPage(EditScoresPage):
970    """Page that filters and lists students.
971    """
972    grok.template('editscorespage')
973
974    def _searchCatalog(self, session):
975        cat = queryUtility(ICatalog, name='coursetickets_catalog')
976        coursetickets = cat.searchResults(
977            session=(session, session),
978            code=(self.context.code, self.context.code)
979            )
980        try:
981            score_editing_enabled = grok.getSite()[
982                'configuration'][str(session)].score_editing_enabled
983        except KeyError:
984            return []
985        coursetickets_list = [courseticket for courseticket in coursetickets
986            if courseticket.student.current_mode in score_editing_enabled]
987        return coursetickets_list
988
989    def _extract_uploadfile(self, uploadfile):
990        """Get a mapping of student-ids to scores.
991
992        The mapping is constructed by reading contents from `uploadfile`.
993
994        We expect uploadfile to be a regular CSV file with columns
995        ``student_id``, ``score``, ``imported_ts``
996        and ``ca`` (other cols are ignored).
997        """
998        result = dict()
999        data = StringIO(uploadfile.read())  # ensure we have something seekable
1000        reader = csv.DictReader(data)
1001        for row in reader:
1002            if not ('student_id' in row and 'score' in row and 'ca' in row and
1003                'imported_ts' in row):
1004                continue
1005            result[row['student_id']] = (
1006                row['score'], row['ca'], row['imported_ts'])
1007        return result
1008
1009    def _update_scores(self, form):
1010        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
1011        error = ''
1012        if 'UPDATE_FILE' in form:
1013            if form['uploadfile']:
1014                try:
1015                    formvals = self._extract_uploadfile(form['uploadfile'])
1016                except:
1017                    self.flash(
1018                        _('Uploaded file contains illegal data. Ignored'),
1019                        type="danger")
1020                    return False
1021            else:
1022                self.flash(
1023                    _('No file provided.'), type="danger")
1024                return False
1025        else:
1026            formvals = dict(zip(form['sids'], zip(
1027                form['scores'], form['cas'], form['imported_tss'])))
1028        for ticket in self.editable_tickets:
1029            ticket_error = False
1030            score = ticket.score
1031            ca = ticket.ca
1032            imported_ts = ticket.imported_ts
1033            sid = ticket.student.student_id
1034            if formvals[sid][0] == '':
1035                score = None
1036            if formvals[sid][1] == '':
1037                ca = None
1038            if formvals[sid][2] == '':
1039                imported_ts = None
1040            try:
1041                if formvals[sid][0]:
1042                    score = int(formvals[sid][0])
1043                if formvals[sid][1]:
1044                    ca = int(formvals[sid][1])
1045                if formvals[sid][2]:
1046                    imported_ts = int(formvals[sid][2])
1047            except ValueError:
1048                error += '%s, ' % ticket.student.display_fullname
1049                ticket_error = True
1050            if not ticket_error and ticket.score != score:
1051                try:
1052                    ticket.score = score
1053                except TooBig:
1054                    error += '%s, ' % ticket.student.display_fullname
1055                    ticket_error = True
1056                    pass
1057                ticket.student.__parent__.logger.info(
1058                    '%s - %s %s/%s score updated (%s)' %
1059                    (ob_class, ticket.student.student_id,
1060                     ticket.level, ticket.code, score))
1061            if not ticket_error and ticket.ca != ca:
1062                try:
1063                    ticket.ca = ca
1064                except TooBig:
1065                    error += '%s, ' % ticket.student.display_fullname
1066                    pass
1067                ticket.student.__parent__.logger.info(
1068                    '%s - %s %s/%s ca updated (%s)' %
1069                    (ob_class, ticket.student.student_id,
1070                     ticket.level, ticket.code, ca))
1071            if not ticket_error and ticket.imported_ts != imported_ts:
1072                try:
1073                    ticket.imported_ts = imported_ts
1074                except TooBig:
1075                    error += '%s, ' % ticket.student.display_fullname
1076                    pass
1077                ticket.student.__parent__.logger.info(
1078                    '%s - %s %s/%s imported_ts updated (%s)' %
1079                    (ob_class, ticket.student.student_id,
1080                     ticket.level, ticket.code, imported_ts))
1081        if error:
1082            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
1083              % error.strip(', ')), type="danger")
1084        return True
1085
1086class EditPreviousSessionScoresPage(CustomEditScoresPage):
1087
1088    grok.name('edit_prev_scores')
1089
1090    def update(self,  *args, **kw):
1091        form = self.request.form
1092        self.current_academic_session = grok.getSite()[
1093            'configuration'].current_academic_session
1094        if self.context.__parent__.__parent__.score_editing_disabled:
1095            self.flash(_('Score editing disabled.'), type="warning")
1096            self.redirect(self.url(self.context))
1097            return
1098        if not self.current_academic_session:
1099            self.flash(_('Current academic session not set.'), type="warning")
1100            self.redirect(self.url(self.context))
1101            return
1102        previous_session = self.current_academic_session - 1
1103        self.session_title = academic_sessions_vocab.getTerm(
1104            previous_session).title
1105        self.tickets = self._searchCatalog(previous_session)
1106        if not self.tickets:
1107            self.flash(_('No student found.'), type="warning")
1108            self.redirect(self.url(self.context))
1109            return
1110        self.editable_tickets = [
1111            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1112        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1113            return
1114        if not self.editable_tickets:
1115            return
1116        success = self._update_scores(form)
1117        if success:
1118            self.flash(_('You successfully updated course results.'))
1119        return
1120
1121class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1122    """Deliver a PDF slip of course tickets for a lecturer.
1123    """
1124
1125    note = u'\nUpgraded scores are with asterisks.'
1126
1127    def data(self, session):
1128        #site = grok.getSite()
1129        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1130        coursetickets = cat.searchResults(
1131            session=(session, session),
1132            code=(self.context.code, self.context.code)
1133            )
1134        # Apply filter
1135        try:
1136            score_editing_enabled = grok.getSite()[
1137                'configuration'][str(session)].score_editing_enabled
1138            if checkPermission('waeup.manageAcademics', self.context):
1139                score_editing_enabled = True
1140            coursetickets_filtered = [courseticket
1141                for courseticket in coursetickets
1142                if (checkPermission('waeup.manageAcademics', self.context)
1143                    or (courseticket.student.current_mode in
1144                        score_editing_enabled))
1145                and courseticket.total_score is not None
1146                and courseticket.__parent__.__parent__.is_current]
1147        except KeyError:
1148            coursetickets_filtered = coursetickets
1149        # In AAUE only editable tickets can be printed
1150        editable_tickets = [
1151            ticket for ticket in coursetickets_filtered
1152            if ticket.editable_by_lecturer]
1153        header = [[_(''),
1154                   _('Student Id'),
1155                   _('Matric No.'),
1156                   #_('Reg. No.'),
1157                   #_('Fullname'),
1158                   #_('Status'),
1159                   #_('Course of\nStudies'),
1160                   _('Department'),
1161                   _('Level'),
1162                   _(' CA  '),
1163                   _('Exam\nScore'),
1164                   _('Total '),
1165                   _('Grade'),
1166                   ],]
1167        sorted_tickets = sorted(editable_tickets,
1168            key=lambda ticket: ticket.student.depcode
1169                               + ticket.student.faccode
1170                               + ticket.student.matric_number)
1171        no = 1
1172        tickets = []
1173        passed = 0
1174        failed = 0
1175        with_ca = False
1176        # In AAUE only editable tickets can be printed
1177        for ticket in sorted_tickets:
1178            if ticket.ca > 0:
1179                with_ca = True
1180            total = ticket.total_score
1181            if getattr(ticket, 'imported_ts', None):
1182                total = "**%s**" % ticket.imported_ts
1183            grade = ticket._getGradeWeightFromScore[0]
1184            if grade in ('F', '-'):
1185                failed += 1
1186            else:
1187                passed += 1
1188            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1189            #deptitle = site['faculties'][ticket.student.faccode][
1190            #    ticket.student.depcode].longtitle
1191            row = [str(no),
1192                  ticket.student.student_id,
1193                  ticket.student.matric_number,
1194                  #ticket.student.reg_number,
1195                  #fullname,
1196                  #ticket.student.translated_state,
1197                  #ticket.student.certcode,
1198                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1199                  ticket.level,
1200                  ticket.ca,
1201                  ticket.score,
1202                  total,
1203                  grade,
1204                  ]
1205            tickets.append(row)
1206            no += 1
1207        total = passed + failed
1208        passed_perc = 0
1209        failed_perc = 0
1210        if total:
1211            passed_perc = round(100.0 * passed / total)
1212            failed_perc = round(100.0 * failed / total)
1213        dep = self.context.__parent__.__parent__.longtitle
1214        fac = self.context.__parent__.__parent__.__parent__.longtitle
1215        # remove CA column if not necessary
1216        if not with_ca:
1217            header = [[_(''),
1218                       _('Student Id'),
1219                       _('Matric No.'),
1220                       #_('Reg. No.'),
1221                       #_('Fullname'),
1222                       #_('Status'),
1223                       #_('Course of\nStudies'),
1224                       _('Department'),
1225                       _('Level'),
1226                       #_(' CA  '),
1227                       _('Exam\nScore'),
1228                       _('Total '),
1229                       _('Grade'),
1230                       ],]
1231            for ticket in tickets:
1232                del(ticket[5])
1233        return header + tickets, [
1234            dep, fac, total, passed, passed_perc, failed, failed_perc]
1235
1236    def render(self):
1237        session = grok.getSite()['configuration'].current_academic_session
1238        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1239                     if i['local_role'] == 'waeup.local.Lecturer']
1240        lecturers =  ', '.join(lecturers)
1241        students_utils = getUtility(IStudentsUtils)
1242        # only orientation is different
1243        return students_utils.renderPDFCourseticketsOverview(
1244            self, 'coursetickets',
1245            session, self.data(session), lecturers, '', 45, self.note)
1246
1247class DownloadPreviousSessionScoresView(DownloadScoresView):
1248    """View that exports scores.
1249    """
1250    grok.name('download_prev_scores')
1251
1252    def update(self):
1253        self.current_academic_session = grok.getSite()[
1254            'configuration'].current_academic_session
1255        if self.context.__parent__.__parent__.score_editing_disabled:
1256            self.flash(_('Score editing disabled.'), type="warning")
1257            self.redirect(self.url(self.context))
1258            return
1259        if not self.current_academic_session:
1260            self.flash(_('Current academic session not set.'), type="warning")
1261            self.redirect(self.url(self.context))
1262            return
1263        site = grok.getSite()
1264        exporter = getUtility(ICSVExporter, name='lecturer')
1265        self.csv = exporter.export_filtered(site, filepath=None,
1266                                 catalog='coursetickets',
1267                                 session=self.current_academic_session-1,
1268                                 level=None,
1269                                 code=self.context.code)
1270        return
1271
1272class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1273    """Captcha'd request password page for students.
1274    """
1275    grok.name('alumni_requestpw')
1276    grok.require('waeup.Anonymous')
1277    grok.template('alumni_requestpw')
1278    form_fields = grok.AutoFields(IStudentRequestPW).select(
1279        'lastname','number','email')
1280    label = _('Search student record and send password for first-time login')
1281
1282    def _redirect_no_student(self):
1283        self.flash(_('No student record found.'), type="warning")
1284        self.redirect(self.application_url() + '/applicants/trans2017/register')
1285        return
Note: See TracBrowser for help on using the repository browser.