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

Last change on this file since 17671 was 17631, checked in by Henrik Bettermann, 14 months ago

Re-enable course registration.

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