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

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

Disable repair function if studycourse is locked.

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