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

Last change on this file since 17437 was 17431, checked in by Henrik Bettermann, 19 months ago

Rewrite payment configuration.

  • Property svn:keywords set to Id
File size: 54.3 KB
Line 
1## $Id: browser.py 17431 2023-06-16 11:43:53Z 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, GRADUATED
38from waeup.kofa.students.browser import (
39    StartClearancePage,
40    StudentBasePDFFormPage,
41    CourseTicketAddFormPage,
42    StudyLevelDisplayFormPage,
43    StudyLevelManageFormPage,
44    StudyLevelEditFormPage,
45    ExportPDFTranscriptSlip,
46    ExportPDFAdmissionSlip,
47    StudentFilesUploadPage,
48    PaymentsManageFormPage,
49    CourseTicketDisplayFormPage,
50    CourseTicketManageFormPage,
51    EditScoresPage,
52    ExportPDFScoresSlip,
53    StudyCourseTranscriptPage,
54    DownloadScoresView,
55    StudentRequestPasswordPage,
56    StudyCourseManageFormPage,
57    UnregisterCoursesView,
58    addCourseTicket,
59    AddStudyLevelFormPage,
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    NigeriaAccommodationDisplayFormPage,
76    NigeriaStudentBaseDisplayFormPage,
77    NigeriaStudentBaseManageFormPage,
78    NigeriaBedTicketAddPage
79    )
80from waeup.aaue.students.interfaces import (
81    ICustomStudentOnlinePayment,
82    ICustomStudentStudyLevel,
83    ICustomStudent,
84    ICustomStudentPersonal,
85    ICustomStudentPersonalEdit,
86    ICustomUGStudentClearance,
87    ICustomUGStudentClearanceEdit,
88    ICustomPGStudentClearance,
89    ICustomCourseTicket,
90    ICustomStudentBase,
91    ICustomStudentStudyCourse)
92from waeup.aaue.interfaces import MessageFactory as _
93
94grok.context(IKofaObject)  # Make IKofaObject the default context
95
96def translated_values(view):
97    """Translate course ticket attribute values to be displayed on
98    studylevel pages.
99    """
100    lang = view.request.cookies.get('kofa.language')
101    for value in view.context.values():
102        value._p_activate()
103        value_dict = dict([i for i in value.__dict__.items()])
104        value_dict['url'] = view.url(value)
105        value_dict['removable_by_student'] = value.removable_by_student
106        value_dict['mandatory'] = translate(str(value.mandatory), 'zope',
107            target_language=lang)
108        value_dict['carry_over'] = translate(str(value.carry_over), 'zope',
109            target_language=lang)
110        value_dict['outstanding'] = translate(str(value.outstanding), 'zope',
111            target_language=lang)
112        value_dict['automatic'] = translate(str(value.automatic), 'zope',
113            target_language=lang)
114        value_dict['grade'] = value.grade
115        value_dict['weight'] = value.weight
116        value_dict['course_category'] = value.course_category
117        value_dict['total_score'] = value.total_score
118        semester_dict = getUtility(IKofaUtils).SEMESTER_DICT
119        value_dict['semester'] = semester_dict[
120            value.semester].replace('mester', 'm.')
121        # AAUE specific
122        value_dict['formatted_total_score'] = value.total_score
123        if getattr(value, 'imported_ts', None):
124            value_dict['formatted_total_score'] = "<strong>%s</strong>" % value.imported_ts
125        yield value_dict
126
127class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
128    """ Page to display student base data
129    """
130    form_fields = grok.AutoFields(ICustomStudentBase).omit(
131        'password', 'suspended', 'suspended_comment', 'flash_notice')
132    form_fields[
133        'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
134
135class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
136    """ View to manage student base data
137    """
138    form_fields = grok.AutoFields(ICustomStudentBase).omit(
139        'student_id', 'adm_code', 'suspended',
140        'financially_cleared_by', 'financial_clearance_date')
141
142class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
143    """ Page to display student personal data
144    """
145    form_fields = grok.AutoFields(ICustomStudentPersonal)
146    form_fields['perm_address'].custom_widget = BytesDisplayWidget
147    form_fields['father_address'].custom_widget = BytesDisplayWidget
148    form_fields['mother_address'].custom_widget = BytesDisplayWidget
149    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
150    form_fields[
151        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
152
153class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
154    """ Page to edit personal data
155    """
156    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated')
157
158class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
159    """ Page to edit personal data
160    """
161    form_fields = grok.AutoFields(ICustomStudentPersonal)
162    form_fields['personal_updated'].for_display = True
163    form_fields[
164        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
165
166
167class ExportExaminationScheduleSlip(UtilityView, grok.View):
168    """Deliver a examination schedule slip.
169
170    This form page is available only in Uniben and AAUE.
171    """
172    grok.context(ICustomStudent)
173    grok.name('examination_schedule_slip.pdf')
174    grok.require('waeup.viewStudent')
175    prefix = 'form'
176
177    label = u'Examination Schedule Slip'
178
179    omit_fields = (
180        'suspended', 'phone', 'email',
181        'adm_code', 'suspended_comment',
182        'date_of_birth', 'current_level',
183        'current_mode',
184        'entry_session',
185        'flash_notice')
186
187    form_fields = []
188
189    @property
190    def note(self):
191        return """
192 <br /><br />
193 <strong>Instructions on CBT Venue Allocation Slip (VAS)</strong>
194 <br /><br />
195 You should login with your student id from Kofa and surname as password.
196 Download and print two copies of this slip and bring them to the
197 allocated CBT examination center.
198 The copies <strong>MUST</strong> be shown to the invigilators
199 before being admitted into the examination hall.
200 <br /><br />
201 How to start examination:<br /><br />
202  * Username:  "student id" from Kofa e.g E1000000<br />
203  * Password: "surname" as shown on this slip in capital letters<br />
204  * Click the course and click "start exam".
205 <br /><br />
206 <strong>WARNING:</strong> Electronic devices (phones, tablets, laptops etc.)
207 are not allowed in the examination hall. Any electronics seized will not
208 be returned. Any student caught charging his/her mobile phone at the CBT
209 centers will be penalized and the exam of such a student will be cancelled.
210 Bags and any foreign materials are not allowed at the venue of
211 the CBT exams. Any omission/other complaints should be reported to the CBT
212 committee through the HoD before the date of examination.
213 <br /><br />
214 Your examination date, time and venue is scheduled as follows:
215 <br /><br />
216 <strong>%s</strong>
217""" % self.context.flash_notice
218        return
219
220
221    def update(self):
222        if not self.context.flash_notice \
223            or not 'exam' in self.context.flash_notice.lower():
224            self.flash(_('Forbidden'), type="warning")
225            self.redirect(self.url(self.context))
226
227    def render(self):
228        studentview = StudentBasePDFFormPage(self.context.student,
229            self.request, self.omit_fields)
230        students_utils = getUtility(IStudentsUtils)
231        return students_utils.renderPDF(
232            self, 'examination_schedule_slip',
233            self.context.student, studentview,
234            omit_fields=self.omit_fields,
235            note=self.note)
236
237class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage):
238    """ Page to display student clearance data
239    """
240
241    @property
242    def form_fields(self):
243        if self.context.is_postgrad:
244            form_fields = grok.AutoFields(
245                ICustomPGStudentClearance).omit('clearance_locked')
246        else:
247            form_fields = grok.AutoFields(
248                ICustomUGStudentClearance).omit('clearance_locked')
249        if not getattr(self.context, 'officer_comment'):
250            form_fields = form_fields.omit('officer_comment')
251        else:
252            form_fields['officer_comment'].custom_widget = BytesDisplayWidget
253        form_fields = form_fields.omit('def_adm')
254        return form_fields
255
256class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage):
257    """ Page to edit student clearance data
258    """
259
260    @property
261    def form_fields(self):
262        if self.context.is_postgrad:
263            form_fields = grok.AutoFields(
264                ICustomPGStudentClearance).omit('clr_code')
265        else:
266            form_fields = grok.AutoFields(
267                ICustomUGStudentClearance).omit('clr_code')
268        form_fields = form_fields.omit('def_adm')
269        return form_fields
270
271class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
272    """ View to edit student clearance data by student
273    """
274
275    @property
276    def form_fields(self):
277        if self.context.is_postgrad:
278            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
279            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
280            'physical_clearance_date')
281        else:
282            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
283            'clearance_locked', 'clr_code', 'officer_comment',
284            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
285        form_fields = form_fields.omit('def_adm')
286        return form_fields
287
288class CustomStartClearancePage(StartClearancePage):
289    with_ac = False
290
291    @property
292    def all_required_fields_filled(self):
293        if not self.context.email:
294            return _("Email address is missing."), 'edit_base'
295        if not self.context.phone:
296            return _("Phone number is missing."), 'edit_base'
297        if not self.context.father_name:
298            return _("Personal data form is not properly filled."), 'edit_personal'
299        return
300
301class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
302    """ Page to view an online payment ticket
303    """
304    grok.context(ICustomStudentOnlinePayment)
305    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
306        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
307    form_fields[
308        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
309    form_fields[
310        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
311
312class CustomPaymentsManageFormPage(PaymentsManageFormPage):
313    """ Page to manage the student payments.
314
315    This manage form page is for both students and students officers.
316    """
317    @property
318    def manage_payments_allowed(self):
319        return checkPermission('waeup.manageStudent', self.context)
320
321class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
322    """Deliver a PDF slip of the context.
323    """
324    grok.context(ICustomStudentOnlinePayment)
325    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
326        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item',
327        'p_split_data', 'p_combi')
328    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
329    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
330
331    @property
332    def note(self):
333        p_session = self.context.p_session
334        try:
335            academic_session = grok.getSite()['configuration'][str(p_session)]
336        except KeyError:
337            academic_session = None
338        text =  '\n\n The Amount Authorized is inclusive of: '
339        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
340            and academic_session:
341            if self.context.student.entry_session == 2016 \
342                and self.context.student.entry_mode == 'ug_ft' \
343                and self.context.p_session == 2016:
344                # Add student id card fee to first school fee payment.
345
346                ## Attention: The payment slip does not contain any information
347                ## whether the fee was added or not.
348                ## We can only draw conclusions from from the student's entry
349                ## session whether the fee had been included.
350                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
351                #text += ('School Fee, '
352                #         '%s Naira Student ID Card Fee, '
353                #         '%s Naira Student Union Dues, '
354                #         '%s Naira Student Welfare Assurance Fee and '
355                #         % (id_card_fee, union_fee, welfare_fee))
356
357                text += ('School Fee, '
358                         'Student ID Card Fee, '
359                         'Student Union Dues, '
360                         'Sports Fee, '
361                         'Library Levy, '
362                         'Student Welfare Assurance Fee and ')
363            else:
364
365                #text += ('School Fee, '
366                #         '%s Naira Student Union Dues, '
367                #         '%s Naira Student Welfare Assurance Fee and '
368                #         % (union_fee, welfare_fee))
369
370                text += ('School Fee, '
371                         'Student Union Dues, '
372                         'Sports Development Fee, '
373                         'Library Development Levy, '
374                         'LMS + Sundry Fees, '
375                         'Student Welfare Assurance Fee and ')
376        elif self.context.p_category in (
377            'clearance_incl', 'clearance_medical_incl') and academic_session:
378
379            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
380            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
381            #text += ('Acceptance Fee, '
382            #         '%s Naira Matriculation Gown Fee, '
383            #         '%s Naira Lapel/File Fee and '
384            #         % (matric_gown_fee, lapel_fee))
385
386            text += ('Acceptance Fee, '
387                     'Matriculation Gown Fee, '
388                     'Lapel/File Fee and ')
389
390        #return text + '250.0 Naira Transaction Charge.'
391
392        return text + 'Transaction Charge.'
393
394class CustomStudyCourseManageFormPage(StudyCourseManageFormPage):
395    """ Page to edit the student study course data
396    """
397    grok.context(ICustomStudentStudyCourse)
398
399    @property
400    def form_fields(self):
401        if self.context.is_postgrad:
402            form_fields = grok.AutoFields(ICustomStudentStudyCourse).omit(
403                'previous_verdict')
404        else:
405            form_fields = grok.AutoFields(ICustomStudentStudyCourse)
406        form_fields['imported_cgpa'].for_display = True
407        return form_fields
408
409class CustomUnregisterCoursesView(UnregisterCoursesView):
410    """Unregister course list by student
411    """
412    grok.context(ICustomStudentStudyLevel)
413
414    def update(self):
415        if not self.context.__parent__.is_current:
416            emit_lock_message(self)
417            return
418        #try:
419        #    academic_session = grok.getSite()['configuration'][
420        #        str(self.context.level_session)]
421        #    if self.context.student.is_postgrad:
422        #        deadline = academic_session.coursereg_deadline_pg
423        #    elif self.context.student.current_mode.startswith('dp'):
424        #        deadline = academic_session.coursereg_deadline_dp
425        #    elif self.context.student.current_mode in (
426        #        'ug_pt', 'de_pt', 'de_dsh', 'ug_dsh'):
427        #        deadline = academic_session.coursereg_deadline_pt
428        #    elif self.context.student.current_mode == 'found':
429        #        deadline = academic_session.coursereg_deadline_found
430        #    elif self.context.student.current_mode == 'bridge':
431        #        deadline = academic_session.coursereg_deadline_bridge
432        #    else:
433        #        deadline = academic_session.coursereg_deadline
434        #except (TypeError, KeyError):
435        #    deadline = None
436        #if deadline and deadline < datetime.now(pytz.utc):
437        #    self.flash(_(
438        #        "Course registration has ended. "
439        #        "Unregistration is disabled."), type="danger")
440        if str(self.context.__parent__.current_level) != self.context.__name__:
441            self.flash(_('This is not your current level.'), type="danger")
442        elif self.context.student.state == REGISTERED:
443            IWorkflowInfo(self.context.student).fireTransition('reset7')
444            message = _('Course list has been unregistered.')
445            self.flash(message)
446        else:
447            self.flash(_('You are in the wrong state.'), type="warning")
448        self.redirect(self.url(self.context))
449        return
450
451class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
452    """ Page to display student study levels
453    """
454    grok.context(ICustomStudentStudyLevel)
455    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
456        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
457    form_fields[
458        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
459
460    @property
461    def translated_values(self):
462        return translated_values(self)
463
464    @property
465    def show_results(self):
466        isStudent = getattr(
467            self.request.principal, 'user_type', None) == 'student'
468        try:
469            show_results = grok.getSite()[
470                'configuration'][str(self.context.level_session)].show_results
471        except KeyError:
472            return False
473        if isStudent and self.context.student.current_mode not in show_results:
474            return False
475        #if isStudent and self.context.student.state != RETURNING \
476        #    and self.context.student.current_level == self.context.level:
477        #    return False
478        return True
479
480class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
481    """ Page to edit the student study level data
482    """
483    grok.context(ICustomStudentStudyLevel)
484
485    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
486        'validation_date', 'validated_by', 'total_credits', 'gpa', 'level',
487        'total_credits_s1', 'total_credits_s2')
488
489    form_fields['imported_gpa'].for_display = True
490    form_fields['imported_cgpa'].for_display = True
491
492class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
493    """ Page to edit the student study level data by students
494    """
495    grok.context(ICustomStudentStudyLevel)
496
497class StudyLevelRepairFormPage(KofaEditFormPage):
498    """ Page to repair the student study level data by students
499    """
500    grok.context(IStudentStudyLevel)
501    grok.name('repair')
502    grok.require('waeup.editStudyLevel')
503    grok.template('studylevelrepairpage')
504    pnav = 4
505    placeholder = _('Enter valid course code')
506
507    def update(self, ADD=None, course=None):
508        if not self.context.__parent__.is_current \
509            or self.context.student.studycourse_locked:
510            emit_lock_message(self)
511            return
512        try:
513            studylevel_repair_enabled = grok.getSite()['configuration'][
514                str(self.context.level_session)].studylevel_repair_enabled
515        except KeyError:
516            emit_lock_message(self)
517            return
518        if not studylevel_repair_enabled:
519            emit_lock_message(self)
520            return
521        super(StudyLevelRepairFormPage, self).update()
522        if ADD is not None:
523            if not course:
524                self.flash(_('No valid course code entered.'), type="warning")
525                return
526            cat = queryUtility(ICatalog, name='courses_catalog')
527            result = cat.searchResults(code=(course, course))
528            if len(result) != 1:
529                self.flash(_('Course not found.'), type="warning")
530                return
531            course = list(result)[0]
532            addCourseTicket(self, course)
533        return
534
535    @property
536    def label(self):
537        # Here we know that the cookie has been set
538        lang = self.request.cookies.get('kofa.language')
539        level_title = translate(self.context.level_title, 'waeup.kofa',
540            target_language=lang)
541        return _('Repair course list of ${a}',
542            mapping = {'a':level_title})
543
544    @property
545    def translated_values(self):
546        return translated_values(self)
547
548    def _delCourseTicket(self, **data):
549        form = self.request.form
550        if 'val_id' in form:
551            child_id = form['val_id']
552        else:
553            self.flash(_('No ticket selected.'), type="warning")
554            self.redirect(self.url(self.context, '@@edit'))
555            return
556        if not isinstance(child_id, list):
557            child_id = [child_id]
558        deleted = []
559        for id in child_id:
560            # Students are not allowed to remove core tickets
561            if id in self.context and \
562                self.context[id].removable_by_student:
563                del self.context[id]
564                deleted.append(id)
565        if len(deleted):
566            self.flash(_('Successfully removed: ${a}',
567                mapping = {'a':', '.join(deleted)}))
568            self.context.writeLogMessage(
569                self,'removed: %s at %s' %
570                (', '.join(deleted), self.context.level))
571        self.redirect(self.url(self.context, u'@@repair'))
572        return
573
574    @jsaction(_('Remove selected tickets'))
575    def delCourseTicket(self, **data):
576        self._delCourseTicket(**data)
577        return
578
579class CustomExportPDFCourseRegistrationSlip(
580    NigeriaExportPDFCourseRegistrationSlip):
581    """Deliver a PDF slip of the context.
582    """
583    grok.context(ICustomStudentStudyLevel)
584    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
585        'level_session', 'level_verdict',
586        'validated_by', 'validation_date', 'gpa', 'level',
587        'imported_gpa', 'imported_cgpa', 'transcript_remark')
588
589    omit_fields = ('password', 'suspended', 'suspended_comment',
590        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
591        'department', 'current_mode', 'current_level', 'flash_notice',
592        )
593
594    @property
595    def show_results(self):
596        isStudent = getattr(
597            self.request.principal, 'user_type', None) == 'student'
598        try:
599            show_results = grok.getSite()[
600                'configuration'][str(self.context.level_session)].show_results
601        except KeyError:
602            return False
603        if isStudent and self.context.student.current_mode not in show_results:
604            return False
605        if isStudent and self.context.student.state != RETURNING \
606            and self.context.student.current_level == self.context.level:
607            return False
608        return True
609
610    def update(self):
611        if self.context.student.state != REGISTERED \
612            and self.context.student.current_level == self.context.level:
613            self.flash(_('Forbidden'), type="warning")
614            self.redirect(self.url(self.context))
615            return
616
617    @property
618    def label(self):
619        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
620        lang = self.request.cookies.get('kofa.language', portal_language)
621        level_title = translate(self.context.level_title, 'waeup.kofa',
622            target_language=lang)
623        line0 = ''
624        if self.context.student.is_postgrad:
625            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
626        elif self.context.student.current_mode.endswith('_pt'):
627            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
628        line1 = translate(_('Course Registration Slip'),
629            target_language=portal_language) \
630            + ' %s' % level_title
631        line2 = translate(_('Session'),
632            target_language=portal_language) \
633            + ' %s' % self.context.getSessionString
634        return '%s%s\n%s' % (line0, line1, line2)
635
636    @property
637    def title(self):
638        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
639        return translate(_('Units Registered'), target_language=portal_language)
640
641    def _signatures(self):
642        if self.context.student.current_mode in (
643            'ug_pt', 'de_pt', 'ug_dsh', 'de_dsh', 'found'):
644            return (
645                [('I have selected the course on the advise of my Head of '
646                 'Department. <br>', _('Student\'s Signature'), '<br>')],
647                [('This student has satisfied the department\'s requirements. '
648                 'I recommend to approve the course registration. <br>',
649                 _('Head of Department\'s Signature'), '<br>')],
650                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
651                [('', _('Director\'s Signature'))]
652                )
653        if self.context.student.current_mode in (
654            'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'):
655            return ([_('Academic Adviser\'s Signature'),
656                _('Faculty Officer\'s Signature'),
657                _('Student\'s Signature')],)
658
659        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
660            return (
661                [('I declare that all items of information supplied above are correct:' ,
662                    _('Student\'s Signature'), '<br>')],
663                [('We approved the above registration:',
664                    _('Major Supervisor (Name / Signature)'), '')],
665                [('', _('Co-Supervisor (Name / Signature)'), '')],
666                [('', _('Head of Department'), '<br>')],
667                [('The student has satisfied the conditions for renewal of '
668                  'registration for graduate school programme in this university:',
669                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
670                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
671                )
672        return None
673
674
675    def render(self):
676        if not self.context.student.current_mode:
677            self.flash('No certificate assigned.', type="danger")
678            self.redirect(self.url(self.context))
679            return
680        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
681        Sem = translate(_('Term'), target_language=portal_language)
682        Code = translate(_('Code'), target_language=portal_language)
683        Title = translate(_('Title'), target_language=portal_language)
684        Cred = translate(_('Cred.'), target_language=portal_language)
685        CC = translate(_('Cat.'), target_language=portal_language)
686        if self.show_results:
687            TotalScore = translate(_('Total Score'), target_language=portal_language)
688            #CA = translate(_('CA'), target_language=portal_language)
689            Grade = translate(_('Grade'), target_language=portal_language)
690        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
691            target_language=portal_language)
692        studentview = StudentBasePDFFormPage(self.context.student,
693            self.request, self.omit_fields)
694        students_utils = getUtility(IStudentsUtils)
695
696        tabledata = []
697        tableheader = []
698        contenttitle = []
699        for i in range(1,7):
700            tabledata.append(sorted(
701                [value for value in self.context.values()
702                 if value.semester == i and not value.outstanding],
703                 key=lambda value: str(value.semester) + value.code))
704            if self.show_results:
705                tableheader.append([(Code,'code', 2.0),
706                                   (Title,'title', 7),
707                                   (Cred, 'credits', 1.4),
708                                   (CC, 'course_category', 1.2),
709                                   (TotalScore, 'total_score', 1.4),
710                                   #(CA, 'ca', 1.4),
711                                   (Grade, 'grade', 1.4),
712                                   (Signature, 'dummy', 3),
713                                   ])
714            else:
715                tableheader.append([(Code,'code', 2.0),
716                                   (Title,'title', 7),
717                                   (Cred, 'credits', 1.5),
718                                   (CC, 'course_category', 1.2),
719                                   (Signature, 'dummy', 3),
720                                   ])
721        if len(self.label.split('\n')) == 3:
722            topMargin = 1.9
723        elif len(self.label.split('\n')) == 2:
724            topMargin = 1.7
725        else:
726            topMargin = 1.5
727        return students_utils.renderPDF(
728            self, 'course_registration_slip.pdf',
729            self.context.student, studentview,
730            tableheader=tableheader,
731            tabledata=tabledata,
732            signatures=self._signatures(),
733            topMargin=topMargin,
734            omit_fields=self.omit_fields
735            )
736
737class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
738    """ Page to display the student's transcript.
739    """
740    grok.require('waeup.viewStudent')
741
742class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
743    """Deliver a PDF slip of the context.
744    """
745
746    note = _("""
747<br /><br /><br /><br />
748<font size='10'>
749<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
750</font>
751""")
752
753    def _sigsInFooter(self):
754        return []
755
756    def _signatures(self):
757
758        #if self.context.student.state == GRADUATED:
759        #    return ([(
760        #        'O.O OHIKHENA (Manupa)<br />'
761        #        'Principal Asst. Registrar<br /> '
762        #        'Exams, Records and Data Processing Division <br /> '
763        #        'For: Registrar')],)
764        #elif self.context.certificate is not None:
765        #    dep = self.context.certificate.__parent__.__parent__
766        #    if dep.officer_1:
767        #        return ([(dep.officer_1)],)
768        #return
769
770        return ([(
771            'F.O. Omoigui, MANUPA<br />'
772            'Principal Assistant Registrar<br /> '
773            'Exams, Records and Data Processing Division <br /> '
774            'For: Registrar')],)
775
776    def render(self):
777        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
778        Term = translate(_('Term'), target_language=portal_language)
779        Code = translate(_('Code'), target_language=portal_language)
780        Title = translate(_('Title'), target_language=portal_language)
781        Cred = translate(_('Credits'), target_language=portal_language)
782        Score = translate(_('Score'), target_language=portal_language)
783        Grade = translate(_('Grade'), target_language=portal_language)
784        studentview = StudentBasePDFFormPage(self.context.student,
785            self.request, self.omit_fields)
786        students_utils = getUtility(IStudentsUtils)
787
788        tableheader = [(Code,'code', 2.5),
789                         (Title,'title', 7),
790                         (Term, 'semester', 1.5),
791                         (Cred, 'credits', 1.5),
792                         (Score, 'total_score', 1.5),
793                         (Grade, 'grade', 1.5),
794                         ]
795
796        pdfstream = students_utils.renderPDFTranscript(
797            self, 'transcript.pdf',
798            self.context.student, studentview,
799            omit_fields=self.omit_fields,
800            tableheader=tableheader,
801            signatures=self._signatures(),
802            sigs_in_footer=self._sigsInFooter(),
803            digital_sigs=self._digital_sigs(),
804            save_file=self._save_file(),
805            )
806        if not pdfstream:
807            self.redirect(self.url(self.context.student))
808            return
809        return pdfstream
810
811class ExportPDFTranscriptStudViewSlip(CustomExportPDFTranscriptSlip):
812    """Deliver a PDF slip of the context.
813    """
814
815    grok.name('transcript_studview.pdf')
816
817    def render(self):
818        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
819        Term = translate(_('Term'), target_language=portal_language)
820        Code = translate(_('Code'), target_language=portal_language)
821        Title = translate(_('Title'), target_language=portal_language)
822        Cred = translate(_('Credits'), target_language=portal_language)
823        #Score = translate(_('Score'), target_language=portal_language)
824        Grade = translate(_('Grade'), target_language=portal_language)
825        studentview = StudentBasePDFFormPage(self.context.student,
826            self.request, self.omit_fields)
827        students_utils = getUtility(IStudentsUtils)
828
829        tableheader = [(Code,'code', 2.5),
830                         (Title,'title', 7),
831                         (Term, 'semester', 1.5),
832                         (Cred, 'credits', 1.5),
833                         #(Score, 'total_score', 1.5),
834                         (Grade, 'grade', 1.5),
835                         ]
836
837        pdfstream = students_utils.renderPDFTranscript(
838            self, 'transcript.pdf',
839            self.context.student, studentview,
840            omit_fields=self.omit_fields,
841            tableheader=tableheader,
842            signatures=self._signatures(),
843            sigs_in_footer=self._sigsInFooter(),
844            digital_sigs=self._digital_sigs(),
845            save_file=self._save_file(),
846            )
847        if not pdfstream:
848            self.redirect(self.url(self.context.student))
849            return
850        return pdfstream
851
852class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
853    """Deliver a PDF Admission slip.
854    """
855
856    @property
857    def label(self):
858        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
859        return translate(_('e-Admission Slip \n'),
860            target_language=portal_language) \
861            + ' %s' % self.context.display_fullname
862
863class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
864    """Deliver a PDF slip of the context.
865    """
866
867    @property
868    def label(self):
869        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
870        return translate(_('Verification/Clearance Slip\n'),
871            target_language=portal_language) \
872            + ' %s' % self.context.display_fullname
873
874    @property
875    def form_fields(self):
876        if self.context.is_postgrad:
877            form_fields = grok.AutoFields(
878                ICustomPGStudentClearance).omit('clearance_locked')
879        else:
880            form_fields = grok.AutoFields(
881                ICustomUGStudentClearance).omit('clearance_locked')
882        if not getattr(self.context, 'officer_comment'):
883            form_fields = form_fields.omit('officer_comment')
884        form_fields = form_fields.omit('def_adm')
885        return form_fields
886
887class StudentGetMatricNumberPage(UtilityView, grok.View):
888    """ Construct and set the matriculation number.
889    """
890    grok.context(IStudent)
891    grok.name('get_matric_number')
892    grok.require('waeup.viewStudent')
893
894    def update(self):
895        students_utils = getUtility(IStudentsUtils)
896        msg, mnumber = students_utils.setMatricNumber(self.context)
897        if msg:
898            self.flash(msg, type="danger")
899        else:
900            self.flash(_('Matriculation number %s assigned.' % mnumber))
901            self.context.writeLogMessage(self, '%s assigned' % mnumber)
902        self.redirect(self.url(self.context))
903        return
904
905    def render(self):
906        return
907
908class ExportPDFMatricNumberSlip(UtilityView, grok.View):
909    """Deliver a PDF notification slip.
910    """
911    grok.context(ICustomStudent)
912    grok.name('matric_number_slip.pdf')
913    grok.require('waeup.viewStudent')
914    prefix = 'form'
915
916    form_fields = grok.AutoFields(ICustomStudent).select(
917        'student_id', 'matric_number')
918    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
919
920    @property
921    def title(self):
922        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
923        return translate(_('Matriculation Number'), 'waeup.kofa',
924            target_language=portal_language)
925
926    @property
927    def label(self):
928        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
929        return translate(_('Matriculation Number Slip\n'),
930            target_language=portal_language) \
931            + ' %s' % self.context.display_fullname
932
933    def render(self):
934        if self.context.state not in (PAID,) or not self.context.is_fresh \
935            or not self.context.matric_number:
936            self.flash('Not allowed.', type="danger")
937            self.redirect(self.url(self.context))
938            return
939        students_utils = getUtility(IStudentsUtils)
940        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
941                     'payments have been received and your matriculation ' +
942                     'number generated with details as follows.')
943        return students_utils.renderPDFAdmissionLetter(self,
944            self.context.student, omit_fields=self.omit_fields,
945            pre_text=pre_text, post_text='')
946
947class ExportPersonalDataSlip(UtilityView, grok.View):
948    """Deliver a PDF notification slip.
949    """
950    grok.context(ICustomStudent)
951    grok.name('personal_data_slip.pdf')
952    grok.require('waeup.viewStudent')
953    prefix = 'form'
954    note = None
955
956    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
957    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
958                   'certificate', 'flash_notice')
959
960    @property
961    def title(self):
962        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
963        return translate(_('Personal Data'), 'waeup.kofa',
964            target_language=portal_language)
965
966    @property
967    def label(self):
968        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
969        return translate(_('Personal Data Slip\n'),
970            target_language=portal_language) \
971            + ' %s' % self.context.display_fullname
972
973    def render(self):
974        studentview = StudentBasePDFFormPage(self.context.student,
975            self.request, self.omit_fields)
976        students_utils = getUtility(IStudentsUtils)
977        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
978            self.context.student, studentview, note=self.note,
979            omit_fields=self.omit_fields)
980
981class CustomAccommodationDisplayFormPage(NigeriaAccommodationDisplayFormPage):
982    """ Page to view bed tickets.
983    """
984    with_hostel_selection = True
985
986class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
987    """ Page to manage bed tickets.
988    This manage form page is for both students and students officers.
989    """
990    with_hostel_selection = True
991
992class CustomBedTicketAddPage(NigeriaBedTicketAddPage):
993    with_ac = False
994
995class CustomStudentFilesUploadPage(StudentFilesUploadPage):
996    """ View to upload files by student. Inherit from same class in
997    base package, not from kofacustom.nigeria which
998    requires that no application slip exists.
999    """
1000
1001class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
1002    """ Page to display course tickets
1003    """
1004
1005    @property
1006    def show_results(self):
1007        isStudent = getattr(
1008            self.request.principal, 'user_type', None) == 'student'
1009        if isStudent:
1010            return False
1011        return True
1012
1013    @property
1014    def form_fields(self):
1015        if self.show_results:
1016            return grok.AutoFields(ICustomCourseTicket)
1017        else:
1018            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
1019
1020class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
1021    """ Page to manage course tickets
1022    """
1023    form_fields = grok.AutoFields(ICustomCourseTicket)
1024    form_fields['title'].for_display = True
1025    form_fields['fcode'].for_display = True
1026    form_fields['dcode'].for_display = True
1027    form_fields['semester'].for_display = True
1028    form_fields['passmark'].for_display = True
1029    form_fields['credits'].for_display = True
1030    form_fields['mandatory'].for_display = False
1031    form_fields['automatic'].for_display = True
1032    form_fields['carry_over'].for_display = True
1033    form_fields['ticket_session'].for_display = True
1034    form_fields['imported_ts'].for_display = True
1035
1036class CustomEditScoresPage(EditScoresPage):
1037    """Page that filters and lists students.
1038    """
1039    grok.template('editscorespage')
1040
1041    def _searchCatalog(self, session):
1042        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1043        coursetickets = cat.searchResults(
1044            session=(session, session),
1045            code=(self.context.code, self.context.code)
1046            )
1047        try:
1048            score_editing_enabled = grok.getSite()[
1049                'configuration'][str(session)].score_editing_enabled
1050        except KeyError:
1051            return []
1052        coursetickets_list = [courseticket for courseticket in coursetickets
1053            if courseticket.student.current_mode in score_editing_enabled]
1054        return coursetickets_list
1055
1056    def _extract_uploadfile(self, uploadfile):
1057        """Get a mapping of student-ids to scores.
1058
1059        The mapping is constructed by reading contents from `uploadfile`.
1060
1061        We expect uploadfile to be a regular CSV file with columns
1062        ``student_id``, ``score``, ``imported_ts``
1063        and ``ca`` (other cols are ignored).
1064        """
1065        result = dict()
1066        data = StringIO(uploadfile.read())  # ensure we have something seekable
1067        reader = csv.DictReader(data)
1068        for row in reader:
1069            if not ('student_id' in row and 'score' in row and 'ca' in row and
1070                'imported_ts' in row):
1071                continue
1072            result[row['student_id']] = (
1073                row['score'], row['ca'], row['imported_ts'])
1074        return result
1075
1076    def _update_scores(self, form):
1077        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
1078        error = ''
1079        if 'UPDATE_FILE' in form:
1080            if form['uploadfile']:
1081                try:
1082                    formvals = self._extract_uploadfile(form['uploadfile'])
1083                except:
1084                    self.flash(
1085                        _('Uploaded file contains illegal data. Ignored'),
1086                        type="danger")
1087                    return False
1088            else:
1089                self.flash(
1090                    _('No file provided.'), type="danger")
1091                return False
1092        else:
1093            formvals = dict(zip(form['sids'], zip(
1094                form['scores'], form['cas'], form['imported_ts'])))
1095        for ticket in self.editable_tickets:
1096            ticket_error = False
1097            score = ticket.score
1098            ca = ticket.ca
1099            imported_ts = ticket.imported_ts
1100            sid = ticket.student.student_id
1101            if sid not in formvals:
1102                continue
1103            if formvals[sid][0] == '':
1104                score = None
1105            if formvals[sid][1] == '':
1106                ca = None
1107            if formvals[sid][2] == '':
1108                imported_ts = None
1109            try:
1110                if formvals[sid][0]:
1111                    score = int(formvals[sid][0])
1112                if formvals[sid][1]:
1113                    ca = int(formvals[sid][1])
1114                if formvals[sid][2]:
1115                    imported_ts = int(formvals[sid][2])
1116            except ValueError:
1117                error += '%s, ' % ticket.student.display_fullname
1118                ticket_error = True
1119            if score and ca and not ticket_error and score + ca > 100:
1120                error += '%s, ' % ticket.student.display_fullname
1121                ticket_error = True
1122            if not ticket_error and ticket.score != score:
1123                try:
1124                    ticket.score = score
1125                except TooBig:
1126                    error += '%s, ' % ticket.student.display_fullname
1127                    ticket_error = True
1128                    pass
1129                ticket.student.__parent__.logger.info(
1130                    '%s - %s %s/%s score updated (%s)' %
1131                    (ob_class, ticket.student.student_id,
1132                     ticket.level, ticket.code, score))
1133            if not ticket_error and ticket.ca != ca:
1134                try:
1135                    ticket.ca = ca
1136                except TooBig:
1137                    error += '%s, ' % ticket.student.display_fullname
1138                    pass
1139                ticket.student.__parent__.logger.info(
1140                    '%s - %s %s/%s ca updated (%s)' %
1141                    (ob_class, ticket.student.student_id,
1142                     ticket.level, ticket.code, ca))
1143            if not ticket_error and ticket.imported_ts != imported_ts:
1144                try:
1145                    ticket.imported_ts = imported_ts
1146                except TooBig:
1147                    error += '%s, ' % ticket.student.display_fullname
1148                    pass
1149                ticket.student.__parent__.logger.info(
1150                    '%s - %s %s/%s imported_ts updated (%s)' %
1151                    (ob_class, ticket.student.student_id,
1152                     ticket.level, ticket.code, imported_ts))
1153        if error:
1154            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
1155              % error.strip(', ')), type="danger")
1156        return True
1157
1158class EditPreviousSessionScoresPage(CustomEditScoresPage):
1159
1160    grok.name('edit_prev_scores')
1161
1162    def update(self,  *args, **kw):
1163        form = self.request.form
1164        self.current_academic_session = grok.getSite()[
1165            'configuration'].current_academic_session
1166        if self.context.__parent__.__parent__.score_editing_disabled:
1167            self.flash(_('Score editing disabled.'), type="warning")
1168            self.redirect(self.url(self.context))
1169            return
1170        if not self.current_academic_session:
1171            self.flash(_('Current academic session not set.'), type="warning")
1172            self.redirect(self.url(self.context))
1173            return
1174        previous_session = self.current_academic_session - 1
1175        self.session_title = academic_sessions_vocab.getTerm(
1176            previous_session).title
1177        self.tickets = self._searchCatalog(previous_session)
1178        if not self.tickets:
1179            self.flash(_('No student found.'), type="warning")
1180            self.redirect(self.url(self.context))
1181            return
1182        self.editable_tickets = [
1183            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1184        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1185            return
1186        if not self.editable_tickets:
1187            return
1188        success = self._update_scores(form)
1189        if success:
1190            self.flash(_('You successfully updated course results.'))
1191        return
1192
1193class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1194    """Deliver a PDF slip of course tickets for a lecturer.
1195    """
1196
1197    note = u'\nUpgraded scores are with asterisks.'
1198
1199    def data(self, session):
1200        #site = grok.getSite()
1201        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1202        coursetickets = cat.searchResults(
1203            session=(session, session),
1204            code=(self.context.code, self.context.code)
1205            )
1206        # Apply filter
1207        try:
1208            score_editing_enabled = grok.getSite()[
1209                'configuration'][str(session)].score_editing_enabled
1210            if checkPermission('waeup.manageAcademics', self.context):
1211                score_editing_enabled = True
1212            coursetickets_filtered = [courseticket
1213                for courseticket in coursetickets
1214                if (checkPermission('waeup.manageAcademics', self.context)
1215                    or (courseticket.student.current_mode in
1216                        score_editing_enabled))
1217                and courseticket.total_score is not None
1218                and courseticket.__parent__.__parent__.is_current]
1219        except KeyError:
1220            coursetickets_filtered = coursetickets
1221        # In AAUE only editable tickets can be printed (deactivated on 02/10/19)
1222        #editable_tickets = [
1223        #    ticket for ticket in coursetickets_filtered
1224        #    if ticket.editable_by_lecturer]
1225        header = [[_(''),
1226                   _('Student Id'),
1227                   _('Matric No.'),
1228                   #_('Reg. No.'),
1229                   #_('Fullname'),
1230                   #_('Status'),
1231                   #_('Course of\nStudies'),
1232                   _('Department'),
1233                   _('Level'),
1234                   _(' CA  '),
1235                   _('Exam\nScore'),
1236                   _('Total '),
1237                   _('Grade'),
1238                   ],]
1239        sorted_tickets = sorted(coursetickets_filtered, # editable_tickets,
1240            key=lambda ticket: str(ticket.student.depcode)
1241                               + str(ticket.student.faccode)
1242                               + str(ticket.student.matric_number)
1243                               )
1244        no = 1
1245        tickets = []
1246        passed = 0
1247        failed = 0
1248        with_ca = False
1249        grade_stats = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0, 'F':0, }
1250        for ticket in sorted_tickets:
1251            if ticket.ca > 0:
1252                with_ca = True
1253            total = ticket.total_score
1254            if getattr(ticket, 'imported_ts', None):
1255                total = "**%s**" % ticket.imported_ts
1256            grade = ticket._getGradeWeightFromScore[0]
1257            if grade in grade_stats.keys():
1258                grade_stats[grade] += 1
1259            if grade in ('F', '-'):
1260                failed += 1
1261            else:
1262                passed += 1
1263            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1264            #deptitle = site['faculties'][ticket.student.faccode][
1265            #    ticket.student.depcode].longtitle
1266            row = [str(no),
1267                  ticket.student.student_id,
1268                  ticket.student.matric_number,
1269                  #ticket.student.reg_number,
1270                  #fullname,
1271                  #ticket.student.translated_state,
1272                  #ticket.student.certcode,
1273                  str(ticket.student.faccode) + ' / ' + str(ticket.student.depcode),
1274                  ticket.level,
1275                  ticket.ca,
1276                  ticket.score,
1277                  total,
1278                  grade,
1279                  ]
1280            tickets.append(row)
1281            no += 1
1282        total = passed + failed
1283        passed_perc = 0
1284        failed_perc = 0
1285        if total:
1286            passed_perc = round(100.0 * passed / total)
1287            failed_perc = round(100.0 * failed / total)
1288        dep = self.context.__parent__.__parent__.longtitle
1289        fac = self.context.__parent__.__parent__.__parent__.longtitle
1290        # remove CA column if not necessary
1291        if not with_ca:
1292            header = [[_(''),
1293                       _('Student Id'),
1294                       _('Matric No.'),
1295                       #_('Reg. No.'),
1296                       #_('Fullname'),
1297                       #_('Status'),
1298                       #_('Course of\nStudies'),
1299                       _('Department'),
1300                       _('Level'),
1301                       #_(' CA  '),
1302                       _('Exam\nScore'),
1303                       _('Total '),
1304                       _('Grade'),
1305                       ],]
1306            for ticket in tickets:
1307                del(ticket[5])
1308        return header + tickets, [
1309            dep, fac, total, passed, passed_perc, failed, failed_perc, grade_stats]
1310
1311    def render(self):
1312        session = grok.getSite()['configuration'].current_academic_session
1313        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1314                     if i['local_role'] == 'waeup.local.Lecturer']
1315        lecturers = sorted(lecturers)
1316        lecturers =  ', '.join(lecturers)
1317        students_utils = getUtility(IStudentsUtils)
1318        # only orientation is different
1319        return students_utils.renderPDFCourseticketsOverview(
1320            self, 'coursetickets',
1321            session, self.data(session), lecturers, '', 45, self.note)
1322
1323class DownloadPreviousSessionScoresView(DownloadScoresView):
1324    """View that exports scores.
1325    """
1326    grok.name('download_prev_scores')
1327
1328    def update(self):
1329        self.current_academic_session = grok.getSite()[
1330            'configuration'].current_academic_session
1331        if self.context.__parent__.__parent__.score_editing_disabled:
1332            self.flash(_('Score editing disabled.'), type="warning")
1333            self.redirect(self.url(self.context))
1334            return
1335        if not self.current_academic_session:
1336            self.flash(_('Current academic session not set.'), type="warning")
1337            self.redirect(self.url(self.context))
1338            return
1339        site = grok.getSite()
1340        exporter = getUtility(ICSVExporter, name='lecturer')
1341        self.csv = exporter.export_filtered(site, filepath=None,
1342                                 catalog='coursetickets',
1343                                 session=self.current_academic_session-1,
1344                                 level=None,
1345                                 code=self.context.code)
1346        return
1347
1348class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1349    """Captcha'd request password page for students.
1350    """
1351    grok.name('alumni_requestpw')
1352    grok.require('waeup.Anonymous')
1353    grok.template('alumni_requestpw')
1354    form_fields = grok.AutoFields(IStudentRequestPW).select(
1355        'lastname','number','email')
1356    label = _('Search student record and send password for first-time login')
1357
1358    def _redirect_no_student(self):
1359        self.flash(_('No student record found.'), type="warning")
1360        self.redirect(self.application_url() + '/applicants/trans2017/register')
1361        return
1362
1363class CustomAddStudyLevelFormPage(AddStudyLevelFormPage):
1364    """ This page is temporarily locked.
1365    """
1366    def update(self):
1367        if self.context.student.current_mode == 'ug_ft' \
1368            and self.context.student.current_session == 2021 \
1369            and not self.context.student.entry_session == 2021:
1370            emit_lock_message(self)
1371            return
1372        super(CustomAddStudyLevelFormPage, self).update()
1373        return
Note: See TracBrowser for help on using the repository browser.