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

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

Remove CA column if not needed.

  • Property svn:keywords set to Id
File size: 46.9 KB
Line 
1## $Id: browser.py 15229 2018-11-11 21:22:33Z 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        except KeyError:
1059            coursetickets_filtered = coursetickets
1060        # In AAUE only editable tickets can be printed
1061        editable_tickets = [
1062            ticket for ticket in coursetickets_filtered
1063            if ticket.editable_by_lecturer]
1064        header = [[_(''),
1065                   _('Student Id'),
1066                   _('Matric No.'),
1067                   #_('Reg. No.'),
1068                   #_('Fullname'),
1069                   #_('Status'),
1070                   #_('Course of\nStudies'),
1071                   _('Department'),
1072                   _('Level'),
1073                   _(' CA  '),
1074                   _('Exam\nScore'),
1075                   _('Total '),
1076                   _('Grade'),
1077                   ],]
1078        sorted_tickets = sorted(editable_tickets,
1079            key=lambda ticket: ticket.student.depcode
1080                               + ticket.student.faccode
1081                               + ticket.student.matric_number)
1082        no = 1
1083        tickets = []
1084        passed = 0
1085        failed = 0
1086        with_ca = False
1087        # In AAUE only editable tickets can be printed
1088        for ticket in sorted_tickets:
1089            if ticket.ca > 0:
1090                with_ca = True
1091            total = ticket.total_score
1092            grade = ticket._getGradeWeightFromScore[0]
1093            if grade in ('F', '-'):
1094                failed += 1
1095            else:
1096                passed += 1
1097            fullname = textwrap.fill(ticket.student.display_fullname, 30)
1098            #deptitle = site['faculties'][ticket.student.faccode][
1099            #    ticket.student.depcode].longtitle
1100            row = [no,
1101                  ticket.student.student_id,
1102                  ticket.student.matric_number,
1103                  #ticket.student.reg_number,
1104                  #fullname,
1105                  #ticket.student.translated_state,
1106                  #ticket.student.certcode,
1107                  ticket.student.faccode + ' / ' + ticket.student.depcode,
1108                  ticket.level,
1109                  ticket.ca,
1110                  ticket.score,
1111                  total,
1112                  grade,
1113                  ]
1114            tickets.append(row)
1115            no += 1
1116        total = passed + failed
1117        passed_perc = 0
1118        failed_perc = 0
1119        if total:
1120            passed_perc = 100 * passed / total
1121            failed_perc = 100 * failed / total
1122        dep = self.context.__parent__.__parent__.longtitle
1123        fac = self.context.__parent__.__parent__.__parent__.longtitle
1124        # remove CA column if not necessary
1125        if not with_ca:
1126            header = [[_(''),
1127                       _('Student Id'),
1128                       _('Matric No.'),
1129                       #_('Reg. No.'),
1130                       #_('Fullname'),
1131                       #_('Status'),
1132                       #_('Course of\nStudies'),
1133                       _('Department'),
1134                       _('Level'),
1135                       #_(' CA  '),
1136                       _('Exam\nScore'),
1137                       _('Total '),
1138                       _('Grade'),
1139                       ],]
1140            for ticket in tickets:
1141                del(ticket[5])
1142        return header + tickets, [
1143            dep, fac, total, passed, passed_perc, failed, failed_perc]
1144
1145    def render(self):
1146        session = grok.getSite()['configuration'].current_academic_session
1147        lecturers = [i['user_title'] for i in self.getUsersWithLocalRoles()
1148                     if i['local_role'] == 'waeup.local.Lecturer']
1149        lecturers =  ', '.join(lecturers)
1150        students_utils = getUtility(IStudentsUtils)
1151        # only orientation is different
1152        return students_utils.renderPDFCourseticketsOverview(
1153            self, session, self.data(session), lecturers, '', 45)
1154
1155class DownloadPreviousSessionScoresView(DownloadScoresView):
1156    """View that exports scores.
1157    """
1158    grok.name('download_prev_scores')
1159
1160    def update(self):
1161        self.current_academic_session = grok.getSite()[
1162            'configuration'].current_academic_session
1163        if self.context.__parent__.__parent__.score_editing_disabled:
1164            self.flash(_('Score editing disabled.'), type="warning")
1165            self.redirect(self.url(self.context))
1166            return
1167        if not self.current_academic_session:
1168            self.flash(_('Current academic session not set.'), type="warning")
1169            self.redirect(self.url(self.context))
1170            return
1171        site = grok.getSite()
1172        exporter = getUtility(ICSVExporter, name='lecturer')
1173        self.csv = exporter.export_filtered(site, filepath=None,
1174                                 catalog='coursetickets',
1175                                 session=self.current_academic_session-1,
1176                                 level=None,
1177                                 code=self.context.code)
1178        return
1179
1180class AlumniRequestPasswordPage(StudentRequestPasswordPage):
1181    """Captcha'd request password page for students.
1182    """
1183    grok.name('alumni_requestpw')
1184    grok.require('waeup.Anonymous')
1185    grok.template('alumni_requestpw')
1186    form_fields = grok.AutoFields(IStudentRequestPW).select(
1187        'lastname','number','email')
1188    label = _('Search student record and send password for first-time login')
1189
1190    def _redirect_no_student(self):
1191        self.flash(_('No student record found.'), type="warning")
1192        self.redirect(self.application_url() + '/applicants/trans2017/register')
1193        return
Note: See TracBrowser for help on using the repository browser.