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

Last change on this file since 17089 was 17089, checked in by Henrik Bettermann, 2 years ago

Lock 2021 and not 2020.

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