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

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

Adjust to changes in base package.

  • Property svn:keywords set to Id
File size: 52.1 KB
Line 
1## $Id: browser.py 15424 2019-05-24 10:13:52Z 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``, ``imported_ts``
1038        and ``ca`` (other cols are ignored).
1039        """
1040        result = dict()
1041        data = StringIO(uploadfile.read())  # ensure we have something seekable
1042        reader = csv.DictReader(data)
1043        for row in reader:
1044            if not ('student_id' in row and 'score' in row and 'ca' in row and
1045                'imported_ts' in row):
1046                continue
1047            result[row['student_id']] = (
1048                row['score'], row['ca'], row['imported_ts'])
1049        return result
1050
1051    def _update_scores(self, form):
1052        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
1053        error = ''
1054        if 'UPDATE_FILE' in form:
1055            if form['uploadfile']:
1056                try:
1057                    formvals = self._extract_uploadfile(form['uploadfile'])
1058                except:
1059                    self.flash(
1060                        _('Uploaded file contains illegal data. Ignored'),
1061                        type="danger")
1062                    return False
1063            else:
1064                self.flash(
1065                    _('No file provided.'), type="danger")
1066                return False
1067        else:
1068            formvals = dict(zip(form['sids'], zip(
1069                form['scores'], form['cas'], form['imported_tss'])))
1070        for ticket in self.editable_tickets:
1071            ticket_error = False
1072            score = ticket.score
1073            ca = ticket.ca
1074            imported_ts = ticket.imported_ts
1075            sid = ticket.student.student_id
1076            if formvals[sid][0] == '':
1077                score = None
1078            if formvals[sid][1] == '':
1079                ca = None
1080            if formvals[sid][2] == '':
1081                imported_ts = None
1082            try:
1083                if formvals[sid][0]:
1084                    score = int(formvals[sid][0])
1085                if formvals[sid][1]:
1086                    ca = int(formvals[sid][1])
1087                if formvals[sid][2]:
1088                    imported_ts = int(formvals[sid][2])
1089            except ValueError:
1090                error += '%s, ' % ticket.student.display_fullname
1091                ticket_error = True
1092            if not ticket_error and ticket.score != score:
1093                try:
1094                    ticket.score = score
1095                except TooBig:
1096                    error += '%s, ' % ticket.student.display_fullname
1097                    ticket_error = True
1098                    pass
1099                ticket.student.__parent__.logger.info(
1100                    '%s - %s %s/%s score updated (%s)' %
1101                    (ob_class, ticket.student.student_id,
1102                     ticket.level, ticket.code, score))
1103            if not ticket_error and ticket.ca != ca:
1104                try:
1105                    ticket.ca = ca
1106                except TooBig:
1107                    error += '%s, ' % ticket.student.display_fullname
1108                    pass
1109                ticket.student.__parent__.logger.info(
1110                    '%s - %s %s/%s ca updated (%s)' %
1111                    (ob_class, ticket.student.student_id,
1112                     ticket.level, ticket.code, ca))
1113            if not ticket_error and ticket.imported_ts != imported_ts:
1114                try:
1115                    ticket.imported_ts = imported_ts
1116                except TooBig:
1117                    error += '%s, ' % ticket.student.display_fullname
1118                    pass
1119                ticket.student.__parent__.logger.info(
1120                    '%s - %s %s/%s imported_ts updated (%s)' %
1121                    (ob_class, ticket.student.student_id,
1122                     ticket.level, ticket.code, imported_ts))
1123        if error:
1124            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
1125              % error.strip(', ')), type="danger")
1126        return True
1127
1128class EditPreviousSessionScoresPage(CustomEditScoresPage):
1129
1130    grok.name('edit_prev_scores')
1131
1132    def update(self,  *args, **kw):
1133        form = self.request.form
1134        self.current_academic_session = grok.getSite()[
1135            'configuration'].current_academic_session
1136        if self.context.__parent__.__parent__.score_editing_disabled:
1137            self.flash(_('Score editing disabled.'), type="warning")
1138            self.redirect(self.url(self.context))
1139            return
1140        if not self.current_academic_session:
1141            self.flash(_('Current academic session not set.'), type="warning")
1142            self.redirect(self.url(self.context))
1143            return
1144        previous_session = self.current_academic_session - 1
1145        self.session_title = academic_sessions_vocab.getTerm(
1146            previous_session).title
1147        self.tickets = self._searchCatalog(previous_session)
1148        if not self.tickets:
1149            self.flash(_('No student found.'), type="warning")
1150            self.redirect(self.url(self.context))
1151            return
1152        self.editable_tickets = [
1153            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1154        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1155            return
1156        if not self.editable_tickets:
1157            return
1158        success = self._update_scores(form)
1159        if success:
1160            self.flash(_('You successfully updated course results.'))
1161        return
1162
1163class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1164    """Deliver a PDF slip of course tickets for a lecturer.
1165    """
1166
1167    note = u'\nUpgraded scores are with asterisks.'
1168
1169    def data(self, session):
1170        #site = grok.getSite()
1171        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1172        coursetickets = cat.searchResults(
1173            session=(session, session),
1174            code=(self.context.code, self.context.code)
1175            )
1176        # Apply filter
1177        try:
1178            score_editing_enabled = grok.getSite()[
1179                'configuration'][str(session)].score_editing_enabled
1180            coursetickets_filtered = [courseticket
1181                for courseticket in coursetickets
1182                if courseticket.student.current_mode in score_editing_enabled
1183                and courseticket.total_score is not None
1184                and courseticket.__parent__.__parent__.is_current]
1185        except KeyError:
1186            coursetickets_filtered = coursetickets
1187        # In AAUE only editable tickets can be printed
1188        editable_tickets = [
1189            ticket for ticket in coursetickets_filtered
1190            if ticket.editable_by_lecturer]
1191        header = [[_(''),
1192                   _('Student Id'),
1193                   _('Matric No.'),
1194                   #_('Reg. No.'),
1195                   #_('Fullname'),
1196                   #_('Status'),
1197                   #_('Course of\nStudies'),
1198                   _('Department'),
1199                   _('Level'),
1200                   _(' CA  '),
1201                   _('Exam\nScore'),
1202                   _('Total '),
1203                   _('Grade'),
1204                   ],]
1205        sorted_tickets = sorted(editable_tickets,
1206            key=lambda ticket: ticket.student.depcode
1207                               + ticket.student.faccode
1208                               + ticket.student.matric_number)
1209        no = 1
1210        tickets = []
1211        passed = 0
1212        failed = 0
1213        with_ca = False
1214        # In AAUE only editable tickets can be printed
1215        for ticket in sorted_tickets:
1216            if ticket.ca > 0:
1217                with_ca = True
1218            total = ticket.total_score
1219            if getattr(ticket, 'imported_ts', None):
1220                total = "**%s**" % ticket.imported_ts
1221            grade = ticket._getGradeWeightFromScore[0]
1222            if grade in ('F', '-'):
1223                failed += 1
1224            else:
1225                passed += 1
1226            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1227            #deptitle = site['faculties'][ticket.student.faccode][
1228            #    ticket.student.depcode].longtitle
1229            row = [str(no),
1230                  ticket.student.student_id,
1231                  ticket.student.matric_number,
1232                  #ticket.student.reg_number,
1233                  #fullname,
1234                  #ticket.student.translated_state,
1235                  #ticket.student.certcode,
1236                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1237                  ticket.level,
1238                  ticket.ca,
1239                  ticket.score,
1240                  total,
1241                  grade,
1242                  ]
1243            tickets.append(row)
1244            no += 1
1245        total = passed + failed
1246        passed_perc = 0
1247        failed_perc = 0
1248        if total:
1249            passed_perc = round(100.0 * passed / total)
1250            failed_perc = round(100.0 * failed / total)
1251        dep = self.context.__parent__.__parent__.longtitle
1252        fac = self.context.__parent__.__parent__.__parent__.longtitle
1253        # remove CA column if not necessary
1254        if not with_ca:
1255            header = [[_(''),
1256                       _('Student Id'),
1257                       _('Matric No.'),
1258                       #_('Reg. No.'),
1259                       #_('Fullname'),
1260                       #_('Status'),
1261                       #_('Course of\nStudies'),
1262                       _('Department'),
1263                       _('Level'),
1264                       #_(' CA  '),
1265                       _('Exam\nScore'),
1266                       _('Total '),
1267                       _('Grade'),
1268                       ],]
1269            for ticket in tickets:
1270                del(ticket[5])
1271        return header + tickets, [
1272            dep, fac, total, passed, passed_perc, failed, failed_perc]
1273
1274    def render(self):
1275        session = grok.getSite()['configuration'].current_academic_session
1276        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1277                     if i['local_role'] == 'waeup.local.Lecturer']
1278        lecturers =  ', '.join(lecturers)
1279        students_utils = getUtility(IStudentsUtils)
1280        # only orientation is different
1281        return students_utils.renderPDFCourseticketsOverview(
1282            self, 'coursetickets',
1283            session, self.data(session), lecturers, '', 45, self.note)
1284
1285class DownloadPreviousSessionScoresView(DownloadScoresView):
1286    """View that exports scores.
1287    """
1288    grok.name('download_prev_scores')
1289
1290    def update(self):
1291        self.current_academic_session = grok.getSite()[
1292            'configuration'].current_academic_session
1293        if self.context.__parent__.__parent__.score_editing_disabled:
1294            self.flash(_('Score editing disabled.'), type="warning")
1295            self.redirect(self.url(self.context))
1296            return
1297        if not self.current_academic_session:
1298            self.flash(_('Current academic session not set.'), type="warning")
1299            self.redirect(self.url(self.context))
1300            return
1301        site = grok.getSite()
1302        exporter = getUtility(ICSVExporter, name='lecturer')
1303        self.csv = exporter.export_filtered(site, filepath=None,
1304                                 catalog='coursetickets',
1305                                 session=self.current_academic_session-1,
1306                                 level=None,
1307                                 code=self.context.code)
1308        return
1309
1310class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1311    """Captcha'd request password page for students.
1312    """
1313    grok.name('alumni_requestpw')
1314    grok.require('waeup.Anonymous')
1315    grok.template('alumni_requestpw')
1316    form_fields = grok.AutoFields(IStudentRequestPW).select(
1317        'lastname','number','email')
1318    label = _('Search student record and send password for first-time login')
1319
1320    def _redirect_no_student(self):
1321        self.flash(_('No student record found.'), type="warning")
1322        self.redirect(self.application_url() + '/applicants/trans2017/register')
1323        return
Note: See TracBrowser for help on using the repository browser.