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

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

ivama: This should NOT affect students whose entry_session is 2021 (newly admitted students), Reason being, they are fresh and don't needs previous session results to guide their current session registration unlike the returning students who do...

  • Property svn:keywords set to Id
File size: 54.5 KB
RevLine 
[8911]1## $Id: browser.py 17098 2022-09-14 20:11:03Z 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)
[15896]37from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING, GRADUATED
[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,
[15864]59    AddStudyLevelFormPage,
[15330]60    emit_lock_message
[10269]61    )
[8911]62from kofacustom.nigeria.students.browser import (
63    NigeriaOnlinePaymentDisplayFormPage,
64    NigeriaOnlinePaymentAddFormPage,
[13059]65    NigeriaExportPDFPaymentSlip,
66    NigeriaExportPDFCourseRegistrationSlip,
[13351]67    NigeriaStudentPersonalDisplayFormPage,
68    NigeriaStudentPersonalEditFormPage,
69    NigeriaStudentPersonalManageFormPage,
[14084]70    NigeriaStudentClearanceDisplayFormPage,
71    NigeriaExportPDFClearanceSlip,
72    NigeriaStudentClearanceManageFormPage,
[13362]73    NigeriaStudentClearanceEditFormPage,
[13462]74    NigeriaAccommodationManageFormPage,
[15976]75    NigeriaAccommodationDisplayFormPage,
[13795]76    NigeriaStudentBaseDisplayFormPage,
[15713]77    NigeriaStudentBaseManageFormPage,
78    NigeriaBedTicketAddPage
[10269]79    )
[9496]80from waeup.aaue.students.interfaces import (
[9914]81    ICustomStudentOnlinePayment,
[11607]82    ICustomStudentStudyLevel,
[13351]83    ICustomStudent,
84    ICustomStudentPersonal,
[13362]85    ICustomStudentPersonalEdit,
[13770]86    ICustomUGStudentClearance,
[14298]87    ICustomUGStudentClearanceEdit,
[14084]88    ICustomPGStudentClearance,
[13795]89    ICustomCourseTicket,
[14534]90    ICustomStudentBase,
91    ICustomStudentStudyCourse)
[13414]92from waeup.aaue.interswitch.browser import gateway_net_amt
[9914]93from waeup.aaue.interfaces import MessageFactory as _
[8911]94
[14306]95grok.context(IKofaObject)  # Make IKofaObject the default context
96
[15228]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
[13795]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
[13351]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
[14661]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 """
[14674]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.
[14677]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.
[14674]201 <br /><br />
[14677]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.)
[14674]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
[14677]210 centers will be penalized and the exam of such a student will be cancelled.
[14674]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>
[14661]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
[14084]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
[14104]254        form_fields = form_fields.omit('def_adm')
[14084]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')
[14104]269        form_fields = form_fields.omit('def_adm')
[14084]270        return form_fields
271
[13362]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:
[14298]283            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
[13362]284            'clearance_locked', 'clr_code', 'officer_comment',
[14104]285            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
286        form_fields = form_fields.omit('def_adm')
[13362]287        return form_fields
288
[11846]289class CustomStartClearancePage(StartClearancePage):
[13360]290    with_ac = False
[11846]291
[13351]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
[8911]302class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
303    """ Page to view an online payment ticket
304    """
305    grok.context(ICustomStudentOnlinePayment)
[9853]306    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[9990]307        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
[8911]308    form_fields[
309        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
310    form_fields[
311        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
312
[13523]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
[13059]322class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
[8911]323    """Deliver a PDF slip of the context.
324    """
325    grok.context(ICustomStudentOnlinePayment)
[9853]326    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
[15471]327        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item',
[15689]328        'p_split_data', 'p_combi')
[8911]329    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9496]330    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[9914]331
[11625]332    @property
333    def note(self):
[13408]334        p_session = self.context.p_session
[13405]335        try:
[13408]336            academic_session = grok.getSite()['configuration'][str(p_session)]
[13405]337        except KeyError:
338            academic_session = None
[13425]339        text =  '\n\n The Amount Authorized is inclusive of: '
[13512]340        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
341            and academic_session:
[14385]342            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
343            #union_fee = gateway_net_amt(academic_session.union_fee)
[14244]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.
[14385]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
[14244]360                text += ('School Fee, '
[14385]361                         'Student ID Card Fee, '
362                         'Student Union Dues, '
[15330]363                         'Sports Fee, '
364                         'Library Levy, '
[14385]365                         'Student Welfare Assurance Fee and ')
[14244]366            else:
[14385]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
[14244]373                text += ('School Fee, '
[14385]374                         'Student Union Dues, '
[15330]375                         'Sports Development Fee, '
376                         'Library Development Levy, '
[16934]377                         'LMS + Sundry Fees, '
[14385]378                         'Student Welfare Assurance Fee and ')
[13410]379        elif self.context.p_category in (
380            'clearance_incl', 'clearance_medical_incl') and academic_session:
[14385]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
[13437]389            text += ('Acceptance Fee, '
[14385]390                     'Matriculation Gown Fee, '
391                     'Lapel/File Fee and ')
[11625]392
[14385]393        #return text + '250.0 Naira Transaction Charge.'
394
395        return text + 'Transaction Charge.'
396
[14534]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
[15461]412class CustomUnregisterCoursesView(UnregisterCoursesView):
413    """Unregister course list by student
414    """
415    grok.context(ICustomStudentStudyLevel)
[14981]416
[15461]417    def update(self):
418        if not self.context.__parent__.is_current:
419            emit_lock_message(self)
420            return
[15511]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")
[15512]443        if str(self.context.__parent__.current_level) != self.context.__name__:
[15461]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
[14981]453
[9914]454class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
455    """ Page to display student study levels
456    """
457    grok.context(ICustomStudentStudyLevel)
[10480]458    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[14206]459        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
[9914]460    form_fields[
461        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
462
[14000]463    @property
[15228]464    def translated_values(self):
465        return translated_values(self)
466
467    @property
[14000]468    def show_results(self):
469        isStudent = getattr(
470            self.request.principal, 'user_type', None) == 'student'
[14944]471        try:
472            show_results = grok.getSite()[
473                'configuration'][str(self.context.level_session)].show_results
474        except KeyError:
[14000]475            return False
[14944]476        if isStudent and self.context.student.current_mode not in show_results:
477            return False
[15409]478        #if isStudent and self.context.student.state != RETURNING \
479        #    and self.context.student.current_level == self.context.level:
480        #    return False
[14000]481        return True
482
[13834]483class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
484    """ Page to edit the student study level data
485    """
486    grok.context(ICustomStudentStudyLevel)
487
[14534]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
[13834]495class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
496    """ Page to edit the student study level data by students
497    """
498    grok.context(ICustomStudentStudyLevel)
499
[15330]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):
[15340]511        if not self.context.__parent__.is_current \
512            or self.context.student.studycourse_locked:
513            emit_lock_message(self)
514            return
[15330]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
[13059]582class CustomExportPDFCourseRegistrationSlip(
583    NigeriaExportPDFCourseRegistrationSlip):
[9914]584    """Deliver a PDF slip of the context.
585    """
586    grok.context(ICustomStudentStudyLevel)
587    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
[10102]588        'level_session', 'level_verdict',
[14206]589        'validated_by', 'validation_date', 'gpa', 'level',
[16816]590        'imported_gpa', 'imported_cgpa', 'transcript_remark')
[9914]591
[10269]592    omit_fields = ('password', 'suspended', 'suspended_comment',
[10689]593        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
[15408]594        'department', 'current_mode', 'current_level', 'flash_notice',
[16816]595        )
[10269]596
[14000]597    @property
598    def show_results(self):
599        isStudent = getattr(
600            self.request.principal, 'user_type', None) == 'student'
[14944]601        try:
602            show_results = grok.getSite()[
603                'configuration'][str(self.context.level_session)].show_results
604        except KeyError:
[14302]605            return False
[14944]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
[14302]611        return True
[14000]612
[13038]613    def update(self):
614        if self.context.student.state != REGISTERED \
[13051]615            and self.context.student.current_level == self.context.level:
[13038]616            self.flash(_('Forbidden'), type="warning")
617            self.redirect(self.url(self.context))
[14000]618            return
[13038]619
[9914]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 = ''
[13788]627        if self.context.student.is_postgrad:
628            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
629        elif self.context.student.current_mode.endswith('_pt'):
[9914]630            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
[13866]631        line1 = translate(_('Course Registration Slip'),
632            target_language=portal_language) \
[9914]633            + ' %s' % level_title
[13866]634        line2 = translate(_('Session'),
635            target_language=portal_language) \
[9914]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
[13866]642        return translate(_('Units Registered'), target_language=portal_language)
[9914]643
644    def _signatures(self):
[15483]645        if self.context.student.current_mode in (
646            'ug_pt', 'de_pt', 'ug_dsh', 'de_dsh', 'found'):
[13647]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>')],
[13946]653                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
[13647]654                [('', _('Director\'s Signature'))]
655                )
656        if self.context.student.current_mode in (
[15457]657            'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'):
[13649]658            return ([_('Academic Adviser\'s Signature'),
659                _('Faculty Officer\'s Signature'),
660                _('Student\'s Signature')],)
661
[13647]662        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
663            return (
[13676]664                [('I declare that all items of information supplied above are correct:' ,
[13680]665                    _('Student\'s Signature'), '<br>')],
[13676]666                [('We approved the above registration:',
[13680]667                    _('Major Supervisor (Name / Signature)'), '')],
668                [('', _('Co-Supervisor (Name / Signature)'), '')],
[13676]669                [('', _('Head of Department'), '<br>')],
670                [('The student has satisfied the conditions for renewal of '
671                  'registration for graduate school programme in this university:',
[13680]672                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
673                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
[13647]674                )
675        return None
[9914]676
[13647]677
[9914]678    def render(self):
[16041]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
[9914]683        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[16968]684        Sem = translate(_('Term'), target_language=portal_language)
[13866]685        Code = translate(_('Code'), target_language=portal_language)
686        Title = translate(_('Title'), target_language=portal_language)
687        Cred = translate(_('Cred.'), target_language=portal_language)
[14650]688        CC = translate(_('Cat.'), target_language=portal_language)
[14000]689        if self.show_results:
[15317]690            TotalScore = translate(_('Total Score'), target_language=portal_language)
[14000]691            #CA = translate(_('CA'), target_language=portal_language)
692            Grade = translate(_('Grade'), target_language=portal_language)
[13866]693        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
[9914]694            target_language=portal_language)
695        studentview = StudentBasePDFFormPage(self.context.student,
696            self.request, self.omit_fields)
697        students_utils = getUtility(IStudentsUtils)
[10442]698
699        tabledata = []
700        tableheader = []
701        contenttitle = []
702        for i in range(1,7):
703            tabledata.append(sorted(
[16041]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))
[14000]707            if self.show_results:
708                tableheader.append([(Code,'code', 2.0),
709                                   (Title,'title', 7),
[14650]710                                   (Cred, 'credits', 1.4),
711                                   (CC, 'course_category', 1.2),
[15317]712                                   (TotalScore, 'total_score', 1.4),
[14000]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),
[14650]721                                   (CC, 'course_category', 1.2),
[14000]722                                   (Signature, 'dummy', 3),
723                                   ])
[9914]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,
[10442]733            tableheader=tableheader,
734            tabledata=tabledata,
[9914]735            signatures=self._signatures(),
[10269]736            topMargin=topMargin,
737            omit_fields=self.omit_fields
[9914]738            )
[10566]739
[14165]740class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
741    """ Page to display the student's transcript.
742    """
743    grok.require('waeup.viewStudent')
744
[13059]745class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
[10566]746    """Deliver a PDF slip of the context.
747    """
748
[14267]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
[10566]756    def _sigsInFooter(self):
757        return []
758
759    def _signatures(self):
[10922]760
[16143]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 ([(
[16682]774            'Abel A. Imafidon (Manupa)<br />'
[16616]775            'Deputy Registrar<br /> '
[16143]776            'Exams, Records and Data Processing Division <br /> '
777            'For: Registrar')],)
778
[13834]779    def render(self):
780        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[16968]781        Term = translate(_('Term'), target_language=portal_language)
[13866]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)
[13834]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),
[14136]795                         (Score, 'total_score', 1.5),
[13834]796                         (Grade, 'grade', 1.5),
797                         ]
798
[15165]799        pdfstream = students_utils.renderPDFTranscript(
[13834]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(),
[15165]806            digital_sigs=self._digital_sigs(),
807            save_file=self._save_file(),
[13834]808            )
[15165]809        if not pdfstream:
810            self.redirect(self.url(self.context.student))
811            return
812        return pdfstream
[13834]813
[16967]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
[16968]822        Term = translate(_('Term'), target_language=portal_language)
[16967]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
[13059]855class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
[10922]856    """Deliver a PDF Admission slip.
857    """
858
859    @property
860    def label(self):
861        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]862        return translate(_('e-Admission Slip \n'),
863            target_language=portal_language) \
[10922]864            + ' %s' % self.context.display_fullname
[11597]865
[13059]866class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
[11606]867    """Deliver a PDF slip of the context.
868    """
869
870    @property
871    def label(self):
872        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[14114]873        return translate(_('Verification/Clearance Slip\n'),
[13866]874            target_language=portal_language) \
[11606]875            + ' %s' % self.context.display_fullname
876
[14084]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')
[14104]887        form_fields = form_fields.omit('def_adm')
[14084]888        return form_fields
889
[11597]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))
[11602]904            self.context.writeLogMessage(self, '%s assigned' % mnumber)
[11597]905        self.redirect(self.url(self.context))
906        return
907
908    def render(self):
[11607]909        return
910
[13059]911class ExportPDFMatricNumberSlip(UtilityView, grok.View):
[11607]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')
[13713]921    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
[11607]922
923    @property
[13489]924    def title(self):
925        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]926        return translate(_('Matriculation Number'), 'waeup.kofa',
[13489]927            target_language=portal_language)
928
929    @property
[11607]930    def label(self):
931        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]932        return translate(_('Matriculation Number Slip\n'),
933            target_language=portal_language) \
[11607]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)
[11609]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.')
[11607]946        return students_utils.renderPDFAdmissionLetter(self,
947            self.context.student, omit_fields=self.omit_fields,
[13353]948            pre_text=pre_text, post_text='')
949
[13489]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')
[13713]960    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
961                   'certificate', 'flash_notice')
[13489]962
963    @property
964    def title(self):
965        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]966        return translate(_('Personal Data'), 'waeup.kofa',
[13489]967            target_language=portal_language)
968
969    @property
970    def label(self):
971        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[13866]972        return translate(_('Personal Data Slip\n'),
973            target_language=portal_language) \
[13489]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
[15976]984class CustomAccommodationDisplayFormPage(NigeriaAccommodationDisplayFormPage):
985    """ Page to view bed tickets.
986    """
987    with_hostel_selection = True
988
[13462]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
[15713]995class CustomBedTicketAddPage(NigeriaBedTicketAddPage):
[13360]996    with_ac = False
[13380]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.
[13770]1002    """
1003
1004class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
1005    """ Page to display course tickets
1006    """
1007
[14453]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
[13770]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
[15205]1036    form_fields['ticket_session'].for_display = True
[15226]1037    form_fields['imported_ts'].for_display = True
[13770]1038
1039class CustomEditScoresPage(EditScoresPage):
1040    """Page that filters and lists students.
1041    """
1042    grok.template('editscorespage')
1043
[14700]1044    def _searchCatalog(self, session):
1045        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1046        coursetickets = cat.searchResults(
[15876]1047            session=(session, session),
[14700]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 []
[14707]1055        coursetickets_list = [courseticket for courseticket in coursetickets
1056            if courseticket.student.current_mode in score_editing_enabled]
[14700]1057        return coursetickets_list
[13937]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
[15414]1065        ``student_id``, ``score``, ``imported_ts``
1066        and ``ca`` (other cols are ignored).
[13937]1067        """
1068        result = dict()
1069        data = StringIO(uploadfile.read())  # ensure we have something seekable
1070        reader = csv.DictReader(data)
1071        for row in reader:
[15414]1072            if not ('student_id' in row and 'score' in row and 'ca' in row and
1073                'imported_ts' in row):
[13937]1074                continue
[15414]1075            result[row['student_id']] = (
1076                row['score'], row['ca'], row['imported_ts'])
[13937]1077        return result
1078
[14288]1079    def _update_scores(self, form):
[13937]1080        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
[14288]1081        error = ''
[13937]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")
[14288]1090                    return False
[13937]1091            else:
1092                self.flash(
1093                    _('No file provided.'), type="danger")
[14288]1094                return False
[13937]1095        else:
[15414]1096            formvals = dict(zip(form['sids'], zip(
[15822]1097                form['scores'], form['cas'], form['imported_ts'])))
[14149]1098        for ticket in self.editable_tickets:
[13937]1099            ticket_error = False
1100            score = ticket.score
1101            ca = ticket.ca
[15414]1102            imported_ts = ticket.imported_ts
[13937]1103            sid = ticket.student.student_id
[15896]1104            if sid not in formvals:
1105                continue
[13937]1106            if formvals[sid][0] == '':
1107                score = None
1108            if formvals[sid][1] == '':
1109                ca = None
[15414]1110            if formvals[sid][2] == '':
1111                imported_ts = None
[13937]1112            try:
1113                if formvals[sid][0]:
1114                    score = int(formvals[sid][0])
1115                if formvals[sid][1]:
1116                    ca = int(formvals[sid][1])
[15414]1117                if formvals[sid][2]:
1118                    imported_ts = int(formvals[sid][2])
[13937]1119            except ValueError:
1120                error += '%s, ' % ticket.student.display_fullname
1121                ticket_error = True
[16240]1122            if score and ca and not ticket_error and score + ca > 100:
1123                error += '%s, ' % ticket.student.display_fullname
1124                ticket_error = True
[13937]1125            if not ticket_error and ticket.score != score:
[14113]1126                try:
1127                    ticket.score = score
1128                except TooBig:
1129                    error += '%s, ' % ticket.student.display_fullname
1130                    ticket_error = True
1131                    pass
[13937]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:
[14113]1137                try:
1138                    ticket.ca = ca
1139                except TooBig:
1140                    error += '%s, ' % ticket.student.display_fullname
1141                    pass
[13937]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))
[15414]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))
[13937]1156        if error:
[15414]1157            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
[14113]1158              % error.strip(', ')), type="danger")
[14288]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))
[13939]1172            return
[14288]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.'))
[13900]1194        return
1195
1196class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1197    """Deliver a PDF slip of course tickets for a lecturer.
1198    """
1199
[15343]1200    note = u'\nUpgraded scores are with asterisks.'
[15247]1201
[14315]1202    def data(self, session):
[14704]1203        #site = grok.getSite()
[13900]1204        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1205        coursetickets = cat.searchResults(
1206            session=(session, session),
1207            code=(self.context.code, self.context.code)
1208            )
[14707]1209        # Apply filter
1210        try:
1211            score_editing_enabled = grok.getSite()[
1212                'configuration'][str(session)].score_editing_enabled
[15453]1213            if checkPermission('waeup.manageAcademics', self.context):
1214                score_editing_enabled = True
[14707]1215            coursetickets_filtered = [courseticket
1216                for courseticket in coursetickets
[15454]1217                if (checkPermission('waeup.manageAcademics', self.context)
1218                    or (courseticket.student.current_mode in
1219                        score_editing_enabled))
[15236]1220                and courseticket.total_score is not None
1221                and courseticket.__parent__.__parent__.is_current]
[14707]1222        except KeyError:
1223            coursetickets_filtered = coursetickets
[15631]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]
[13963]1228        header = [[_(''),
[15006]1229                   _('Student Id'),
[13963]1230                   _('Matric No.'),
[14703]1231                   #_('Reg. No.'),
1232                   #_('Fullname'),
1233                   #_('Status'),
1234                   #_('Course of\nStudies'),
[14704]1235                   _('Department'),
[13900]1236                   _('Level'),
[15201]1237                   _(' CA  '),
1238                   _('Exam\nScore'),
[13964]1239                   _('Total '),
[13963]1240                   _('Grade'),
1241                   ],]
[15631]1242        sorted_tickets = sorted(coursetickets_filtered, # editable_tickets,
[15912]1243            key=lambda ticket: str(ticket.student.depcode)
1244                               + str(ticket.student.faccode)
[15911]1245                               + str(ticket.student.matric_number)
[15910]1246                               )
[14288]1247        no = 1
[13907]1248        tickets = []
[14315]1249        passed = 0
1250        failed = 0
[15229]1251        with_ca = False
[15822]1252        grade_stats = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0, 'F':0, }
[14288]1253        for ticket in sorted_tickets:
[15229]1254            if ticket.ca > 0:
1255                with_ca = True
[15006]1256            total = ticket.total_score
[15232]1257            if getattr(ticket, 'imported_ts', None):
[15245]1258                total = "**%s**" % ticket.imported_ts
[15006]1259            grade = ticket._getGradeWeightFromScore[0]
[15822]1260            if grade in grade_stats.keys():
1261                grade_stats[grade] += 1
[15006]1262            if grade in ('F', '-'):
1263                failed += 1
[13963]1264            else:
[15006]1265                passed += 1
[13964]1266            fullname = textwrap.fill(ticket.student.display_fullname, 30)
[14704]1267            #deptitle = site['faculties'][ticket.student.faccode][
1268            #    ticket.student.depcode].longtitle
[15240]1269            row = [str(no),
[15006]1270                  ticket.student.student_id,
[13963]1271                  ticket.student.matric_number,
[14703]1272                  #ticket.student.reg_number,
1273                  #fullname,
1274                  #ticket.student.translated_state,
1275                  #ticket.student.certcode,
[15913]1276                  str(ticket.student.faccode) + ' / ' + str(ticket.student.depcode),
[13900]1277                  ticket.level,
[15201]1278                  ticket.ca,
1279                  ticket.score,
[13963]1280                  total,
[13964]1281                  grade,
[13963]1282                  ]
[13907]1283            tickets.append(row)
[13963]1284            no += 1
[14317]1285        total = passed + failed
[14320]1286        passed_perc = 0
1287        failed_perc = 0
[14317]1288        if total:
[15408]1289            passed_perc = round(100.0 * passed / total)
1290            failed_perc = round(100.0 * failed / total)
[14710]1291        dep = self.context.__parent__.__parent__.longtitle
1292        fac = self.context.__parent__.__parent__.__parent__.longtitle
[15229]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])
[14320]1311        return header + tickets, [
[15822]1312            dep, fac, total, passed, passed_perc, failed, failed_perc, grade_stats]
[14288]1313
[14703]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']
[15866]1318        lecturers = sorted(lecturers)
[14703]1319        lecturers =  ', '.join(lecturers)
1320        students_utils = getUtility(IStudentsUtils)
1321        # only orientation is different
1322        return students_utils.renderPDFCourseticketsOverview(
[15424]1323            self, 'coursetickets',
1324            session, self.data(session), lecturers, '', 45, self.note)
[14703]1325
[14288]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)
[14306]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')
[15864]1364        return
1365
[17077]1366class CustomAddStudyLevelFormPage(AddStudyLevelFormPage):
1367    """ This page is temporarily locked.
1368    """
1369    def update(self):
1370        if self.context.student.current_mode == 'ug_ft' \
[17098]1371            and self.context.student.current_session == 2021 \
1372            and not self.context.student.entry_session == 2021:
[17077]1373            emit_lock_message(self)
1374            return
1375        super(CustomAddStudyLevelFormPage, self).update()
1376        return
Note: See TracBrowser for help on using the repository browser.