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

Last change on this file since 16935 was 16934, checked in by Henrik Bettermann, 3 years ago

Addm LMS + Sundry Fees to school fee.

  • Property svn:keywords set to Id
File size: 52.8 KB
Line 
1## $Id: browser.py 16934 2022-04-26 14:57:39Z 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                         'LMS + Sundry Fees, '
378                         'Student Welfare Assurance Fee and ')
379        elif self.context.p_category in (
380            'clearance_incl', 'clearance_medical_incl') and academic_session:
381
382            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
383            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
384            #text += ('Acceptance Fee, '
385            #         '%s Naira Matriculation Gown Fee, '
386            #         '%s Naira Lapel/File Fee and '
387            #         % (matric_gown_fee, lapel_fee))
388
389            text += ('Acceptance Fee, '
390                     'Matriculation Gown Fee, '
391                     'Lapel/File Fee and ')
392
393        #return text + '250.0 Naira Transaction Charge.'
394
395        return text + 'Transaction Charge.'
396
397class CustomStudyCourseManageFormPage(StudyCourseManageFormPage):
398    """ Page to edit the student study course data
399    """
400    grok.context(ICustomStudentStudyCourse)
401
402    @property
403    def form_fields(self):
404        if self.context.is_postgrad:
405            form_fields = grok.AutoFields(ICustomStudentStudyCourse).omit(
406                'previous_verdict')
407        else:
408            form_fields = grok.AutoFields(ICustomStudentStudyCourse)
409        form_fields['imported_cgpa'].for_display = True
410        return form_fields
411
412class CustomUnregisterCoursesView(UnregisterCoursesView):
413    """Unregister course list by student
414    """
415    grok.context(ICustomStudentStudyLevel)
416
417    def update(self):
418        if not self.context.__parent__.is_current:
419            emit_lock_message(self)
420            return
421        #try:
422        #    academic_session = grok.getSite()['configuration'][
423        #        str(self.context.level_session)]
424        #    if self.context.student.is_postgrad:
425        #        deadline = academic_session.coursereg_deadline_pg
426        #    elif self.context.student.current_mode.startswith('dp'):
427        #        deadline = academic_session.coursereg_deadline_dp
428        #    elif self.context.student.current_mode in (
429        #        'ug_pt', 'de_pt', 'de_dsh', 'ug_dsh'):
430        #        deadline = academic_session.coursereg_deadline_pt
431        #    elif self.context.student.current_mode == 'found':
432        #        deadline = academic_session.coursereg_deadline_found
433        #    elif self.context.student.current_mode == 'bridge':
434        #        deadline = academic_session.coursereg_deadline_bridge
435        #    else:
436        #        deadline = academic_session.coursereg_deadline
437        #except (TypeError, KeyError):
438        #    deadline = None
439        #if deadline and deadline < datetime.now(pytz.utc):
440        #    self.flash(_(
441        #        "Course registration has ended. "
442        #        "Unregistration is disabled."), type="danger")
443        if str(self.context.__parent__.current_level) != self.context.__name__:
444            self.flash(_('This is not your current level.'), type="danger")
445        elif self.context.student.state == REGISTERED:
446            IWorkflowInfo(self.context.student).fireTransition('reset7')
447            message = _('Course list has been unregistered.')
448            self.flash(message)
449        else:
450            self.flash(_('You are in the wrong state.'), type="warning")
451        self.redirect(self.url(self.context))
452        return
453
454class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
455    """ Page to display student study levels
456    """
457    grok.context(ICustomStudentStudyLevel)
458    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
459        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
460    form_fields[
461        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
462
463    @property
464    def translated_values(self):
465        return translated_values(self)
466
467    @property
468    def show_results(self):
469        isStudent = getattr(
470            self.request.principal, 'user_type', None) == 'student'
471        try:
472            show_results = grok.getSite()[
473                'configuration'][str(self.context.level_session)].show_results
474        except KeyError:
475            return False
476        if isStudent and self.context.student.current_mode not in show_results:
477            return False
478        #if isStudent and self.context.student.state != RETURNING \
479        #    and self.context.student.current_level == self.context.level:
480        #    return False
481        return True
482
483class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
484    """ Page to edit the student study level data
485    """
486    grok.context(ICustomStudentStudyLevel)
487
488    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
489        'validation_date', 'validated_by', 'total_credits', 'gpa', 'level',
490        'total_credits_s1', 'total_credits_s2')
491
492    form_fields['imported_gpa'].for_display = True
493    form_fields['imported_cgpa'].for_display = True
494
495class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
496    """ Page to edit the student study level data by students
497    """
498    grok.context(ICustomStudentStudyLevel)
499
500class StudyLevelRepairFormPage(KofaEditFormPage):
501    """ Page to repair the student study level data by students
502    """
503    grok.context(IStudentStudyLevel)
504    grok.name('repair')
505    grok.require('waeup.editStudyLevel')
506    grok.template('studylevelrepairpage')
507    pnav = 4
508    placeholder = _('Enter valid course code')
509
510    def update(self, ADD=None, course=None):
511        if not self.context.__parent__.is_current \
512            or self.context.student.studycourse_locked:
513            emit_lock_message(self)
514            return
515        try:
516            studylevel_repair_enabled = grok.getSite()['configuration'][
517                str(self.context.level_session)].studylevel_repair_enabled
518        except KeyError:
519            emit_lock_message(self)
520            return
521        if not studylevel_repair_enabled:
522            emit_lock_message(self)
523            return
524        super(StudyLevelRepairFormPage, self).update()
525        if ADD is not None:
526            if not course:
527                self.flash(_('No valid course code entered.'), type="warning")
528                return
529            cat = queryUtility(ICatalog, name='courses_catalog')
530            result = cat.searchResults(code=(course, course))
531            if len(result) != 1:
532                self.flash(_('Course not found.'), type="warning")
533                return
534            course = list(result)[0]
535            addCourseTicket(self, course)
536        return
537
538    @property
539    def label(self):
540        # Here we know that the cookie has been set
541        lang = self.request.cookies.get('kofa.language')
542        level_title = translate(self.context.level_title, 'waeup.kofa',
543            target_language=lang)
544        return _('Repair course list of ${a}',
545            mapping = {'a':level_title})
546
547    @property
548    def translated_values(self):
549        return translated_values(self)
550
551    def _delCourseTicket(self, **data):
552        form = self.request.form
553        if 'val_id' in form:
554            child_id = form['val_id']
555        else:
556            self.flash(_('No ticket selected.'), type="warning")
557            self.redirect(self.url(self.context, '@@edit'))
558            return
559        if not isinstance(child_id, list):
560            child_id = [child_id]
561        deleted = []
562        for id in child_id:
563            # Students are not allowed to remove core tickets
564            if id in self.context and \
565                self.context[id].removable_by_student:
566                del self.context[id]
567                deleted.append(id)
568        if len(deleted):
569            self.flash(_('Successfully removed: ${a}',
570                mapping = {'a':', '.join(deleted)}))
571            self.context.writeLogMessage(
572                self,'removed: %s at %s' %
573                (', '.join(deleted), self.context.level))
574        self.redirect(self.url(self.context, u'@@repair'))
575        return
576
577    @jsaction(_('Remove selected tickets'))
578    def delCourseTicket(self, **data):
579        self._delCourseTicket(**data)
580        return
581
582class CustomExportPDFCourseRegistrationSlip(
583    NigeriaExportPDFCourseRegistrationSlip):
584    """Deliver a PDF slip of the context.
585    """
586    grok.context(ICustomStudentStudyLevel)
587    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
588        'level_session', 'level_verdict',
589        'validated_by', 'validation_date', 'gpa', 'level',
590        'imported_gpa', 'imported_cgpa', 'transcript_remark')
591
592    omit_fields = ('password', 'suspended', 'suspended_comment',
593        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
594        'department', 'current_mode', 'current_level', 'flash_notice',
595        )
596
597    @property
598    def show_results(self):
599        isStudent = getattr(
600            self.request.principal, 'user_type', None) == 'student'
601        try:
602            show_results = grok.getSite()[
603                'configuration'][str(self.context.level_session)].show_results
604        except KeyError:
605            return False
606        if isStudent and self.context.student.current_mode not in show_results:
607            return False
608        if isStudent and self.context.student.state != RETURNING \
609            and self.context.student.current_level == self.context.level:
610            return False
611        return True
612
613    def update(self):
614        if self.context.student.state != REGISTERED \
615            and self.context.student.current_level == self.context.level:
616            self.flash(_('Forbidden'), type="warning")
617            self.redirect(self.url(self.context))
618            return
619
620    @property
621    def label(self):
622        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
623        lang = self.request.cookies.get('kofa.language', portal_language)
624        level_title = translate(self.context.level_title, 'waeup.kofa',
625            target_language=lang)
626        line0 = ''
627        if self.context.student.is_postgrad:
628            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
629        elif self.context.student.current_mode.endswith('_pt'):
630            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
631        line1 = translate(_('Course Registration Slip'),
632            target_language=portal_language) \
633            + ' %s' % level_title
634        line2 = translate(_('Session'),
635            target_language=portal_language) \
636            + ' %s' % self.context.getSessionString
637        return '%s%s\n%s' % (line0, line1, line2)
638
639    @property
640    def title(self):
641        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
642        return translate(_('Units Registered'), target_language=portal_language)
643
644    def _signatures(self):
645        if self.context.student.current_mode in (
646            'ug_pt', 'de_pt', 'ug_dsh', 'de_dsh', 'found'):
647            return (
648                [('I have selected the course on the advise of my Head of '
649                 'Department. <br>', _('Student\'s Signature'), '<br>')],
650                [('This student has satisfied the department\'s requirements. '
651                 'I recommend to approve the course registration. <br>',
652                 _('Head of Department\'s Signature'), '<br>')],
653                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
654                [('', _('Director\'s Signature'))]
655                )
656        if self.context.student.current_mode in (
657            'de_ft', 'ug_ft', 'dp_ft', 'transfer', 'bridge'):
658            return ([_('Academic Adviser\'s Signature'),
659                _('Faculty Officer\'s Signature'),
660                _('Student\'s Signature')],)
661
662        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
663            return (
664                [('I declare that all items of information supplied above are correct:' ,
665                    _('Student\'s Signature'), '<br>')],
666                [('We approved the above registration:',
667                    _('Major Supervisor (Name / Signature)'), '')],
668                [('', _('Co-Supervisor (Name / Signature)'), '')],
669                [('', _('Head of Department'), '<br>')],
670                [('The student has satisfied the conditions for renewal of '
671                  'registration for graduate school programme in this university:',
672                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
673                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
674                )
675        return None
676
677
678    def render(self):
679        if not self.context.student.current_mode:
680            self.flash('No certificate assigned.', type="danger")
681            self.redirect(self.url(self.context))
682            return
683        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
684        Sem = translate(_('Sem.'), target_language=portal_language)
685        Code = translate(_('Code'), target_language=portal_language)
686        Title = translate(_('Title'), target_language=portal_language)
687        Cred = translate(_('Cred.'), target_language=portal_language)
688        CC = translate(_('Cat.'), target_language=portal_language)
689        if self.show_results:
690            TotalScore = translate(_('Total Score'), target_language=portal_language)
691            #CA = translate(_('CA'), target_language=portal_language)
692            Grade = translate(_('Grade'), target_language=portal_language)
693        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
694            target_language=portal_language)
695        studentview = StudentBasePDFFormPage(self.context.student,
696            self.request, self.omit_fields)
697        students_utils = getUtility(IStudentsUtils)
698
699        tabledata = []
700        tableheader = []
701        contenttitle = []
702        for i in range(1,7):
703            tabledata.append(sorted(
704                [value for value in self.context.values()
705                 if value.semester == i and not value.outstanding],
706                 key=lambda value: str(value.semester) + value.code))
707            if self.show_results:
708                tableheader.append([(Code,'code', 2.0),
709                                   (Title,'title', 7),
710                                   (Cred, 'credits', 1.4),
711                                   (CC, 'course_category', 1.2),
712                                   (TotalScore, 'total_score', 1.4),
713                                   #(CA, 'ca', 1.4),
714                                   (Grade, 'grade', 1.4),
715                                   (Signature, 'dummy', 3),
716                                   ])
717            else:
718                tableheader.append([(Code,'code', 2.0),
719                                   (Title,'title', 7),
720                                   (Cred, 'credits', 1.5),
721                                   (CC, 'course_category', 1.2),
722                                   (Signature, 'dummy', 3),
723                                   ])
724        if len(self.label.split('\n')) == 3:
725            topMargin = 1.9
726        elif len(self.label.split('\n')) == 2:
727            topMargin = 1.7
728        else:
729            topMargin = 1.5
730        return students_utils.renderPDF(
731            self, 'course_registration_slip.pdf',
732            self.context.student, studentview,
733            tableheader=tableheader,
734            tabledata=tabledata,
735            signatures=self._signatures(),
736            topMargin=topMargin,
737            omit_fields=self.omit_fields
738            )
739
740class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
741    """ Page to display the student's transcript.
742    """
743    grok.require('waeup.viewStudent')
744
745class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
746    """Deliver a PDF slip of the context.
747    """
748
749    note = _("""
750<br /><br /><br /><br />
751<font size='10'>
752<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
753</font>
754""")
755
756    def _sigsInFooter(self):
757        return []
758
759    def _signatures(self):
760
761        #if self.context.student.state == GRADUATED:
762        #    return ([(
763        #        'O.O OHIKHENA (Manupa)<br />'
764        #        'Principal Asst. Registrar<br /> '
765        #        'Exams, Records and Data Processing Division <br /> '
766        #        'For: Registrar')],)
767        #elif self.context.certificate is not None:
768        #    dep = self.context.certificate.__parent__.__parent__
769        #    if dep.officer_1:
770        #        return ([(dep.officer_1)],)
771        #return
772
773        return ([(
774            'Abel A. Imafidon (Manupa)<br />'
775            'Deputy Registrar<br /> '
776            'Exams, Records and Data Processing Division <br /> '
777            'For: Registrar')],)
778
779    def render(self):
780        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
781        Term = translate(_('Sem.'), target_language=portal_language)
782        Code = translate(_('Code'), target_language=portal_language)
783        Title = translate(_('Title'), target_language=portal_language)
784        Cred = translate(_('Credits'), target_language=portal_language)
785        Score = translate(_('Score'), target_language=portal_language)
786        Grade = translate(_('Grade'), target_language=portal_language)
787        studentview = StudentBasePDFFormPage(self.context.student,
788            self.request, self.omit_fields)
789        students_utils = getUtility(IStudentsUtils)
790
791        tableheader = [(Code,'code', 2.5),
792                         (Title,'title', 7),
793                         (Term, 'semester', 1.5),
794                         (Cred, 'credits', 1.5),
795                         (Score, 'total_score', 1.5),
796                         (Grade, 'grade', 1.5),
797                         ]
798
799        pdfstream = students_utils.renderPDFTranscript(
800            self, 'transcript.pdf',
801            self.context.student, studentview,
802            omit_fields=self.omit_fields,
803            tableheader=tableheader,
804            signatures=self._signatures(),
805            sigs_in_footer=self._sigsInFooter(),
806            digital_sigs=self._digital_sigs(),
807            save_file=self._save_file(),
808            )
809        if not pdfstream:
810            self.redirect(self.url(self.context.student))
811            return
812        return pdfstream
813
814class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
815    """Deliver a PDF Admission slip.
816    """
817
818    @property
819    def label(self):
820        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
821        return translate(_('e-Admission Slip \n'),
822            target_language=portal_language) \
823            + ' %s' % self.context.display_fullname
824
825class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
826    """Deliver a PDF slip of the context.
827    """
828
829    @property
830    def label(self):
831        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
832        return translate(_('Verification/Clearance Slip\n'),
833            target_language=portal_language) \
834            + ' %s' % self.context.display_fullname
835
836    @property
837    def form_fields(self):
838        if self.context.is_postgrad:
839            form_fields = grok.AutoFields(
840                ICustomPGStudentClearance).omit('clearance_locked')
841        else:
842            form_fields = grok.AutoFields(
843                ICustomUGStudentClearance).omit('clearance_locked')
844        if not getattr(self.context, 'officer_comment'):
845            form_fields = form_fields.omit('officer_comment')
846        form_fields = form_fields.omit('def_adm')
847        return form_fields
848
849class StudentGetMatricNumberPage(UtilityView, grok.View):
850    """ Construct and set the matriculation number.
851    """
852    grok.context(IStudent)
853    grok.name('get_matric_number')
854    grok.require('waeup.viewStudent')
855
856    def update(self):
857        students_utils = getUtility(IStudentsUtils)
858        msg, mnumber = students_utils.setMatricNumber(self.context)
859        if msg:
860            self.flash(msg, type="danger")
861        else:
862            self.flash(_('Matriculation number %s assigned.' % mnumber))
863            self.context.writeLogMessage(self, '%s assigned' % mnumber)
864        self.redirect(self.url(self.context))
865        return
866
867    def render(self):
868        return
869
870class ExportPDFMatricNumberSlip(UtilityView, grok.View):
871    """Deliver a PDF notification slip.
872    """
873    grok.context(ICustomStudent)
874    grok.name('matric_number_slip.pdf')
875    grok.require('waeup.viewStudent')
876    prefix = 'form'
877
878    form_fields = grok.AutoFields(ICustomStudent).select(
879        'student_id', 'matric_number')
880    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
881
882    @property
883    def title(self):
884        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
885        return translate(_('Matriculation Number'), 'waeup.kofa',
886            target_language=portal_language)
887
888    @property
889    def label(self):
890        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
891        return translate(_('Matriculation Number Slip\n'),
892            target_language=portal_language) \
893            + ' %s' % self.context.display_fullname
894
895    def render(self):
896        if self.context.state not in (PAID,) or not self.context.is_fresh \
897            or not self.context.matric_number:
898            self.flash('Not allowed.', type="danger")
899            self.redirect(self.url(self.context))
900            return
901        students_utils = getUtility(IStudentsUtils)
902        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
903                     'payments have been received and your matriculation ' +
904                     'number generated with details as follows.')
905        return students_utils.renderPDFAdmissionLetter(self,
906            self.context.student, omit_fields=self.omit_fields,
907            pre_text=pre_text, post_text='')
908
909class ExportPersonalDataSlip(UtilityView, grok.View):
910    """Deliver a PDF notification slip.
911    """
912    grok.context(ICustomStudent)
913    grok.name('personal_data_slip.pdf')
914    grok.require('waeup.viewStudent')
915    prefix = 'form'
916    note = None
917
918    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
919    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
920                   'certificate', 'flash_notice')
921
922    @property
923    def title(self):
924        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
925        return translate(_('Personal Data'), 'waeup.kofa',
926            target_language=portal_language)
927
928    @property
929    def label(self):
930        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
931        return translate(_('Personal Data Slip\n'),
932            target_language=portal_language) \
933            + ' %s' % self.context.display_fullname
934
935    def render(self):
936        studentview = StudentBasePDFFormPage(self.context.student,
937            self.request, self.omit_fields)
938        students_utils = getUtility(IStudentsUtils)
939        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
940            self.context.student, studentview, note=self.note,
941            omit_fields=self.omit_fields)
942
943class CustomAccommodationDisplayFormPage(NigeriaAccommodationDisplayFormPage):
944    """ Page to view bed tickets.
945    """
946    with_hostel_selection = True
947
948class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
949    """ Page to manage bed tickets.
950    This manage form page is for both students and students officers.
951    """
952    with_hostel_selection = True
953
954class CustomBedTicketAddPage(NigeriaBedTicketAddPage):
955    with_ac = False
956
957class CustomStudentFilesUploadPage(StudentFilesUploadPage):
958    """ View to upload files by student. Inherit from same class in
959    base package, not from kofacustom.nigeria which
960    requires that no application slip exists.
961    """
962
963class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
964    """ Page to display course tickets
965    """
966
967    @property
968    def show_results(self):
969        isStudent = getattr(
970            self.request.principal, 'user_type', None) == 'student'
971        if isStudent:
972            return False
973        return True
974
975    @property
976    def form_fields(self):
977        if self.show_results:
978            return grok.AutoFields(ICustomCourseTicket)
979        else:
980            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
981
982class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
983    """ Page to manage course tickets
984    """
985    form_fields = grok.AutoFields(ICustomCourseTicket)
986    form_fields['title'].for_display = True
987    form_fields['fcode'].for_display = True
988    form_fields['dcode'].for_display = True
989    form_fields['semester'].for_display = True
990    form_fields['passmark'].for_display = True
991    form_fields['credits'].for_display = True
992    form_fields['mandatory'].for_display = False
993    form_fields['automatic'].for_display = True
994    form_fields['carry_over'].for_display = True
995    form_fields['ticket_session'].for_display = True
996    form_fields['imported_ts'].for_display = True
997
998class CustomEditScoresPage(EditScoresPage):
999    """Page that filters and lists students.
1000    """
1001    grok.template('editscorespage')
1002
1003    def _searchCatalog(self, session):
1004        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1005        coursetickets = cat.searchResults(
1006            session=(session, session),
1007            code=(self.context.code, self.context.code)
1008            )
1009        try:
1010            score_editing_enabled = grok.getSite()[
1011                'configuration'][str(session)].score_editing_enabled
1012        except KeyError:
1013            return []
1014        coursetickets_list = [courseticket for courseticket in coursetickets
1015            if courseticket.student.current_mode in score_editing_enabled]
1016        return coursetickets_list
1017
1018    def _extract_uploadfile(self, uploadfile):
1019        """Get a mapping of student-ids to scores.
1020
1021        The mapping is constructed by reading contents from `uploadfile`.
1022
1023        We expect uploadfile to be a regular CSV file with columns
1024        ``student_id``, ``score``, ``imported_ts``
1025        and ``ca`` (other cols are ignored).
1026        """
1027        result = dict()
1028        data = StringIO(uploadfile.read())  # ensure we have something seekable
1029        reader = csv.DictReader(data)
1030        for row in reader:
1031            if not ('student_id' in row and 'score' in row and 'ca' in row and
1032                'imported_ts' in row):
1033                continue
1034            result[row['student_id']] = (
1035                row['score'], row['ca'], row['imported_ts'])
1036        return result
1037
1038    def _update_scores(self, form):
1039        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
1040        error = ''
1041        if 'UPDATE_FILE' in form:
1042            if form['uploadfile']:
1043                try:
1044                    formvals = self._extract_uploadfile(form['uploadfile'])
1045                except:
1046                    self.flash(
1047                        _('Uploaded file contains illegal data. Ignored'),
1048                        type="danger")
1049                    return False
1050            else:
1051                self.flash(
1052                    _('No file provided.'), type="danger")
1053                return False
1054        else:
1055            formvals = dict(zip(form['sids'], zip(
1056                form['scores'], form['cas'], form['imported_ts'])))
1057        for ticket in self.editable_tickets:
1058            ticket_error = False
1059            score = ticket.score
1060            ca = ticket.ca
1061            imported_ts = ticket.imported_ts
1062            sid = ticket.student.student_id
1063            if sid not in formvals:
1064                continue
1065            if formvals[sid][0] == '':
1066                score = None
1067            if formvals[sid][1] == '':
1068                ca = None
1069            if formvals[sid][2] == '':
1070                imported_ts = None
1071            try:
1072                if formvals[sid][0]:
1073                    score = int(formvals[sid][0])
1074                if formvals[sid][1]:
1075                    ca = int(formvals[sid][1])
1076                if formvals[sid][2]:
1077                    imported_ts = int(formvals[sid][2])
1078            except ValueError:
1079                error += '%s, ' % ticket.student.display_fullname
1080                ticket_error = True
1081            if score and ca and not ticket_error and score + ca > 100:
1082                error += '%s, ' % ticket.student.display_fullname
1083                ticket_error = True
1084            if not ticket_error and ticket.score != score:
1085                try:
1086                    ticket.score = score
1087                except TooBig:
1088                    error += '%s, ' % ticket.student.display_fullname
1089                    ticket_error = True
1090                    pass
1091                ticket.student.__parent__.logger.info(
1092                    '%s - %s %s/%s score updated (%s)' %
1093                    (ob_class, ticket.student.student_id,
1094                     ticket.level, ticket.code, score))
1095            if not ticket_error and ticket.ca != ca:
1096                try:
1097                    ticket.ca = ca
1098                except TooBig:
1099                    error += '%s, ' % ticket.student.display_fullname
1100                    pass
1101                ticket.student.__parent__.logger.info(
1102                    '%s - %s %s/%s ca updated (%s)' %
1103                    (ob_class, ticket.student.student_id,
1104                     ticket.level, ticket.code, ca))
1105            if not ticket_error and ticket.imported_ts != imported_ts:
1106                try:
1107                    ticket.imported_ts = imported_ts
1108                except TooBig:
1109                    error += '%s, ' % ticket.student.display_fullname
1110                    pass
1111                ticket.student.__parent__.logger.info(
1112                    '%s - %s %s/%s imported_ts updated (%s)' %
1113                    (ob_class, ticket.student.student_id,
1114                     ticket.level, ticket.code, imported_ts))
1115        if error:
1116            self.flash(_('Error: Score(s), CA(s) and Imported TS(s) of %s have not be updated. '
1117              % error.strip(', ')), type="danger")
1118        return True
1119
1120class EditPreviousSessionScoresPage(CustomEditScoresPage):
1121
1122    grok.name('edit_prev_scores')
1123
1124    def update(self,  *args, **kw):
1125        form = self.request.form
1126        self.current_academic_session = grok.getSite()[
1127            'configuration'].current_academic_session
1128        if self.context.__parent__.__parent__.score_editing_disabled:
1129            self.flash(_('Score editing disabled.'), type="warning")
1130            self.redirect(self.url(self.context))
1131            return
1132        if not self.current_academic_session:
1133            self.flash(_('Current academic session not set.'), type="warning")
1134            self.redirect(self.url(self.context))
1135            return
1136        previous_session = self.current_academic_session - 1
1137        self.session_title = academic_sessions_vocab.getTerm(
1138            previous_session).title
1139        self.tickets = self._searchCatalog(previous_session)
1140        if not self.tickets:
1141            self.flash(_('No student found.'), type="warning")
1142            self.redirect(self.url(self.context))
1143            return
1144        self.editable_tickets = [
1145            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
1146        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
1147            return
1148        if not self.editable_tickets:
1149            return
1150        success = self._update_scores(form)
1151        if success:
1152            self.flash(_('You successfully updated course results.'))
1153        return
1154
1155class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
1156    """Deliver a PDF slip of course tickets for a lecturer.
1157    """
1158
1159    note = u'\nUpgraded scores are with asterisks.'
1160
1161    def data(self, session):
1162        #site = grok.getSite()
1163        cat = queryUtility(ICatalog, name='coursetickets_catalog')
1164        coursetickets = cat.searchResults(
1165            session=(session, session),
1166            code=(self.context.code, self.context.code)
1167            )
1168        # Apply filter
1169        try:
1170            score_editing_enabled = grok.getSite()[
1171                'configuration'][str(session)].score_editing_enabled
1172            if checkPermission('waeup.manageAcademics', self.context):
1173                score_editing_enabled = True
1174            coursetickets_filtered = [courseticket
1175                for courseticket in coursetickets
1176                if (checkPermission('waeup.manageAcademics', self.context)
1177                    or (courseticket.student.current_mode in
1178                        score_editing_enabled))
1179                and courseticket.total_score is not None
1180                and courseticket.__parent__.__parent__.is_current]
1181        except KeyError:
1182            coursetickets_filtered = coursetickets
1183        # In AAUE only editable tickets can be printed (deactivated on 02/10/19)
1184        #editable_tickets = [
1185        #    ticket for ticket in coursetickets_filtered
1186        #    if ticket.editable_by_lecturer]
1187        header = [[_(''),
1188                   _('Student Id'),
1189                   _('Matric No.'),
1190                   #_('Reg. No.'),
1191                   #_('Fullname'),
1192                   #_('Status'),
1193                   #_('Course of\nStudies'),
1194                   _('Department'),
1195                   _('Level'),
1196                   _(' CA  '),
1197                   _('Exam\nScore'),
1198                   _('Total '),
1199                   _('Grade'),
1200                   ],]
1201        sorted_tickets = sorted(coursetickets_filtered, # editable_tickets,
1202            key=lambda ticket: str(ticket.student.depcode)
1203                               + str(ticket.student.faccode)
1204                               + str(ticket.student.matric_number)
1205                               )
1206        no = 1
1207        tickets = []
1208        passed = 0
1209        failed = 0
1210        with_ca = False
1211        grade_stats = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0, 'F':0, }
1212        for ticket in sorted_tickets:
1213            if ticket.ca > 0:
1214                with_ca = True
1215            total = ticket.total_score
1216            if getattr(ticket, 'imported_ts', None):
1217                total = "**%s**" % ticket.imported_ts
1218            grade = ticket._getGradeWeightFromScore[0]
1219            if grade in grade_stats.keys():
1220                grade_stats[grade] += 1
1221            if grade in ('F', '-'):
1222                failed += 1
1223            else:
1224                passed += 1
1225            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1226            #deptitle = site['faculties'][ticket.student.faccode][
1227            #    ticket.student.depcode].longtitle
1228            row = [str(no),
1229                  ticket.student.student_id,
1230                  ticket.student.matric_number,
1231                  #ticket.student.reg_number,
1232                  #fullname,
1233                  #ticket.student.translated_state,
1234                  #ticket.student.certcode,
1235                  str(ticket.student.faccode) + ' / ' + str(ticket.student.depcode),
1236                  ticket.level,
1237                  ticket.ca,
1238                  ticket.score,
1239                  total,
1240                  grade,
1241                  ]
1242            tickets.append(row)
1243            no += 1
1244        total = passed + failed
1245        passed_perc = 0
1246        failed_perc = 0
1247        if total:
1248            passed_perc = round(100.0 * passed / total)
1249            failed_perc = round(100.0 * failed / total)
1250        dep = self.context.__parent__.__parent__.longtitle
1251        fac = self.context.__parent__.__parent__.__parent__.longtitle
1252        # remove CA column if not necessary
1253        if not with_ca:
1254            header = [[_(''),
1255                       _('Student Id'),
1256                       _('Matric No.'),
1257                       #_('Reg. No.'),
1258                       #_('Fullname'),
1259                       #_('Status'),
1260                       #_('Course of\nStudies'),
1261                       _('Department'),
1262                       _('Level'),
1263                       #_(' CA  '),
1264                       _('Exam\nScore'),
1265                       _('Total '),
1266                       _('Grade'),
1267                       ],]
1268            for ticket in tickets:
1269                del(ticket[5])
1270        return header + tickets, [
1271            dep, fac, total, passed, passed_perc, failed, failed_perc, grade_stats]
1272
1273    def render(self):
1274        session = grok.getSite()['configuration'].current_academic_session
1275        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1276                     if i['local_role'] == 'waeup.local.Lecturer']
1277        lecturers = sorted(lecturers)
1278        lecturers =  ', '.join(lecturers)
1279        students_utils = getUtility(IStudentsUtils)
1280        # only orientation is different
1281        return students_utils.renderPDFCourseticketsOverview(
1282            self, 'coursetickets',
1283            session, self.data(session), lecturers, '', 45, self.note)
1284
1285class DownloadPreviousSessionScoresView(DownloadScoresView):
1286    """View that exports scores.
1287    """
1288    grok.name('download_prev_scores')
1289
1290    def update(self):
1291        self.current_academic_session = grok.getSite()[
1292            'configuration'].current_academic_session
1293        if self.context.__parent__.__parent__.score_editing_disabled:
1294            self.flash(_('Score editing disabled.'), type="warning")
1295            self.redirect(self.url(self.context))
1296            return
1297        if not self.current_academic_session:
1298            self.flash(_('Current academic session not set.'), type="warning")
1299            self.redirect(self.url(self.context))
1300            return
1301        site = grok.getSite()
1302        exporter = getUtility(ICSVExporter, name='lecturer')
1303        self.csv = exporter.export_filtered(site, filepath=None,
1304                                 catalog='coursetickets',
1305                                 session=self.current_academic_session-1,
1306                                 level=None,
1307                                 code=self.context.code)
1308        return
1309
1310class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1311    """Captcha'd request password page for students.
1312    """
1313    grok.name('alumni_requestpw')
1314    grok.require('waeup.Anonymous')
1315    grok.template('alumni_requestpw')
1316    form_fields = grok.AutoFields(IStudentRequestPW).select(
1317        'lastname','number','email')
1318    label = _('Search student record and send password for first-time login')
1319
1320    def _redirect_no_student(self):
1321        self.flash(_('No student record found.'), type="warning")
1322        self.redirect(self.application_url() + '/applicants/trans2017/register')
1323        return
1324
1325#class CustomAddStudyLevelFormPage(AddStudyLevelFormPage):
1326#    """ This page is temporarily locked.
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.