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

Last change on this file since 17959 was 17958, checked in by Henrik Bettermann, 7 days ago

Change signature again.

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