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

Last change on this file since 16317 was 16240, checked in by Henrik Bettermann, 4 years ago

Don't allow the total outcome of course grade exceed 100 (ie if CA + Score > 100, return "total score is greater than 100").

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