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

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

Students in all states can see the results.

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