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

Last change on this file since 15406 was 15388, checked in by Henrik Bettermann, 6 years ago

Restrict study modes ug_dsh and de_dsh from paying some following items.

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