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

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

Use the asteriks to mark imported total scores. Reportlab doesn't like Paragraph instances in columns.

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