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

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

Restrict study modes ug_dsh and de_dsh from paying some following items.

  • Property svn:keywords set to Id
File size: 51.2 KB
Line 
1## $Id: browser.py 15388 2019-04-05 12:25:30Z 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
633    @property
634    def show_results(self):
635        isStudent = getattr(
636            self.request.principal, 'user_type', None) == 'student'
637        try:
638            show_results = grok.getSite()[
639                'configuration'][str(self.context.level_session)].show_results
640        except KeyError:
641            return False
642        if isStudent and self.context.student.current_mode not in show_results:
643            return False
644        if isStudent and self.context.student.state != RETURNING \
645            and self.context.student.current_level == self.context.level:
646            return False
647        return True
648
649    def update(self):
650        if self.context.student.state != REGISTERED \
651            and self.context.student.current_level == self.context.level:
652            self.flash(_('Forbidden'), type="warning")
653            self.redirect(self.url(self.context))
654            return
655
656    @property
657    def label(self):
658        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
659        lang = self.request.cookies.get('kofa.language', portal_language)
660        level_title = translate(self.context.level_title, 'waeup.kofa',
661            target_language=lang)
662        line0 = ''
663        if self.context.student.is_postgrad:
664            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
665        elif self.context.student.current_mode.endswith('_pt'):
666            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
667        line1 = translate(_('Course Registration Slip'),
668            target_language=portal_language) \
669            + ' %s' % level_title
670        line2 = translate(_('Session'),
671            target_language=portal_language) \
672            + ' %s' % self.context.getSessionString
673        return '%s%s\n%s' % (line0, line1, line2)
674
675    @property
676    def title(self):
677        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
678        return translate(_('Units Registered'), target_language=portal_language)
679
680    def _signatures(self):
681        if self.context.student.current_mode.endswith('_pt') \
682            or self.context.student.current_mode == 'found':
683            return (
684                [('I have selected the course on the advise of my Head of '
685                 'Department. <br>', _('Student\'s Signature'), '<br>')],
686                [('This student has satisfied the department\'s requirements. '
687                 'I recommend to approve the course registration. <br>',
688                 _('Head of Department\'s Signature'), '<br>')],
689                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
690                [('', _('Director\'s Signature'))]
691                )
692        if self.context.student.current_mode in (
693            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
694            return ([_('Academic Adviser\'s Signature'),
695                _('Faculty Officer\'s Signature'),
696                _('Student\'s Signature')],)
697
698        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
699            return (
700                [('I declare that all items of information supplied above are correct:' ,
701                    _('Student\'s Signature'), '<br>')],
702                [('We approved the above registration:',
703                    _('Major Supervisor (Name / Signature)'), '')],
704                [('', _('Co-Supervisor (Name / Signature)'), '')],
705                [('', _('Head of Department'), '<br>')],
706                [('The student has satisfied the conditions for renewal of '
707                  'registration for graduate school programme in this university:',
708                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
709                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
710                )
711        return None
712
713
714    def render(self):
715        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
716        Sem = translate(_('Sem.'), target_language=portal_language)
717        Code = translate(_('Code'), target_language=portal_language)
718        Title = translate(_('Title'), target_language=portal_language)
719        Cred = translate(_('Cred.'), target_language=portal_language)
720        CC = translate(_('Cat.'), target_language=portal_language)
721        if self.show_results:
722            TotalScore = translate(_('Total Score'), target_language=portal_language)
723            #CA = translate(_('CA'), target_language=portal_language)
724            Grade = translate(_('Grade'), target_language=portal_language)
725        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
726            target_language=portal_language)
727        studentview = StudentBasePDFFormPage(self.context.student,
728            self.request, self.omit_fields)
729        students_utils = getUtility(IStudentsUtils)
730
731        tabledata = []
732        tableheader = []
733        contenttitle = []
734        for i in range(1,7):
735            tabledata.append(sorted(
736                [value for value in self.context.values() if value.semester == i],
737                key=lambda value: str(value.semester) + value.code))
738            if self.show_results:
739                tableheader.append([(Code,'code', 2.0),
740                                   (Title,'title', 7),
741                                   (Cred, 'credits', 1.4),
742                                   (CC, 'course_category', 1.2),
743                                   (TotalScore, 'total_score', 1.4),
744                                   #(CA, 'ca', 1.4),
745                                   (Grade, 'grade', 1.4),
746                                   (Signature, 'dummy', 3),
747                                   ])
748            else:
749                tableheader.append([(Code,'code', 2.0),
750                                   (Title,'title', 7),
751                                   (Cred, 'credits', 1.5),
752                                   (CC, 'course_category', 1.2),
753                                   (Signature, 'dummy', 3),
754                                   ])
755        if len(self.label.split('\n')) == 3:
756            topMargin = 1.9
757        elif len(self.label.split('\n')) == 2:
758            topMargin = 1.7
759        else:
760            topMargin = 1.5
761        return students_utils.renderPDF(
762            self, 'course_registration_slip.pdf',
763            self.context.student, studentview,
764            tableheader=tableheader,
765            tabledata=tabledata,
766            signatures=self._signatures(),
767            topMargin=topMargin,
768            omit_fields=self.omit_fields
769            )
770
771class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
772    """ Page to display the student's transcript.
773    """
774    grok.require('waeup.viewStudent')
775
776class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
777    """Deliver a PDF slip of the context.
778    """
779#    grok.require('waeup.viewStudent')
780
781    note = _("""
782<br /><br /><br /><br />
783<font size='10'>
784<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
785</font>
786""")
787
788    def _sigsInFooter(self):
789        return []
790
791    def _signatures(self):
792        return ([(
793            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
794            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
795
796    def render(self):
797        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
798        Term = translate(_('Sem.'), target_language=portal_language)
799        Code = translate(_('Code'), target_language=portal_language)
800        Title = translate(_('Title'), target_language=portal_language)
801        Cred = translate(_('Credits'), target_language=portal_language)
802        Score = translate(_('Score'), target_language=portal_language)
803        Grade = translate(_('Grade'), target_language=portal_language)
804        studentview = StudentBasePDFFormPage(self.context.student,
805            self.request, self.omit_fields)
806        students_utils = getUtility(IStudentsUtils)
807
808        tableheader = [(Code,'code', 2.5),
809                         (Title,'title', 7),
810                         (Term, 'semester', 1.5),
811                         (Cred, 'credits', 1.5),
812                         (Score, 'total_score', 1.5),
813                         (Grade, 'grade', 1.5),
814                         ]
815
816        pdfstream = students_utils.renderPDFTranscript(
817            self, 'transcript.pdf',
818            self.context.student, studentview,
819            omit_fields=self.omit_fields,
820            tableheader=tableheader,
821            signatures=self._signatures(),
822            sigs_in_footer=self._sigsInFooter(),
823            digital_sigs=self._digital_sigs(),
824            save_file=self._save_file(),
825            )
826        if not pdfstream:
827            self.redirect(self.url(self.context.student))
828            return
829        return pdfstream
830
831class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
832    """Deliver a PDF Admission slip.
833    """
834
835    @property
836    def label(self):
837        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
838        return translate(_('e-Admission Slip \n'),
839            target_language=portal_language) \
840            + ' %s' % self.context.display_fullname
841
842class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
843    """Deliver a PDF slip of the context.
844    """
845
846    @property
847    def label(self):
848        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
849        return translate(_('Verification/Clearance Slip\n'),
850            target_language=portal_language) \
851            + ' %s' % self.context.display_fullname
852
853    @property
854    def form_fields(self):
855        if self.context.is_postgrad:
856            form_fields = grok.AutoFields(
857                ICustomPGStudentClearance).omit('clearance_locked')
858        else:
859            form_fields = grok.AutoFields(
860                ICustomUGStudentClearance).omit('clearance_locked')
861        if not getattr(self.context, 'officer_comment'):
862            form_fields = form_fields.omit('officer_comment')
863        form_fields = form_fields.omit('def_adm')
864        return form_fields
865
866class StudentGetMatricNumberPage(UtilityView, grok.View):
867    """ Construct and set the matriculation number.
868    """
869    grok.context(IStudent)
870    grok.name('get_matric_number')
871    grok.require('waeup.viewStudent')
872
873    def update(self):
874        students_utils = getUtility(IStudentsUtils)
875        msg, mnumber = students_utils.setMatricNumber(self.context)
876        if msg:
877            self.flash(msg, type="danger")
878        else:
879            self.flash(_('Matriculation number %s assigned.' % mnumber))
880            self.context.writeLogMessage(self, '%s assigned' % mnumber)
881        self.redirect(self.url(self.context))
882        return
883
884    def render(self):
885        return
886
887class ExportPDFMatricNumberSlip(UtilityView, grok.View):
888    """Deliver a PDF notification slip.
889    """
890    grok.context(ICustomStudent)
891    grok.name('matric_number_slip.pdf')
892    grok.require('waeup.viewStudent')
893    prefix = 'form'
894
895    form_fields = grok.AutoFields(ICustomStudent).select(
896        'student_id', 'matric_number')
897    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
898
899    @property
900    def title(self):
901        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
902        return translate(_('Matriculation Number'), 'waeup.kofa',
903            target_language=portal_language)
904
905    @property
906    def label(self):
907        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
908        return translate(_('Matriculation Number Slip\n'),
909            target_language=portal_language) \
910            + ' %s' % self.context.display_fullname
911
912    def render(self):
913        if self.context.state not in (PAID,) or not self.context.is_fresh \
914            or not self.context.matric_number:
915            self.flash('Not allowed.', type="danger")
916            self.redirect(self.url(self.context))
917            return
918        students_utils = getUtility(IStudentsUtils)
919        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
920                     'payments have been received and your matriculation ' +
921                     'number generated with details as follows.')
922        return students_utils.renderPDFAdmissionLetter(self,
923            self.context.student, omit_fields=self.omit_fields,
924            pre_text=pre_text, post_text='')
925
926class ExportPersonalDataSlip(UtilityView, grok.View):
927    """Deliver a PDF notification slip.
928    """
929    grok.context(ICustomStudent)
930    grok.name('personal_data_slip.pdf')
931    grok.require('waeup.viewStudent')
932    prefix = 'form'
933    note = None
934
935    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
936    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
937                   'certificate', 'flash_notice')
938
939    @property
940    def title(self):
941        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
942        return translate(_('Personal Data'), 'waeup.kofa',
943            target_language=portal_language)
944
945    @property
946    def label(self):
947        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
948        return translate(_('Personal Data Slip\n'),
949            target_language=portal_language) \
950            + ' %s' % self.context.display_fullname
951
952    def render(self):
953        studentview = StudentBasePDFFormPage(self.context.student,
954            self.request, self.omit_fields)
955        students_utils = getUtility(IStudentsUtils)
956        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
957            self.context.student, studentview, note=self.note,
958            omit_fields=self.omit_fields)
959
960class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
961    """ Page to manage bed tickets.
962    This manage form page is for both students and students officers.
963    """
964    with_hostel_selection = True
965
966class CustomBedTicketAddPage(BedTicketAddPage):
967    with_ac = False
968
969class CustomStudentFilesUploadPage(StudentFilesUploadPage):
970    """ View to upload files by student. Inherit from same class in
971    base package, not from kofacustom.nigeria which
972    requires that no application slip exists.
973    """
974
975class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
976    """ Page to display course tickets
977    """
978
979    @property
980    def show_results(self):
981        isStudent = getattr(
982            self.request.principal, 'user_type', None) == 'student'
983        if isStudent:
984            return False
985        return True
986
987    @property
988    def form_fields(self):
989        if self.show_results:
990            return grok.AutoFields(ICustomCourseTicket)
991        else:
992            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
993
994class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
995    """ Page to manage course tickets
996    """
997    form_fields = grok.AutoFields(ICustomCourseTicket)
998    form_fields['title'].for_display = True
999    form_fields['fcode'].for_display = True
1000    form_fields['dcode'].for_display = True
1001    form_fields['semester'].for_display = True
1002    form_fields['passmark'].for_display = True
1003    form_fields['credits'].for_display = True
1004    form_fields['mandatory'].for_display = False
1005    form_fields['automatic'].for_display = True
1006    form_fields['carry_over'].for_display = True
1007    form_fields['ticket_session'].for_display = True
1008    form_fields['imported_ts'].for_display = True
1009
1010class CustomEditScoresPage(EditScoresPage):
1011    """Page that filters and lists students.
1012    """
1013    grok.template('editscorespage')
1014
1015    def _searchCatalog(self, session):
1016        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1017        coursetickets = cat.searchResults(
1018            session=(session, session),
1019            code=(self.context.code, self.context.code)
1020            )
1021        try:
1022            score_editing_enabled = grok.getSite()[
1023                'configuration'][str(session)].score_editing_enabled
1024        except KeyError:
1025            return []
1026        coursetickets_list = [courseticket for courseticket in coursetickets
1027            if courseticket.student.current_mode in score_editing_enabled]
1028        return coursetickets_list
1029
1030    def _extract_uploadfile(self, uploadfile):
1031        """Get a mapping of student-ids to scores.
1032
1033        The mapping is constructed by reading contents from `uploadfile`.
1034
1035        We expect uploadfile to be a regular CSV file with columns
1036        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
1037        """
1038        result = dict()
1039        data = StringIO(uploadfile.read())  # ensure we have something seekable
1040        reader = csv.DictReader(data)
1041        for row in reader:
1042            if not ('student_id' in row and 'score' in row and 'ca' in row):
1043                continue
1044            result[row['student_id']] = (row['score'], row['ca'])
1045        return result
1046
1047    def _update_scores(self, form):
1048        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
1049        error = ''
1050        if 'UPDATE_FILE' in form:
1051            if form['uploadfile']:
1052                try:
1053                    formvals = self._extract_uploadfile(form['uploadfile'])
1054                except:
1055                    self.flash(
1056                        _('Uploaded file contains illegal data. Ignored'),
1057                        type="danger")
1058                    return False
1059            else:
1060                self.flash(
1061                    _('No file provided.'), type="danger")
1062                return False
1063        else:
1064            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
1065        for ticket in self.editable_tickets:
1066            ticket_error = False
1067            score = ticket.score
1068            ca = ticket.ca
1069            sid = ticket.student.student_id
1070            if formvals[sid][0] == '':
1071                score = None
1072            if formvals[sid][1] == '':
1073                ca = None
1074            try:
1075                if formvals[sid][0]:
1076                    score = int(formvals[sid][0])
1077                if formvals[sid][1]:
1078                    ca = int(formvals[sid][1])
1079            except ValueError:
1080                error += '%s, ' % ticket.student.display_fullname
1081                ticket_error = True
1082            if not ticket_error and ticket.score != score:
1083                try:
1084                    ticket.score = score
1085                except TooBig:
1086                    error += '%s, ' % ticket.student.display_fullname
1087                    ticket_error = True
1088                    pass
1089                ticket.student.__parent__.logger.info(
1090                    '%s - %s %s/%s score updated (%s)' %
1091                    (ob_class, ticket.student.student_id,
1092                     ticket.level, ticket.code, score))
1093            if not ticket_error and ticket.ca != ca:
1094                try:
1095                    ticket.ca = ca
1096                except TooBig:
1097                    error += '%s, ' % ticket.student.display_fullname
1098                    pass
1099                ticket.student.__parent__.logger.info(
1100                    '%s - %s %s/%s ca updated (%s)' %
1101                    (ob_class, ticket.student.student_id,
1102                     ticket.level, ticket.code, ca))
1103        if error:
1104            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
1105              % error.strip(', ')), type="danger")
1106        return True
1107
1108class EditPreviousSessionScoresPage(CustomEditScoresPage):
1109
1110    grok.name('edit_prev_scores')
1111
1112    def update(self,  *args, **kw):
1113        form = self.request.form
1114        self.current_academic_session = grok.getSite()[
1115            'configuration'].current_academic_session
1116        if self.context.__parent__.__parent__.score_editing_disabled:
1117            self.flash(_('Score editing disabled.'), type="warning")
1118            self.redirect(self.url(self.context))
1119            return
1120        if not self.current_academic_session:
1121            self.flash(_('Current academic session not set.'), type="warning")
1122            self.redirect(self.url(self.context))
1123            return
1124        previous_session = self.current_academic_session - 1
1125        self.session_title = academic_sessions_vocab.getTerm(
1126            previous_session).title
1127        self.tickets = self._searchCatalog(previous_session)
1128        if not self.tickets:
1129            self.flash(_('No student found.'), type="warning")
1130            self.redirect(self.url(self.context))
1131            return
1132        self.editable_tickets = [
1133            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1134        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1135            return
1136        if not self.editable_tickets:
1137            return
1138        success = self._update_scores(form)
1139        if success:
1140            self.flash(_('You successfully updated course results.'))
1141        return
1142
1143class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1144    """Deliver a PDF slip of course tickets for a lecturer.
1145    """
1146
1147    note = u'\nUpgraded scores are with asterisks.'
1148
1149    def data(self, session):
1150        #site = grok.getSite()
1151        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1152        coursetickets = cat.searchResults(
1153            session=(session, session),
1154            code=(self.context.code, self.context.code)
1155            )
1156        # Apply filter
1157        try:
1158            score_editing_enabled = grok.getSite()[
1159                'configuration'][str(session)].score_editing_enabled
1160            coursetickets_filtered = [courseticket
1161                for courseticket in coursetickets
1162                if courseticket.student.current_mode in score_editing_enabled
1163                and courseticket.total_score is not None
1164                and courseticket.__parent__.__parent__.is_current]
1165        except KeyError:
1166            coursetickets_filtered = coursetickets
1167        # In AAUE only editable tickets can be printed
1168        editable_tickets = [
1169            ticket for ticket in coursetickets_filtered
1170            if ticket.editable_by_lecturer]
1171        header = [[_(''),
1172                   _('Student Id'),
1173                   _('Matric No.'),
1174                   #_('Reg. No.'),
1175                   #_('Fullname'),
1176                   #_('Status'),
1177                   #_('Course of\nStudies'),
1178                   _('Department'),
1179                   _('Level'),
1180                   _(' CA  '),
1181                   _('Exam\nScore'),
1182                   _('Total '),
1183                   _('Grade'),
1184                   ],]
1185        sorted_tickets = sorted(editable_tickets,
1186            key=lambda ticket: ticket.student.depcode
1187                               + ticket.student.faccode
1188                               + ticket.student.matric_number)
1189        no = 1
1190        tickets = []
1191        passed = 0
1192        failed = 0
1193        with_ca = False
1194        # In AAUE only editable tickets can be printed
1195        for ticket in sorted_tickets:
1196            if ticket.ca > 0:
1197                with_ca = True
1198            total = ticket.total_score
1199            if getattr(ticket, 'imported_ts', None):
1200                total = "**%s**" % ticket.imported_ts
1201            grade = ticket._getGradeWeightFromScore[0]
1202            if grade in ('F', '-'):
1203                failed += 1
1204            else:
1205                passed += 1
1206            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1207            #deptitle = site['faculties'][ticket.student.faccode][
1208            #    ticket.student.depcode].longtitle
1209            row = [str(no),
1210                  ticket.student.student_id,
1211                  ticket.student.matric_number,
1212                  #ticket.student.reg_number,
1213                  #fullname,
1214                  #ticket.student.translated_state,
1215                  #ticket.student.certcode,
1216                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1217                  ticket.level,
1218                  ticket.ca,
1219                  ticket.score,
1220                  total,
1221                  grade,
1222                  ]
1223            tickets.append(row)
1224            no += 1
1225        total = passed + failed
1226        passed_perc = 0
1227        failed_perc = 0
1228        if total:
1229            passed_perc = 100 * passed / total
1230            failed_perc = 100 * failed / total
1231        dep = self.context.__parent__.__parent__.longtitle
1232        fac = self.context.__parent__.__parent__.__parent__.longtitle
1233        # remove CA column if not necessary
1234        if not with_ca:
1235            header = [[_(''),
1236                       _('Student Id'),
1237                       _('Matric No.'),
1238                       #_('Reg. No.'),
1239                       #_('Fullname'),
1240                       #_('Status'),
1241                       #_('Course of\nStudies'),
1242                       _('Department'),
1243                       _('Level'),
1244                       #_(' CA  '),
1245                       _('Exam\nScore'),
1246                       _('Total '),
1247                       _('Grade'),
1248                       ],]
1249            for ticket in tickets:
1250                del(ticket[5])
1251        return header + tickets, [
1252            dep, fac, total, passed, passed_perc, failed, failed_perc]
1253
1254    def render(self):
1255        session = grok.getSite()['configuration'].current_academic_session
1256        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1257                     if i['local_role'] == 'waeup.local.Lecturer']
1258        lecturers =  ', '.join(lecturers)
1259        students_utils = getUtility(IStudentsUtils)
1260        # only orientation is different
1261        return students_utils.renderPDFCourseticketsOverview(
1262            self, session, self.data(session), lecturers, '', 45, self.note)
1263
1264class DownloadPreviousSessionScoresView(DownloadScoresView):
1265    """View that exports scores.
1266    """
1267    grok.name('download_prev_scores')
1268
1269    def update(self):
1270        self.current_academic_session = grok.getSite()[
1271            'configuration'].current_academic_session
1272        if self.context.__parent__.__parent__.score_editing_disabled:
1273            self.flash(_('Score editing disabled.'), type="warning")
1274            self.redirect(self.url(self.context))
1275            return
1276        if not self.current_academic_session:
1277            self.flash(_('Current academic session not set.'), type="warning")
1278            self.redirect(self.url(self.context))
1279            return
1280        site = grok.getSite()
1281        exporter = getUtility(ICSVExporter, name='lecturer')
1282        self.csv = exporter.export_filtered(site, filepath=None,
1283                                 catalog='coursetickets',
1284                                 session=self.current_academic_session-1,
1285                                 level=None,
1286                                 code=self.context.code)
1287        return
1288
1289class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1290    """Captcha'd request password page for students.
1291    """
1292    grok.name('alumni_requestpw')
1293    grok.require('waeup.Anonymous')
1294    grok.template('alumni_requestpw')
1295    form_fields = grok.AutoFields(IStudentRequestPW).select(
1296        'lastname','number','email')
1297    label = _('Search student record and send password for first-time login')
1298
1299    def _redirect_no_student(self):
1300        self.flash(_('No student record found.'), type="warning")
1301        self.redirect(self.application_url() + '/applicants/trans2017/register')
1302        return
Note: See TracBrowser for help on using the repository browser.