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

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

Store xml split data in payment ticket.

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