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

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

Add grade statistics.

  • Property svn:keywords set to Id
File size: 51.1 KB
RevLine 
[8911]1## $Id: browser.py 15822 2019-11-18 13:24:41Z 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
[13937]19import csv
[13964]20import textwrap
[14981]21import pytz
[13937]22from cStringIO import StringIO
[14981]23from datetime import datetime
[8911]24from zope.i18n import translate
[13900]25from zope.component import getUtility, queryUtility
[14113]26from zope.schema.interfaces import TooBig, TooSmall
[13523]27from zope.security import checkPermission
[13900]28from zope.catalog.interfaces import ICatalog
[13351]29from zope.formlib.textwidgets import BytesDisplayWidget
[14981]30from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[15330]31from waeup.kofa.browser.layout import UtilityView, KofaEditFormPage, jsaction
[8911]32from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
[14288]33from waeup.kofa.interfaces import (
[14306]34    IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject)
35from waeup.kofa.students.interfaces import (
[15330]36    IStudentsUtils, IStudent, IStudentRequestPW, IStudentStudyLevel)
[14000]37from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
[9914]38from waeup.kofa.students.browser import (
[11846]39    StartClearancePage,
[9914]40    StudentBasePDFFormPage,
41    CourseTicketAddFormPage,
42    StudyLevelDisplayFormPage,
[13834]43    StudyLevelManageFormPage,
44    StudyLevelEditFormPage,
[13059]45    ExportPDFTranscriptSlip,
46    ExportPDFAdmissionSlip,
[13380]47    StudentFilesUploadPage,
[13523]48    PaymentsManageFormPage,
[13770]49    CourseTicketDisplayFormPage,
50    CourseTicketManageFormPage,
[13900]51    EditScoresPage,
[14165]52    ExportPDFScoresSlip,
53    StudyCourseTranscriptPage,
[14288]54    DownloadScoresView,
[14534]55    StudentRequestPasswordPage,
[14981]56    StudyCourseManageFormPage,
[15330]57    UnregisterCoursesView,
58    addCourseTicket,
59    emit_lock_message
[10269]60    )
[8911]61from kofacustom.nigeria.students.browser import (
62    NigeriaOnlinePaymentDisplayFormPage,
63    NigeriaOnlinePaymentAddFormPage,
[13059]64    NigeriaExportPDFPaymentSlip,
65    NigeriaExportPDFCourseRegistrationSlip,
[13351]66    NigeriaStudentPersonalDisplayFormPage,
67    NigeriaStudentPersonalEditFormPage,
68    NigeriaStudentPersonalManageFormPage,
[14084]69    NigeriaStudentClearanceDisplayFormPage,
70    NigeriaExportPDFClearanceSlip,
71    NigeriaStudentClearanceManageFormPage,
[13362]72    NigeriaStudentClearanceEditFormPage,
[13462]73    NigeriaAccommodationManageFormPage,
[13795]74    NigeriaStudentBaseDisplayFormPage,
[15713]75    NigeriaStudentBaseManageFormPage,
76    NigeriaBedTicketAddPage
[10269]77    )
[9496]78from waeup.aaue.students.interfaces import (
[9914]79    ICustomStudentOnlinePayment,
[11607]80    ICustomStudentStudyLevel,
[13351]81    ICustomStudent,
82    ICustomStudentPersonal,
[13362]83    ICustomStudentPersonalEdit,
[13770]84    ICustomUGStudentClearance,
[14298]85    ICustomUGStudentClearanceEdit,
[14084]86    ICustomPGStudentClearance,
[13795]87    ICustomCourseTicket,
[14534]88    ICustomStudentBase,
89    ICustomStudentStudyCourse)
[13414]90from waeup.aaue.interswitch.browser import gateway_net_amt
[9914]91from waeup.aaue.interfaces import MessageFactory as _
[8911]92
[14306]93grok.context(IKofaObject)  # Make IKofaObject the default context
94
[15228]95def translated_values(view):
96    """Translate course ticket attribute values to be displayed on
97    studylevel pages.
98    """
99    lang = view.request.cookies.get('kofa.language')
100    for value in view.context.values():
101        value._p_activate()
102        value_dict = dict([i for i in value.__dict__.items()])
103        value_dict['url'] = view.url(value)
104        value_dict['removable_by_student'] = value.removable_by_student
105        value_dict['mandatory'] = translate(str(value.mandatory), 'zope',
106            target_language=lang)
107        value_dict['carry_over'] = translate(str(value.carry_over), 'zope',
108            target_language=lang)
109        value_dict['outstanding'] = translate(str(value.outstanding), 'zope',
110            target_language=lang)
111        value_dict['automatic'] = translate(str(value.automatic), 'zope',
112            target_language=lang)
113        value_dict['grade'] = value.grade
114        value_dict['weight'] = value.weight
115        value_dict['course_category'] = value.course_category
116        value_dict['total_score'] = value.total_score
117        semester_dict = getUtility(IKofaUtils).SEMESTER_DICT
118        value_dict['semester'] = semester_dict[
119            value.semester].replace('mester', 'm.')
120        # AAUE specific
121        value_dict['formatted_total_score'] = value.total_score
122        if getattr(value, 'imported_ts', None):
123            value_dict['formatted_total_score'] = "<strong>%s</strong>" % value.imported_ts
124        yield value_dict
125
[13795]126class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
127    """ Page to display student base data
128    """
129    form_fields = grok.AutoFields(ICustomStudentBase).omit(
130        'password', 'suspended', 'suspended_comment', 'flash_notice')
131    form_fields[
132        'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
133
134class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
135    """ View to manage student base data
136    """
137    form_fields = grok.AutoFields(ICustomStudentBase).omit(
138        'student_id', 'adm_code', 'suspended',
139        'financially_cleared_by', 'financial_clearance_date')
140
[13351]141class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
142    """ Page to display student personal data
143    """
144    form_fields = grok.AutoFields(ICustomStudentPersonal)
145    form_fields['perm_address'].custom_widget = BytesDisplayWidget
146    form_fields['father_address'].custom_widget = BytesDisplayWidget
147    form_fields['mother_address'].custom_widget = BytesDisplayWidget
148    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
149    form_fields[
150        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
151
152class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
153    """ Page to edit personal data
154    """
155    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated')
156
157class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
158    """ Page to edit personal data
159    """
160    form_fields = grok.AutoFields(ICustomStudentPersonal)
161    form_fields['personal_updated'].for_display = True
162    form_fields[
163        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
164
[14661]165
166class ExportExaminationScheduleSlip(UtilityView, grok.View):
167    """Deliver a examination schedule slip.
168
169    This form page is available only in Uniben and AAUE.
170    """
171    grok.context(ICustomStudent)
172    grok.name('examination_schedule_slip.pdf')
173    grok.require('waeup.viewStudent')
174    prefix = 'form'
175
176    label = u'Examination Schedule Slip'
177
178    omit_fields = (
179        'suspended', 'phone', 'email',
180        'adm_code', 'suspended_comment',
181        'date_of_birth', 'current_level',
182        'current_mode',
183        'entry_session',
184        'flash_notice')
185
186    form_fields = []
187
188    @property
189    def note(self):
190        return """
[14674]191 <br /><br />
192 <strong>Instructions on CBT Venue Allocation Slip (VAS)</strong>
193 <br /><br />
194 You should login with your student id from Kofa and surname as password.
[14677]195 Download and print two copies of this slip and bring them to the
196 allocated CBT examination center.
197 The copies <strong>MUST</strong> be shown to the invigilators
198 before being admitted into the examination hall.
[14674]199 <br /><br />
[14677]200 How to start examination:<br /><br />
201  * Username:  "student id" from Kofa e.g E1000000<br />
202  * Password: "surname" as shown on this slip in capital letters<br />
203  * Click the course and click "start exam".
204 <br /><br />
205 <strong>WARNING:</strong> Electronic devices (phones, tablets, laptops etc.)
[14674]206 are not allowed in the examination hall. Any electronics seized will not
207 be returned. Any student caught charging his/her mobile phone at the CBT
[14677]208 centers will be penalized and the exam of such a student will be cancelled.
[14674]209 Bags and any foreign materials are not allowed at the venue of
210 the CBT exams. Any omission/other complaints should be reported to the CBT
211 committee through the HoD before the date of examination.
212 <br /><br />
213 Your examination date, time and venue is scheduled as follows:
214 <br /><br />
215 <strong>%s</strong>
[14661]216""" % self.context.flash_notice
217        return
218
219
220    def update(self):
221        if not self.context.flash_notice \
222            or not 'exam' in self.context.flash_notice.lower():
223            self.flash(_('Forbidden'), type="warning")
224            self.redirect(self.url(self.context))
225
226    def render(self):
227        studentview = StudentBasePDFFormPage(self.context.student,
228            self.request, self.omit_fields)
229        students_utils = getUtility(IStudentsUtils)
230        return students_utils.renderPDF(
231            self, 'examination_schedule_slip',
232            self.context.student, studentview,
233            omit_fields=self.omit_fields,
234            note=self.note)
235
[14084]236class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage):
237    """ Page to display student clearance data
238    """
239
240    @property
241    def form_fields(self):
242        if self.context.is_postgrad:
243            form_fields = grok.AutoFields(
244                ICustomPGStudentClearance).omit('clearance_locked')
245        else:
246            form_fields = grok.AutoFields(
247                ICustomUGStudentClearance).omit('clearance_locked')
248        if not getattr(self.context, 'officer_comment'):
249            form_fields = form_fields.omit('officer_comment')
250        else:
251            form_fields['officer_comment'].custom_widget = BytesDisplayWidget
[14104]252        form_fields = form_fields.omit('def_adm')
[14084]253        return form_fields
254
255class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage):
256    """ Page to edit student clearance data
257    """
258
259    @property
260    def form_fields(self):
261        if self.context.is_postgrad:
262            form_fields = grok.AutoFields(
263                ICustomPGStudentClearance).omit('clr_code')
264        else:
265            form_fields = grok.AutoFields(
266                ICustomUGStudentClearance).omit('clr_code')
[14104]267        form_fields = form_fields.omit('def_adm')
[14084]268        return form_fields
269
[13362]270class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
271    """ View to edit student clearance data by student
272    """
273
274    @property
275    def form_fields(self):
276        if self.context.is_postgrad:
277            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
278            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
279            'physical_clearance_date')
280        else:
[14298]281            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
[13362]282            'clearance_locked', 'clr_code', 'officer_comment',
[14104]283            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
284        form_fields = form_fields.omit('def_adm')
[13362]285        return form_fields
286
[11846]287class CustomStartClearancePage(StartClearancePage):
[13360]288    with_ac = False
[11846]289
[13351]290    @property
291    def all_required_fields_filled(self):
292        if not self.context.email:
293            return _("Email address is missing."), 'edit_base'
294        if not self.context.phone:
295            return _("Phone number is missing."), 'edit_base'
296        if not self.context.father_name:
297            return _("Personal data form is not properly filled."), 'edit_personal'
298        return
299
[8911]300class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
301    """ Page to view an online payment ticket
302    """
303    grok.context(ICustomStudentOnlinePayment)
[9853]304    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]305        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]306    form_fields[
307        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
308    form_fields[
309        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
310
[13523]311class CustomPaymentsManageFormPage(PaymentsManageFormPage):
312    """ Page to manage the student payments.
313
314    This manage form page is for both students and students officers.
315    """
316    @property
317    def manage_payments_allowed(self):
318        return checkPermission('waeup.manageStudent', self.context)
319
[13059]320class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
[8911]321    """Deliver a PDF slip of the context.
322    """
323    grok.context(ICustomStudentOnlinePayment)
[9853]324    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[15471]325        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item',
[15689]326        'p_split_data', 'p_combi')
[8911]327    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9496]328    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9914]329
[11625]330    @property
331    def note(self):
[13408]332        p_session = self.context.p_session
[13405]333        try:
[13408]334            academic_session = grok.getSite()['configuration'][str(p_session)]
[13405]335        except KeyError:
336            academic_session = None
[13425]337        text =  '\n\n The Amount Authorized is inclusive of: '
[13512]338        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
339            and academic_session:
[14385]340            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
341            #union_fee = gateway_net_amt(academic_session.union_fee)
[14244]342            if self.context.student.entry_session == 2016 \
343                and self.context.student.entry_mode == 'ug_ft' \
344                and self.context.p_session == 2016:
345                # Add student id card fee to first school fee payment.
[14385]346
347                ## Attention: The payment slip does not contain any information
348                ## whether the fee was added or not.
349                ## We can only draw conclusions from from the student's entry
350                ## session whether the fee had been included.
351                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
352                #text += ('School Fee, '
353                #         '%s Naira Student ID Card Fee, '
354                #         '%s Naira Student Union Dues, '
355                #         '%s Naira Student Welfare Assurance Fee and '
356                #         % (id_card_fee, union_fee, welfare_fee))
357
[14244]358                text += ('School Fee, '
[14385]359                         'Student ID Card Fee, '
360                         'Student Union Dues, '
[15330]361                         'Sports Fee, '
362                         'Library Levy, '
[14385]363                         'Student Welfare Assurance Fee and ')
[14244]364            else:
[14385]365
366                #text += ('School Fee, '
367                #         '%s Naira Student Union Dues, '
368                #         '%s Naira Student Welfare Assurance Fee and '
369                #         % (union_fee, welfare_fee))
370
[14244]371                text += ('School Fee, '
[14385]372                         'Student Union Dues, '
[15330]373                         'Sports Development Fee, '
374                         'Library Development Levy, '
[14385]375                         'Student Welfare Assurance Fee and ')
[13410]376        elif self.context.p_category in (
377            'clearance_incl', 'clearance_medical_incl') and academic_session:
[14385]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
[13437]386            text += ('Acceptance Fee, '
[14385]387                     'Matriculation Gown Fee, '
388                     'Lapel/File Fee and ')
[11625]389
[14385]390        #return text + '250.0 Naira Transaction Charge.'
391
392        return text + 'Transaction Charge.'
393
[14534]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
[15461]409class CustomUnregisterCoursesView(UnregisterCoursesView):
410    """Unregister course list by student
411    """
412    grok.context(ICustomStudentStudyLevel)
[14981]413
[15461]414    def update(self):
415        if not self.context.__parent__.is_current:
416            emit_lock_message(self)
417            return
[15511]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")
[15512]440        if str(self.context.__parent__.current_level) != self.context.__name__:
[15461]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
[14981]450
[9914]451class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
452    """ Page to display student study levels
453    """
454    grok.context(ICustomStudentStudyLevel)
[10480]455    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[14206]456        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
[9914]457    form_fields[
458        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
459
[14000]460    @property
[15228]461    def translated_values(self):
462        return translated_values(self)
463
464    @property
[14000]465    def show_results(self):
466        isStudent = getattr(
467            self.request.principal, 'user_type', None) == 'student'
[14944]468        try:
469            show_results = grok.getSite()[
470                'configuration'][str(self.context.level_session)].show_results
471        except KeyError:
[14000]472            return False
[14944]473        if isStudent and self.context.student.current_mode not in show_results:
474            return False
[15409]475        #if isStudent and self.context.student.state != RETURNING \
476        #    and self.context.student.current_level == self.context.level:
477        #    return False
[14000]478        return True
479
[13834]480class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
481    """ Page to edit the student study level data
482    """
483    grok.context(ICustomStudentStudyLevel)
484
[14534]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
[13834]492class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
493    """ Page to edit the student study level data by students
494    """
495    grok.context(ICustomStudentStudyLevel)
496
[15330]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):
[15340]508        if not self.context.__parent__.is_current \
509            or self.context.student.studycourse_locked:
510            emit_lock_message(self)
511            return
[15330]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
[13059]579class CustomExportPDFCourseRegistrationSlip(
580    NigeriaExportPDFCourseRegistrationSlip):
[9914]581    """Deliver a PDF slip of the context.
582    """
583    grok.context(ICustomStudentStudyLevel)
584    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[10102]585        'level_session', 'level_verdict',
[14206]586        'validated_by', 'validation_date', 'gpa', 'level',
587        'imported_gpa', 'imported_cgpa')
[9914]588
[10269]589    omit_fields = ('password', 'suspended', 'suspended_comment',
[10689]590        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
[15408]591        'department', 'current_mode', 'current_level', 'flash_notice',
592        'transcript_remark')
[10269]593
[14000]594    @property
595    def show_results(self):
596        isStudent = getattr(
597            self.request.principal, 'user_type', None) == 'student'
[14944]598        try:
599            show_results = grok.getSite()[
600                'configuration'][str(self.context.level_session)].show_results
601        except KeyError:
[14302]602            return False
[14944]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
[14302]608        return True
[14000]609
[13038]610    def update(self):
611        if self.context.student.state != REGISTERED \
[13051]612            and self.context.student.current_level == self.context.level:
[13038]613            self.flash(_('Forbidden'), type="warning")
614            self.redirect(self.url(self.context))
[14000]615            return
[13038]616
[9914]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 = ''
[13788]624        if self.context.student.is_postgrad:
625            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
626        elif self.context.student.current_mode.endswith('_pt'):
[9914]627            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
[13866]628        line1 = translate(_('Course Registration Slip'),
629            target_language=portal_language) \
[9914]630            + ' %s' % level_title
[13866]631        line2 = translate(_('Session'),
632            target_language=portal_language) \
[9914]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
[13866]639        return translate(_('Units Registered'), target_language=portal_language)
[9914]640
641    def _signatures(self):
[15483]642        if self.context.student.current_mode in (
643            'ug_pt', 'de_pt', 'ug_dsh', 'de_dsh', 'found'):
[13647]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>')],
[13946]650                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
[13647]651                [('', _('Director\'s Signature'))]
652                )
653        if self.context.student.current_mode in (
[15457]654            'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'):
[13649]655            return ([_('Academic Adviser\'s Signature'),
656                _('Faculty Officer\'s Signature'),
657                _('Student\'s Signature')],)
658
[13647]659        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
660            return (
[13676]661                [('I declare that all items of information supplied above are correct:' ,
[13680]662                    _('Student\'s Signature'), '<br>')],
[13676]663                [('We approved the above registration:',
[13680]664                    _('Major Supervisor (Name / Signature)'), '')],
665                [('', _('Co-Supervisor (Name / Signature)'), '')],
[13676]666                [('', _('Head of Department'), '<br>')],
667                [('The student has satisfied the conditions for renewal of '
668                  'registration for graduate school programme in this university:',
[13680]669                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
670                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
[13647]671                )
672        return None
[9914]673
[13647]674
[9914]675    def render(self):
676        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]677        Sem = translate(_('Sem.'), target_language=portal_language)
678        Code = translate(_('Code'), target_language=portal_language)
679        Title = translate(_('Title'), target_language=portal_language)
680        Cred = translate(_('Cred.'), target_language=portal_language)
[14650]681        CC = translate(_('Cat.'), target_language=portal_language)
[14000]682        if self.show_results:
[15317]683            TotalScore = translate(_('Total Score'), target_language=portal_language)
[14000]684            #CA = translate(_('CA'), target_language=portal_language)
685            Grade = translate(_('Grade'), target_language=portal_language)
[13866]686        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
[9914]687            target_language=portal_language)
688        studentview = StudentBasePDFFormPage(self.context.student,
689            self.request, self.omit_fields)
690        students_utils = getUtility(IStudentsUtils)
[10442]691
692        tabledata = []
693        tableheader = []
694        contenttitle = []
695        for i in range(1,7):
696            tabledata.append(sorted(
697                [value for value in self.context.values() if value.semester == i],
698                key=lambda value: str(value.semester) + value.code))
[14000]699            if self.show_results:
700                tableheader.append([(Code,'code', 2.0),
701                                   (Title,'title', 7),
[14650]702                                   (Cred, 'credits', 1.4),
703                                   (CC, 'course_category', 1.2),
[15317]704                                   (TotalScore, 'total_score', 1.4),
[14000]705                                   #(CA, 'ca', 1.4),
706                                   (Grade, 'grade', 1.4),
707                                   (Signature, 'dummy', 3),
708                                   ])
709            else:
710                tableheader.append([(Code,'code', 2.0),
711                                   (Title,'title', 7),
712                                   (Cred, 'credits', 1.5),
[14650]713                                   (CC, 'course_category', 1.2),
[14000]714                                   (Signature, 'dummy', 3),
715                                   ])
[9914]716        if len(self.label.split('\n')) == 3:
717            topMargin = 1.9
718        elif len(self.label.split('\n')) == 2:
719            topMargin = 1.7
720        else:
721            topMargin = 1.5
722        return students_utils.renderPDF(
723            self, 'course_registration_slip.pdf',
724            self.context.student, studentview,
[10442]725            tableheader=tableheader,
726            tabledata=tabledata,
[9914]727            signatures=self._signatures(),
[10269]728            topMargin=topMargin,
729            omit_fields=self.omit_fields
[9914]730            )
[10566]731
[14165]732class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
733    """ Page to display the student's transcript.
734    """
735    grok.require('waeup.viewStudent')
736
[13059]737class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
[10566]738    """Deliver a PDF slip of the context.
739    """
[14315]740#    grok.require('waeup.viewStudent')
[10566]741
[14267]742    note = _("""
743<br /><br /><br /><br />
744<font size='10'>
745<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
746</font>
747""")
748
[10566]749    def _sigsInFooter(self):
750        return []
751
752    def _signatures(self):
753        return ([(
[14999]754            'O.O OHIKHENA (Manupa)<br />Principal Asst. Registrar<br /> '
[11555]755            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
[10922]756
[13834]757    def render(self):
758        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]759        Term = translate(_('Sem.'), target_language=portal_language)
760        Code = translate(_('Code'), target_language=portal_language)
761        Title = translate(_('Title'), target_language=portal_language)
762        Cred = translate(_('Credits'), target_language=portal_language)
763        Score = translate(_('Score'), target_language=portal_language)
764        Grade = translate(_('Grade'), target_language=portal_language)
[13834]765        studentview = StudentBasePDFFormPage(self.context.student,
766            self.request, self.omit_fields)
767        students_utils = getUtility(IStudentsUtils)
768
769        tableheader = [(Code,'code', 2.5),
770                         (Title,'title', 7),
771                         (Term, 'semester', 1.5),
772                         (Cred, 'credits', 1.5),
[14136]773                         (Score, 'total_score', 1.5),
[13834]774                         (Grade, 'grade', 1.5),
775                         ]
776
[15165]777        pdfstream = students_utils.renderPDFTranscript(
[13834]778            self, 'transcript.pdf',
779            self.context.student, studentview,
780            omit_fields=self.omit_fields,
781            tableheader=tableheader,
782            signatures=self._signatures(),
783            sigs_in_footer=self._sigsInFooter(),
[15165]784            digital_sigs=self._digital_sigs(),
785            save_file=self._save_file(),
[13834]786            )
[15165]787        if not pdfstream:
788            self.redirect(self.url(self.context.student))
789            return
790        return pdfstream
[13834]791
[13059]792class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
[10922]793    """Deliver a PDF Admission slip.
794    """
795
796    @property
797    def label(self):
798        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]799        return translate(_('e-Admission Slip \n'),
800            target_language=portal_language) \
[10922]801            + ' %s' % self.context.display_fullname
[11597]802
[13059]803class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
[11606]804    """Deliver a PDF slip of the context.
805    """
806
807    @property
808    def label(self):
809        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[14114]810        return translate(_('Verification/Clearance Slip\n'),
[13866]811            target_language=portal_language) \
[11606]812            + ' %s' % self.context.display_fullname
813
[14084]814    @property
815    def form_fields(self):
816        if self.context.is_postgrad:
817            form_fields = grok.AutoFields(
818                ICustomPGStudentClearance).omit('clearance_locked')
819        else:
820            form_fields = grok.AutoFields(
821                ICustomUGStudentClearance).omit('clearance_locked')
822        if not getattr(self.context, 'officer_comment'):
823            form_fields = form_fields.omit('officer_comment')
[14104]824        form_fields = form_fields.omit('def_adm')
[14084]825        return form_fields
826
[11597]827class StudentGetMatricNumberPage(UtilityView, grok.View):
828    """ Construct and set the matriculation number.
829    """
830    grok.context(IStudent)
831    grok.name('get_matric_number')
832    grok.require('waeup.viewStudent')
833
834    def update(self):
835        students_utils = getUtility(IStudentsUtils)
836        msg, mnumber = students_utils.setMatricNumber(self.context)
837        if msg:
838            self.flash(msg, type="danger")
839        else:
840            self.flash(_('Matriculation number %s assigned.' % mnumber))
[11602]841            self.context.writeLogMessage(self, '%s assigned' % mnumber)
[11597]842        self.redirect(self.url(self.context))
843        return
844
845    def render(self):
[11607]846        return
847
[13059]848class ExportPDFMatricNumberSlip(UtilityView, grok.View):
[11607]849    """Deliver a PDF notification slip.
850    """
851    grok.context(ICustomStudent)
852    grok.name('matric_number_slip.pdf')
853    grok.require('waeup.viewStudent')
854    prefix = 'form'
855
856    form_fields = grok.AutoFields(ICustomStudent).select(
857        'student_id', 'matric_number')
[13713]858    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
[11607]859
860    @property
[13489]861    def title(self):
862        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]863        return translate(_('Matriculation Number'), 'waeup.kofa',
[13489]864            target_language=portal_language)
865
866    @property
[11607]867    def label(self):
868        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]869        return translate(_('Matriculation Number Slip\n'),
870            target_language=portal_language) \
[11607]871            + ' %s' % self.context.display_fullname
872
873    def render(self):
874        if self.context.state not in (PAID,) or not self.context.is_fresh \
875            or not self.context.matric_number:
876            self.flash('Not allowed.', type="danger")
877            self.redirect(self.url(self.context))
878            return
879        students_utils = getUtility(IStudentsUtils)
[11609]880        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
881                     'payments have been received and your matriculation ' +
882                     'number generated with details as follows.')
[11607]883        return students_utils.renderPDFAdmissionLetter(self,
884            self.context.student, omit_fields=self.omit_fields,
[13353]885            pre_text=pre_text, post_text='')
886
[13489]887class ExportPersonalDataSlip(UtilityView, grok.View):
888    """Deliver a PDF notification slip.
889    """
890    grok.context(ICustomStudent)
891    grok.name('personal_data_slip.pdf')
892    grok.require('waeup.viewStudent')
893    prefix = 'form'
894    note = None
895
896    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
[13713]897    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
898                   'certificate', 'flash_notice')
[13489]899
900    @property
901    def title(self):
902        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]903        return translate(_('Personal Data'), 'waeup.kofa',
[13489]904            target_language=portal_language)
905
906    @property
907    def label(self):
908        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]909        return translate(_('Personal Data Slip\n'),
910            target_language=portal_language) \
[13489]911            + ' %s' % self.context.display_fullname
912
913    def render(self):
914        studentview = StudentBasePDFFormPage(self.context.student,
915            self.request, self.omit_fields)
916        students_utils = getUtility(IStudentsUtils)
917        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
918            self.context.student, studentview, note=self.note,
919            omit_fields=self.omit_fields)
920
[13462]921class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
922    """ Page to manage bed tickets.
923    This manage form page is for both students and students officers.
924    """
925    with_hostel_selection = True
926
[15713]927class CustomBedTicketAddPage(NigeriaBedTicketAddPage):
[13360]928    with_ac = False
[13380]929
930class CustomStudentFilesUploadPage(StudentFilesUploadPage):
931    """ View to upload files by student. Inherit from same class in
932    base package, not from kofacustom.nigeria which
933    requires that no application slip exists.
[13770]934    """
935
936class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
937    """ Page to display course tickets
938    """
939
[14453]940    @property
941    def show_results(self):
942        isStudent = getattr(
943            self.request.principal, 'user_type', None) == 'student'
944        if isStudent:
945            return False
946        return True
947
948    @property
949    def form_fields(self):
950        if self.show_results:
951            return grok.AutoFields(ICustomCourseTicket)
952        else:
953            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
954
[13770]955class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
956    """ Page to manage course tickets
957    """
958    form_fields = grok.AutoFields(ICustomCourseTicket)
959    form_fields['title'].for_display = True
960    form_fields['fcode'].for_display = True
961    form_fields['dcode'].for_display = True
962    form_fields['semester'].for_display = True
963    form_fields['passmark'].for_display = True
964    form_fields['credits'].for_display = True
965    form_fields['mandatory'].for_display = False
966    form_fields['automatic'].for_display = True
967    form_fields['carry_over'].for_display = True
[15205]968    form_fields['ticket_session'].for_display = True
[15226]969    form_fields['imported_ts'].for_display = True
[13770]970
971class CustomEditScoresPage(EditScoresPage):
972    """Page that filters and lists students.
973    """
974    grok.template('editscorespage')
975
[14700]976    def _searchCatalog(self, session):
977        cat = queryUtility(ICatalog, name='coursetickets_catalog')
978        coursetickets = cat.searchResults(
979            session=(session, session),
980            code=(self.context.code, self.context.code)
981            )
982        try:
983            score_editing_enabled = grok.getSite()[
984                'configuration'][str(session)].score_editing_enabled
985        except KeyError:
986            return []
[14707]987        coursetickets_list = [courseticket for courseticket in coursetickets
988            if courseticket.student.current_mode in score_editing_enabled]
[14700]989        return coursetickets_list
[13937]990
991    def _extract_uploadfile(self, uploadfile):
992        """Get a mapping of student-ids to scores.
993
994        The mapping is constructed by reading contents from `uploadfile`.
995
996        We expect uploadfile to be a regular CSV file with columns
[15414]997        ``student_id``, ``score``, ``imported_ts``
998        and ``ca`` (other cols are ignored).
[13937]999        """
1000        result = dict()
1001        data = StringIO(uploadfile.read())  # ensure we have something seekable
1002        reader = csv.DictReader(data)
1003        for row in reader:
[15414]1004            if not ('student_id' in row and 'score' in row and 'ca' in row and
1005                'imported_ts' in row):
[13937]1006                continue
[15414]1007            result[row['student_id']] = (
1008                row['score'], row['ca'], row['imported_ts'])
[13937]1009        return result
1010
[14288]1011    def _update_scores(self, form):
[13937]1012        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
[14288]1013        error = ''
[13937]1014        if 'UPDATE_FILE' in form:
1015            if form['uploadfile']:
1016                try:
1017                    formvals = self._extract_uploadfile(form['uploadfile'])
1018                except:
1019                    self.flash(
1020                        _('Uploaded file contains illegal data. Ignored'),
1021                        type="danger")
[14288]1022                    return False
[13937]1023            else:
1024                self.flash(
1025                    _('No file provided.'), type="danger")
[14288]1026                return False
[13937]1027        else:
[15414]1028            formvals = dict(zip(form['sids'], zip(
[15822]1029                form['scores'], form['cas'], form['imported_ts'])))
[14149]1030        for ticket in self.editable_tickets:
[13937]1031            ticket_error = False
1032            score = ticket.score
1033            ca = ticket.ca
[15414]1034            imported_ts = ticket.imported_ts
[13937]1035            sid = ticket.student.student_id
1036            if formvals[sid][0] == '':
1037                score = None
1038            if formvals[sid][1] == '':
1039                ca = None
[15414]1040            if formvals[sid][2] == '':
1041                imported_ts = None
[13937]1042            try:
1043                if formvals[sid][0]:
1044                    score = int(formvals[sid][0])
1045                if formvals[sid][1]:
1046                    ca = int(formvals[sid][1])
[15414]1047                if formvals[sid][2]:
1048                    imported_ts = int(formvals[sid][2])
[13937]1049            except ValueError:
1050                error += '%s, ' % ticket.student.display_fullname
1051                ticket_error = True
1052            if not ticket_error and ticket.score != score:
[14113]1053                try:
1054                    ticket.score = score
1055                except TooBig:
1056                    error += '%s, ' % ticket.student.display_fullname
1057                    ticket_error = True
1058                    pass
[13937]1059                ticket.student.__parent__.logger.info(
1060                    '%s - %s %s/%s score updated (%s)' %
1061                    (ob_class, ticket.student.student_id,
1062                     ticket.level, ticket.code, score))
1063            if not ticket_error and ticket.ca != ca:
[14113]1064                try:
1065                    ticket.ca = ca
1066                except TooBig:
1067                    error += '%s, ' % ticket.student.display_fullname
1068                    pass
[13937]1069                ticket.student.__parent__.logger.info(
1070                    '%s - %s %s/%s ca updated (%s)' %
1071                    (ob_class, ticket.student.student_id,
1072                     ticket.level, ticket.code, ca))
[15414]1073            if not ticket_error and ticket.imported_ts != imported_ts:
1074                try:
1075                    ticket.imported_ts = imported_ts
1076                except TooBig:
1077                    error += '%s, ' % ticket.student.display_fullname
1078                    pass
1079                ticket.student.__parent__.logger.info(
1080                    '%s - %s %s/%s imported_ts updated (%s)' %
1081                    (ob_class, ticket.student.student_id,
1082                     ticket.level, ticket.code, imported_ts))
[13937]1083        if error:
[15414]1084            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
[14113]1085              % error.strip(', ')), type="danger")
[14288]1086        return True
1087
1088class EditPreviousSessionScoresPage(CustomEditScoresPage):
1089
1090    grok.name('edit_prev_scores')
1091
1092    def update(self,  *args, **kw):
1093        form = self.request.form
1094        self.current_academic_session = grok.getSite()[
1095            'configuration'].current_academic_session
1096        if self.context.__parent__.__parent__.score_editing_disabled:
1097            self.flash(_('Score editing disabled.'), type="warning")
1098            self.redirect(self.url(self.context))
[13939]1099            return
[14288]1100        if not self.current_academic_session:
1101            self.flash(_('Current academic session not set.'), type="warning")
1102            self.redirect(self.url(self.context))
1103            return
1104        previous_session = self.current_academic_session - 1
1105        self.session_title = academic_sessions_vocab.getTerm(
1106            previous_session).title
1107        self.tickets = self._searchCatalog(previous_session)
1108        if not self.tickets:
1109            self.flash(_('No student found.'), type="warning")
1110            self.redirect(self.url(self.context))
1111            return
1112        self.editable_tickets = [
1113            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1114        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1115            return
1116        if not self.editable_tickets:
1117            return
1118        success = self._update_scores(form)
1119        if success:
1120            self.flash(_('You successfully updated course results.'))
[13900]1121        return
1122
1123class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1124    """Deliver a PDF slip of course tickets for a lecturer.
1125    """
1126
[15343]1127    note = u'\nUpgraded scores are with asterisks.'
[15247]1128
[14315]1129    def data(self, session):
[14704]1130        #site = grok.getSite()
[13900]1131        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1132        coursetickets = cat.searchResults(
1133            session=(session, session),
1134            code=(self.context.code, self.context.code)
1135            )
[14707]1136        # Apply filter
1137        try:
1138            score_editing_enabled = grok.getSite()[
1139                'configuration'][str(session)].score_editing_enabled
[15453]1140            if checkPermission('waeup.manageAcademics', self.context):
1141                score_editing_enabled = True
[14707]1142            coursetickets_filtered = [courseticket
1143                for courseticket in coursetickets
[15454]1144                if (checkPermission('waeup.manageAcademics', self.context)
1145                    or (courseticket.student.current_mode in
1146                        score_editing_enabled))
[15236]1147                and courseticket.total_score is not None
1148                and courseticket.__parent__.__parent__.is_current]
[14707]1149        except KeyError:
1150            coursetickets_filtered = coursetickets
[15631]1151        # In AAUE only editable tickets can be printed (deactivated on 02/10/19)
1152        #editable_tickets = [
1153        #    ticket for ticket in coursetickets_filtered
1154        #    if ticket.editable_by_lecturer]
[13963]1155        header = [[_(''),
[15006]1156                   _('Student Id'),
[13963]1157                   _('Matric No.'),
[14703]1158                   #_('Reg. No.'),
1159                   #_('Fullname'),
1160                   #_('Status'),
1161                   #_('Course of\nStudies'),
[14704]1162                   _('Department'),
[13900]1163                   _('Level'),
[15201]1164                   _(' CA  '),
1165                   _('Exam\nScore'),
[13964]1166                   _('Total '),
[13963]1167                   _('Grade'),
1168                   ],]
[15631]1169        sorted_tickets = sorted(coursetickets_filtered, # editable_tickets,
[15199]1170            key=lambda ticket: ticket.student.depcode
1171                               + ticket.student.faccode
[14704]1172                               + ticket.student.matric_number)
[14288]1173        no = 1
[13907]1174        tickets = []
[14315]1175        passed = 0
1176        failed = 0
[15229]1177        with_ca = False
[15822]1178        grade_stats = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0, 'F':0, }
[14288]1179        for ticket in sorted_tickets:
[15229]1180            if ticket.ca > 0:
1181                with_ca = True
[15006]1182            total = ticket.total_score
[15232]1183            if getattr(ticket, 'imported_ts', None):
[15245]1184                total = "**%s**" % ticket.imported_ts
[15006]1185            grade = ticket._getGradeWeightFromScore[0]
[15822]1186            if grade in grade_stats.keys():
1187                grade_stats[grade] += 1
[15006]1188            if grade in ('F', '-'):
1189                failed += 1
[13963]1190            else:
[15006]1191                passed += 1
[13964]1192            fullname = textwrap.fill(ticket.student.display_fullname, 30)
[14704]1193            #deptitle = site['faculties'][ticket.student.faccode][
1194            #    ticket.student.depcode].longtitle
[15240]1195            row = [str(no),
[15006]1196                  ticket.student.student_id,
[13963]1197                  ticket.student.matric_number,
[14703]1198                  #ticket.student.reg_number,
1199                  #fullname,
1200                  #ticket.student.translated_state,
1201                  #ticket.student.certcode,
[14704]1202                  ticket.student.faccode + ' / ' + ticket.student.depcode,
[13900]1203                  ticket.level,
[15201]1204                  ticket.ca,
1205                  ticket.score,
[13963]1206                  total,
[13964]1207                  grade,
[13963]1208                  ]
[13907]1209            tickets.append(row)
[13963]1210            no += 1
[14317]1211        total = passed + failed
[14320]1212        passed_perc = 0
1213        failed_perc = 0
[14317]1214        if total:
[15408]1215            passed_perc = round(100.0 * passed / total)
1216            failed_perc = round(100.0 * failed / total)
[14710]1217        dep = self.context.__parent__.__parent__.longtitle
1218        fac = self.context.__parent__.__parent__.__parent__.longtitle
[15229]1219        # remove CA column if not necessary
1220        if not with_ca:
1221            header = [[_(''),
1222                       _('Student Id'),
1223                       _('Matric No.'),
1224                       #_('Reg. No.'),
1225                       #_('Fullname'),
1226                       #_('Status'),
1227                       #_('Course of\nStudies'),
1228                       _('Department'),
1229                       _('Level'),
1230                       #_(' CA  '),
1231                       _('Exam\nScore'),
1232                       _('Total '),
1233                       _('Grade'),
1234                       ],]
1235            for ticket in tickets:
1236                del(ticket[5])
[14320]1237        return header + tickets, [
[15822]1238            dep, fac, total, passed, passed_perc, failed, failed_perc, grade_stats]
[14288]1239
[14703]1240    def render(self):
1241        session = grok.getSite()['configuration'].current_academic_session
1242        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1243                     if i['local_role'] == 'waeup.local.Lecturer']
1244        lecturers =  ', '.join(lecturers)
1245        students_utils = getUtility(IStudentsUtils)
1246        # only orientation is different
1247        return students_utils.renderPDFCourseticketsOverview(
[15424]1248            self, 'coursetickets',
1249            session, self.data(session), lecturers, '', 45, self.note)
[14703]1250
[14288]1251class DownloadPreviousSessionScoresView(DownloadScoresView):
1252    """View that exports scores.
1253    """
1254    grok.name('download_prev_scores')
1255
1256    def update(self):
1257        self.current_academic_session = grok.getSite()[
1258            'configuration'].current_academic_session
1259        if self.context.__parent__.__parent__.score_editing_disabled:
1260            self.flash(_('Score editing disabled.'), type="warning")
1261            self.redirect(self.url(self.context))
1262            return
1263        if not self.current_academic_session:
1264            self.flash(_('Current academic session not set.'), type="warning")
1265            self.redirect(self.url(self.context))
1266            return
1267        site = grok.getSite()
1268        exporter = getUtility(ICSVExporter, name='lecturer')
1269        self.csv = exporter.export_filtered(site, filepath=None,
1270                                 catalog='coursetickets',
1271                                 session=self.current_academic_session-1,
1272                                 level=None,
1273                                 code=self.context.code)
[14306]1274        return
1275
1276class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1277    """Captcha'd request password page for students.
1278    """
1279    grok.name('alumni_requestpw')
1280    grok.require('waeup.Anonymous')
1281    grok.template('alumni_requestpw')
1282    form_fields = grok.AutoFields(IStudentRequestPW).select(
1283        'lastname','number','email')
1284    label = _('Search student record and send password for first-time login')
1285
1286    def _redirect_no_student(self):
1287        self.flash(_('No student record found.'), type="warning")
1288        self.redirect(self.application_url() + '/applicants/trans2017/register')
[14288]1289        return
Note: See TracBrowser for help on using the repository browser.