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

Last change on this file since 14478 was 14453, checked in by Henrik Bettermann, 8 years ago

Students must not see scores, cas and grades of their registered courses.

  • Property svn:keywords set to Id
File size: 36.5 KB
Line 
1## $Id: browser.py 14453 2017-01-24 09:32:47Z 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
21from cStringIO import StringIO
22from zope.i18n import translate
23from zope.component import getUtility, queryUtility
24from zope.schema.interfaces import TooBig, TooSmall
25from zope.security import checkPermission
26from zope.catalog.interfaces import ICatalog
27from zope.formlib.textwidgets import BytesDisplayWidget
28from waeup.kofa.browser.layout import UtilityView
29from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
30from waeup.kofa.interfaces import (
31    IKofaUtils, academic_sessions_vocab, ICSVExporter, IKofaObject)
32from waeup.kofa.students.interfaces import (
33    IStudentsUtils, IStudent, IStudentRequestPW)
34from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
35from waeup.kofa.students.studylevel import getGradeWeightFromScore
36from waeup.kofa.students.browser import (
37    StartClearancePage,
38    StudentBasePDFFormPage,
39    CourseTicketAddFormPage,
40    StudyLevelDisplayFormPage,
41    StudyLevelManageFormPage,
42    StudyLevelEditFormPage,
43    ExportPDFTranscriptSlip,
44    ExportPDFAdmissionSlip,
45    BedTicketAddPage,
46    StudentFilesUploadPage,
47    PaymentsManageFormPage,
48    CourseTicketDisplayFormPage,
49    CourseTicketManageFormPage,
50    EditScoresPage,
51    ExportPDFScoresSlip,
52    StudyCourseTranscriptPage,
53    DownloadScoresView,
54    StudentRequestPasswordPage
55    )
56from kofacustom.nigeria.students.browser import (
57    NigeriaOnlinePaymentDisplayFormPage,
58    NigeriaOnlinePaymentAddFormPage,
59    NigeriaExportPDFPaymentSlip,
60    NigeriaExportPDFCourseRegistrationSlip,
61    NigeriaStudentPersonalDisplayFormPage,
62    NigeriaStudentPersonalEditFormPage,
63    NigeriaStudentPersonalManageFormPage,
64    NigeriaStudentClearanceDisplayFormPage,
65    NigeriaExportPDFClearanceSlip,
66    NigeriaStudentClearanceManageFormPage,
67    NigeriaStudentClearanceEditFormPage,
68    NigeriaAccommodationManageFormPage,
69    NigeriaStudentBaseDisplayFormPage,
70    NigeriaStudentBaseManageFormPage
71    )
72from waeup.aaue.students.interfaces import (
73    ICustomStudentOnlinePayment,
74    ICustomStudentStudyLevel,
75    ICustomStudent,
76    ICustomStudentPersonal,
77    ICustomStudentPersonalEdit,
78    ICustomUGStudentClearance,
79    ICustomUGStudentClearanceEdit,
80    ICustomPGStudentClearance,
81    ICustomCourseTicket,
82    ICustomStudentBase)
83from waeup.aaue.interswitch.browser import gateway_net_amt
84from waeup.aaue.interfaces import MessageFactory as _
85
86grok.context(IKofaObject)  # Make IKofaObject the default context
87
88class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
89    """ Page to display student base data
90    """
91    form_fields = grok.AutoFields(ICustomStudentBase).omit(
92        'password', 'suspended', 'suspended_comment', 'flash_notice')
93    form_fields[
94        'financial_clearance_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
95
96class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
97    """ View to manage student base data
98    """
99    form_fields = grok.AutoFields(ICustomStudentBase).omit(
100        'student_id', 'adm_code', 'suspended',
101        'financially_cleared_by', 'financial_clearance_date')
102
103class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
104    """ Page to display student personal data
105    """
106    form_fields = grok.AutoFields(ICustomStudentPersonal)
107    form_fields['perm_address'].custom_widget = BytesDisplayWidget
108    form_fields['father_address'].custom_widget = BytesDisplayWidget
109    form_fields['mother_address'].custom_widget = BytesDisplayWidget
110    form_fields['next_kin_address'].custom_widget = BytesDisplayWidget
111    form_fields[
112        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
113
114class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
115    """ Page to edit personal data
116    """
117    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit('personal_updated')
118
119class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
120    """ Page to edit personal data
121    """
122    form_fields = grok.AutoFields(ICustomStudentPersonal)
123    form_fields['personal_updated'].for_display = True
124    form_fields[
125        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
126
127class CustomStudentClearanceDisplayFormPage(NigeriaStudentClearanceDisplayFormPage):
128    """ Page to display student clearance data
129    """
130
131    @property
132    def form_fields(self):
133        if self.context.is_postgrad:
134            form_fields = grok.AutoFields(
135                ICustomPGStudentClearance).omit('clearance_locked')
136        else:
137            form_fields = grok.AutoFields(
138                ICustomUGStudentClearance).omit('clearance_locked')
139        if not getattr(self.context, 'officer_comment'):
140            form_fields = form_fields.omit('officer_comment')
141        else:
142            form_fields['officer_comment'].custom_widget = BytesDisplayWidget
143        form_fields = form_fields.omit('def_adm')
144        return form_fields
145
146class CustomStudentClearanceManageFormPage(NigeriaStudentClearanceManageFormPage):
147    """ Page to edit student clearance data
148    """
149
150    @property
151    def form_fields(self):
152        if self.context.is_postgrad:
153            form_fields = grok.AutoFields(
154                ICustomPGStudentClearance).omit('clr_code')
155        else:
156            form_fields = grok.AutoFields(
157                ICustomUGStudentClearance).omit('clr_code')
158        form_fields = form_fields.omit('def_adm')
159        return form_fields
160
161class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
162    """ View to edit student clearance data by student
163    """
164
165    @property
166    def form_fields(self):
167        if self.context.is_postgrad:
168            form_fields = grok.AutoFields(ICustomPGStudentClearance).omit(
169            'clearance_locked', 'nysc_location', 'clr_code', 'officer_comment',
170            'physical_clearance_date')
171        else:
172            form_fields = grok.AutoFields(ICustomUGStudentClearanceEdit).omit(
173            'clearance_locked', 'clr_code', 'officer_comment',
174            'physical_clearance_date', 'date_of_birth', 'nationality', 'lga')
175        form_fields = form_fields.omit('def_adm')
176        return form_fields
177
178class CustomStartClearancePage(StartClearancePage):
179    with_ac = False
180
181    @property
182    def all_required_fields_filled(self):
183        if not self.context.email:
184            return _("Email address is missing."), 'edit_base'
185        if not self.context.phone:
186            return _("Phone number is missing."), 'edit_base'
187        if not self.context.father_name:
188            return _("Personal data form is not properly filled."), 'edit_personal'
189        return
190
191class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
192    """ Page to view an online payment ticket
193    """
194    grok.context(ICustomStudentOnlinePayment)
195    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
196        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
197    form_fields[
198        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
199    form_fields[
200        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
201
202class CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
203    """ Page to add an online payment ticket
204    """
205    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
206        'p_category')
207
208    alumni_payment_cats =  {
209        'transcript_local': 'Transcript Fee Local',
210        'transcript_inter': 'Transcript Fee International',
211        }
212
213    @property
214    def selectable_categories(self):
215        if 'alumni' in self.application_url():
216            return self.alumni_payment_cats.items()
217        categories = getUtility(IKofaUtils).SELECTABLE_PAYMENT_CATEGORIES
218        return sorted(categories.items())
219
220class CustomPaymentsManageFormPage(PaymentsManageFormPage):
221    """ Page to manage the student payments.
222
223    This manage form page is for both students and students officers.
224    """
225    @property
226    def manage_payments_allowed(self):
227        return checkPermission('waeup.manageStudent', self.context)
228
229class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
230    """Deliver a PDF slip of the context.
231    """
232    grok.context(ICustomStudentOnlinePayment)
233    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
234        'provider_amt', 'gateway_amt', 'thirdparty_amt', 'p_item')
235    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
236    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
237
238    @property
239    def note(self):
240        p_session = self.context.p_session
241        try:
242            academic_session = grok.getSite()['configuration'][str(p_session)]
243        except KeyError:
244            academic_session = None
245        text =  '\n\n The Amount Authorized is inclusive of: '
246        if self.context.p_category in ('schoolfee_incl', 'schoolfee_1') \
247            and academic_session:
248            #welfare_fee = gateway_net_amt(academic_session.welfare_fee)
249            #union_fee = gateway_net_amt(academic_session.union_fee)
250            if self.context.student.entry_session == 2016 \
251                and self.context.student.entry_mode == 'ug_ft' \
252                and self.context.p_session == 2016:
253                # Add student id card fee to first school fee payment.
254
255                ## Attention: The payment slip does not contain any information
256                ## whether the fee was added or not.
257                ## We can only draw conclusions from from the student's entry
258                ## session whether the fee had been included.
259                #id_card_fee = gateway_net_amt(academic_session.id_card_fee)
260                #text += ('School Fee, '
261                #         '%s Naira Student ID Card Fee, '
262                #         '%s Naira Student Union Dues, '
263                #         '%s Naira Student Welfare Assurance Fee and '
264                #         % (id_card_fee, union_fee, welfare_fee))
265
266                text += ('School Fee, '
267                         'Student ID Card Fee, '
268                         'Student Union Dues, '
269                         'Student Welfare Assurance Fee and ')
270            else:
271
272                #text += ('School Fee, '
273                #         '%s Naira Student Union Dues, '
274                #         '%s Naira Student Welfare Assurance Fee and '
275                #         % (union_fee, welfare_fee))
276
277                text += ('School Fee, '
278                         'Student Union Dues, '
279                         'Student Welfare Assurance Fee and ')
280        elif self.context.p_category in (
281            'clearance_incl', 'clearance_medical_incl') and academic_session:
282
283            #matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
284            #lapel_fee = gateway_net_amt(academic_session.lapel_fee)
285            #text += ('Acceptance Fee, '
286            #         '%s Naira Matriculation Gown Fee, '
287            #         '%s Naira Lapel/File Fee and '
288            #         % (matric_gown_fee, lapel_fee))
289
290            text += ('Acceptance Fee, '
291                     'Matriculation Gown Fee, '
292                     'Lapel/File Fee and ')
293
294        #return text + '250.0 Naira Transaction Charge.'
295
296        return text + 'Transaction Charge.'
297
298class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
299    """ Page to display student study levels
300    """
301    grok.context(ICustomStudentStudyLevel)
302    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
303        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
304    form_fields[
305        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
306
307    @property
308    def show_results(self):
309        isStudent = getattr(
310            self.request.principal, 'user_type', None) == 'student'
311        # Temporarily disabled on 1/12/2016
312        if isStudent:
313            return False
314        #if isStudent and self.context.student.state != RETURNING \
315        #    and self.context.student.current_level == self.context.level:
316        #    return False
317        return True
318
319class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
320    """ Page to edit the student study level data
321    """
322    grok.context(ICustomStudentStudyLevel)
323
324class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
325    """ Page to edit the student study level data by students
326    """
327    grok.context(ICustomStudentStudyLevel)
328
329class CustomExportPDFCourseRegistrationSlip(
330    NigeriaExportPDFCourseRegistrationSlip):
331    """Deliver a PDF slip of the context.
332    """
333    grok.context(ICustomStudentStudyLevel)
334    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
335        'level_session', 'level_verdict',
336        'validated_by', 'validation_date', 'gpa', 'level',
337        'imported_gpa', 'imported_cgpa')
338
339    omit_fields = ('password', 'suspended', 'suspended_comment',
340        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
341        'department', 'current_mode', 'current_level', 'flash_notice')
342
343    @property
344    def show_results(self):
345        isStudent = getattr(
346            self.request.principal, 'user_type', None) == 'student'
347        # Temporarily disabled on 1/12/2016
348        if isStudent:
349            return False
350        #if isStudent and self.context.student.state != RETURNING \
351        #    and self.context.student.current_level == self.context.level:
352        #    return False
353        return True
354
355    def update(self):
356        if self.context.student.state != REGISTERED \
357            and self.context.student.current_level == self.context.level:
358            self.flash(_('Forbidden'), type="warning")
359            self.redirect(self.url(self.context))
360            return
361
362    @property
363    def label(self):
364        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
365        lang = self.request.cookies.get('kofa.language', portal_language)
366        level_title = translate(self.context.level_title, 'waeup.kofa',
367            target_language=lang)
368        line0 = ''
369        if self.context.student.is_postgrad:
370            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
371        elif self.context.student.current_mode.endswith('_pt'):
372            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
373        line1 = translate(_('Course Registration Slip'),
374            target_language=portal_language) \
375            + ' %s' % level_title
376        line2 = translate(_('Session'),
377            target_language=portal_language) \
378            + ' %s' % self.context.getSessionString
379        return '%s%s\n%s' % (line0, line1, line2)
380
381    @property
382    def title(self):
383        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
384        return translate(_('Units Registered'), target_language=portal_language)
385
386    def _signatures(self):
387        if self.context.student.current_mode.endswith('_pt') \
388            or self.context.student.current_mode == 'found':
389            return (
390                [('I have selected the course on the advise of my Head of '
391                 'Department. <br>', _('Student\'s Signature'), '<br>')],
392                [('This student has satisfied the department\'s requirements. '
393                 'I recommend to approve the course registration. <br>',
394                 _('Head of Department\'s Signature'), '<br>')],
395                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
396                [('', _('Director\'s Signature'))]
397                )
398        if self.context.student.current_mode in (
399            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
400            return ([_('Academic Adviser\'s Signature'),
401                _('Faculty Officer\'s Signature'),
402                _('Student\'s Signature')],)
403
404        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
405            return (
406                [('I declare that all items of information supplied above are correct:' ,
407                    _('Student\'s Signature'), '<br>')],
408                [('We approved the above registration:',
409                    _('Major Supervisor (Name / Signature)'), '')],
410                [('', _('Co-Supervisor (Name / Signature)'), '')],
411                [('', _('Head of Department'), '<br>')],
412                [('The student has satisfied the conditions for renewal of '
413                  'registration for graduate school programme in this university:',
414                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
415                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
416                )
417        return None
418
419
420    def render(self):
421        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
422        Sem = translate(_('Sem.'), target_language=portal_language)
423        Code = translate(_('Code'), target_language=portal_language)
424        Title = translate(_('Title'), target_language=portal_language)
425        Cred = translate(_('Cred.'), target_language=portal_language)
426        if self.show_results:
427            Score = translate(_('Score'), target_language=portal_language)
428            #CA = translate(_('CA'), target_language=portal_language)
429            Grade = translate(_('Grade'), target_language=portal_language)
430        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
431            target_language=portal_language)
432        studentview = StudentBasePDFFormPage(self.context.student,
433            self.request, self.omit_fields)
434        students_utils = getUtility(IStudentsUtils)
435
436        tabledata = []
437        tableheader = []
438        contenttitle = []
439        for i in range(1,7):
440            tabledata.append(sorted(
441                [value for value in self.context.values() if value.semester == i],
442                key=lambda value: str(value.semester) + value.code))
443            if self.show_results:
444                tableheader.append([(Code,'code', 2.0),
445                                   (Title,'title', 7),
446                                   (Cred, 'credits', 1.5),
447                                   (Score, 'score', 1.4),
448                                   #(CA, 'ca', 1.4),
449                                   (Grade, 'grade', 1.4),
450                                   (Signature, 'dummy', 3),
451                                   ])
452            else:
453                tableheader.append([(Code,'code', 2.0),
454                                   (Title,'title', 7),
455                                   (Cred, 'credits', 1.5),
456                                   (Signature, 'dummy', 3),
457                                   ])
458        if len(self.label.split('\n')) == 3:
459            topMargin = 1.9
460        elif len(self.label.split('\n')) == 2:
461            topMargin = 1.7
462        else:
463            topMargin = 1.5
464        return students_utils.renderPDF(
465            self, 'course_registration_slip.pdf',
466            self.context.student, studentview,
467            tableheader=tableheader,
468            tabledata=tabledata,
469            signatures=self._signatures(),
470            topMargin=topMargin,
471            omit_fields=self.omit_fields
472            )
473
474class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
475    """ Page to display the student's transcript.
476    """
477    grok.require('waeup.viewStudent')
478
479class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
480    """Deliver a PDF slip of the context.
481    """
482#    grok.require('waeup.viewStudent')
483
484    note = _("""
485<br /><br /><br /><br />
486<font size='10'>
487<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
488</font>
489""")
490
491    def _sigsInFooter(self):
492        return []
493
494    def _signatures(self):
495        return ([(
496            'Mrs. Uniamikogbo, S.O., mnim, manupa <br /> Prin. Asst Registrar  <br /> '
497            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
498
499    def render(self):
500        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
501        Term = translate(_('Sem.'), target_language=portal_language)
502        Code = translate(_('Code'), target_language=portal_language)
503        Title = translate(_('Title'), target_language=portal_language)
504        Cred = translate(_('Credits'), target_language=portal_language)
505        Score = translate(_('Score'), target_language=portal_language)
506        Grade = translate(_('Grade'), target_language=portal_language)
507        studentview = StudentBasePDFFormPage(self.context.student,
508            self.request, self.omit_fields)
509        students_utils = getUtility(IStudentsUtils)
510
511        tableheader = [(Code,'code', 2.5),
512                         (Title,'title', 7),
513                         (Term, 'semester', 1.5),
514                         (Cred, 'credits', 1.5),
515                         (Score, 'total_score', 1.5),
516                         (Grade, 'grade', 1.5),
517                         ]
518
519        return students_utils.renderPDFTranscript(
520            self, 'transcript.pdf',
521            self.context.student, studentview,
522            omit_fields=self.omit_fields,
523            tableheader=tableheader,
524            signatures=self._signatures(),
525            sigs_in_footer=self._sigsInFooter(),
526            note = self.note,
527            no_passport=True
528            )
529
530class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
531    """Deliver a PDF Admission slip.
532    """
533
534    @property
535    def label(self):
536        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
537        return translate(_('e-Admission Slip \n'),
538            target_language=portal_language) \
539            + ' %s' % self.context.display_fullname
540
541class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
542    """Deliver a PDF slip of the context.
543    """
544
545    @property
546    def label(self):
547        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
548        return translate(_('Verification/Clearance Slip\n'),
549            target_language=portal_language) \
550            + ' %s' % self.context.display_fullname
551
552    @property
553    def form_fields(self):
554        if self.context.is_postgrad:
555            form_fields = grok.AutoFields(
556                ICustomPGStudentClearance).omit('clearance_locked')
557        else:
558            form_fields = grok.AutoFields(
559                ICustomUGStudentClearance).omit('clearance_locked')
560        if not getattr(self.context, 'officer_comment'):
561            form_fields = form_fields.omit('officer_comment')
562        form_fields = form_fields.omit('def_adm')
563        return form_fields
564
565class StudentGetMatricNumberPage(UtilityView, grok.View):
566    """ Construct and set the matriculation number.
567    """
568    grok.context(IStudent)
569    grok.name('get_matric_number')
570    grok.require('waeup.viewStudent')
571
572    def update(self):
573        students_utils = getUtility(IStudentsUtils)
574        msg, mnumber = students_utils.setMatricNumber(self.context)
575        if msg:
576            self.flash(msg, type="danger")
577        else:
578            self.flash(_('Matriculation number %s assigned.' % mnumber))
579            self.context.writeLogMessage(self, '%s assigned' % mnumber)
580        self.redirect(self.url(self.context))
581        return
582
583    def render(self):
584        return
585
586class ExportPDFMatricNumberSlip(UtilityView, grok.View):
587    """Deliver a PDF notification slip.
588    """
589    grok.context(ICustomStudent)
590    grok.name('matric_number_slip.pdf')
591    grok.require('waeup.viewStudent')
592    prefix = 'form'
593
594    form_fields = grok.AutoFields(ICustomStudent).select(
595        'student_id', 'matric_number')
596    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
597
598    @property
599    def title(self):
600        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
601        return translate(_('Matriculation Number'), 'waeup.kofa',
602            target_language=portal_language)
603
604    @property
605    def label(self):
606        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
607        return translate(_('Matriculation Number Slip\n'),
608            target_language=portal_language) \
609            + ' %s' % self.context.display_fullname
610
611    def render(self):
612        if self.context.state not in (PAID,) or not self.context.is_fresh \
613            or not self.context.matric_number:
614            self.flash('Not allowed.', type="danger")
615            self.redirect(self.url(self.context))
616            return
617        students_utils = getUtility(IStudentsUtils)
618        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
619                     'payments have been received and your matriculation ' +
620                     'number generated with details as follows.')
621        return students_utils.renderPDFAdmissionLetter(self,
622            self.context.student, omit_fields=self.omit_fields,
623            pre_text=pre_text, post_text='')
624
625class ExportPersonalDataSlip(UtilityView, grok.View):
626    """Deliver a PDF notification slip.
627    """
628    grok.context(ICustomStudent)
629    grok.name('personal_data_slip.pdf')
630    grok.require('waeup.viewStudent')
631    prefix = 'form'
632    note = None
633
634    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
635    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
636                   'certificate', 'flash_notice')
637
638    @property
639    def title(self):
640        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
641        return translate(_('Personal Data'), 'waeup.kofa',
642            target_language=portal_language)
643
644    @property
645    def label(self):
646        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
647        return translate(_('Personal Data Slip\n'),
648            target_language=portal_language) \
649            + ' %s' % self.context.display_fullname
650
651    def render(self):
652        studentview = StudentBasePDFFormPage(self.context.student,
653            self.request, self.omit_fields)
654        students_utils = getUtility(IStudentsUtils)
655        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
656            self.context.student, studentview, note=self.note,
657            omit_fields=self.omit_fields)
658
659class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
660    """ Page to manage bed tickets.
661    This manage form page is for both students and students officers.
662    """
663    with_hostel_selection = True
664
665class CustomBedTicketAddPage(BedTicketAddPage):
666    with_ac = False
667
668class CustomStudentFilesUploadPage(StudentFilesUploadPage):
669    """ View to upload files by student. Inherit from same class in
670    base package, not from kofacustom.nigeria which
671    requires that no application slip exists.
672    """
673
674class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
675    """ Page to display course tickets
676    """
677
678    @property
679    def show_results(self):
680        isStudent = getattr(
681            self.request.principal, 'user_type', None) == 'student'
682        if isStudent:
683            return False
684        return True
685
686    @property
687    def form_fields(self):
688        if self.show_results:
689            return grok.AutoFields(ICustomCourseTicket)
690        else:
691            return grok.AutoFields(ICustomCourseTicket).omit('score').omit('ca')
692
693class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
694    """ Page to manage course tickets
695    """
696    form_fields = grok.AutoFields(ICustomCourseTicket)
697    form_fields['title'].for_display = True
698    form_fields['fcode'].for_display = True
699    form_fields['dcode'].for_display = True
700    form_fields['semester'].for_display = True
701    form_fields['passmark'].for_display = True
702    form_fields['credits'].for_display = True
703    form_fields['mandatory'].for_display = False
704    form_fields['automatic'].for_display = True
705    form_fields['carry_over'].for_display = True
706
707class CustomEditScoresPage(EditScoresPage):
708    """Page that filters and lists students.
709    """
710    grok.template('editscorespage')
711
712
713    def _extract_uploadfile(self, uploadfile):
714        """Get a mapping of student-ids to scores.
715
716        The mapping is constructed by reading contents from `uploadfile`.
717
718        We expect uploadfile to be a regular CSV file with columns
719        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
720        """
721        result = dict()
722        data = StringIO(uploadfile.read())  # ensure we have something seekable
723        reader = csv.DictReader(data)
724        for row in reader:
725            if not ('student_id' in row and 'score' in row and 'ca' in row):
726                continue
727            result[row['student_id']] = (row['score'], row['ca'])
728        return result
729
730    def _update_scores(self, form):
731        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
732        error = ''
733        if 'UPDATE_FILE' in form:
734            if form['uploadfile']:
735                try:
736                    formvals = self._extract_uploadfile(form['uploadfile'])
737                except:
738                    self.flash(
739                        _('Uploaded file contains illegal data. Ignored'),
740                        type="danger")
741                    return False
742            else:
743                self.flash(
744                    _('No file provided.'), type="danger")
745                return False
746        else:
747            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
748        for ticket in self.editable_tickets:
749            ticket_error = False
750            score = ticket.score
751            ca = ticket.ca
752            sid = ticket.student.student_id
753            if formvals[sid][0] == '':
754                score = None
755            if formvals[sid][1] == '':
756                ca = None
757            try:
758                if formvals[sid][0]:
759                    score = int(formvals[sid][0])
760                if formvals[sid][1]:
761                    ca = int(formvals[sid][1])
762            except ValueError:
763                error += '%s, ' % ticket.student.display_fullname
764                ticket_error = True
765            if not ticket_error and ticket.score != score:
766                try:
767                    ticket.score = score
768                except TooBig:
769                    error += '%s, ' % ticket.student.display_fullname
770                    ticket_error = True
771                    pass
772                ticket.student.__parent__.logger.info(
773                    '%s - %s %s/%s score updated (%s)' %
774                    (ob_class, ticket.student.student_id,
775                     ticket.level, ticket.code, score))
776            if not ticket_error and ticket.ca != ca:
777                try:
778                    ticket.ca = ca
779                except TooBig:
780                    error += '%s, ' % ticket.student.display_fullname
781                    pass
782                ticket.student.__parent__.logger.info(
783                    '%s - %s %s/%s ca updated (%s)' %
784                    (ob_class, ticket.student.student_id,
785                     ticket.level, ticket.code, ca))
786        if error:
787            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
788              % error.strip(', ')), type="danger")
789        return True
790
791class EditPreviousSessionScoresPage(CustomEditScoresPage):
792
793    grok.name('edit_prev_scores')
794
795    def update(self,  *args, **kw):
796        form = self.request.form
797        self.current_academic_session = grok.getSite()[
798            'configuration'].current_academic_session
799        if self.context.__parent__.__parent__.score_editing_disabled:
800            self.flash(_('Score editing disabled.'), type="warning")
801            self.redirect(self.url(self.context))
802            return
803        if not self.current_academic_session:
804            self.flash(_('Current academic session not set.'), type="warning")
805            self.redirect(self.url(self.context))
806            return
807        previous_session = self.current_academic_session - 1
808        self.session_title = academic_sessions_vocab.getTerm(
809            previous_session).title
810        self.tickets = self._searchCatalog(previous_session)
811        if not self.tickets:
812            self.flash(_('No student found.'), type="warning")
813            self.redirect(self.url(self.context))
814            return
815        self.editable_tickets = [
816            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
817        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
818            return
819        if not self.editable_tickets:
820            return
821        success = self._update_scores(form)
822        if success:
823            self.flash(_('You successfully updated course results.'))
824        return
825
826class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
827    """Deliver a PDF slip of course tickets for a lecturer.
828    """
829
830    def data(self, session):
831        cat = queryUtility(ICatalog, name='coursetickets_catalog')
832        coursetickets = cat.searchResults(
833            session=(session, session),
834            code=(self.context.code, self.context.code)
835            )
836        # In AAUE only editable tickets can be printed
837        editable_tickets = [
838            ticket for ticket in coursetickets if ticket.editable_by_lecturer]
839        header = [[_(''),
840                   _('Matric No.'),
841                   _('Reg. No.'),
842                   _('Fullname'),
843                   _('Status'),
844                   _('Course of\nStudies'),
845                   _('Level'),
846                   _('Exam\nScore'),
847                   _(' CA  '),
848                   _('Total '),
849                   _('Grade'),
850                   ],]
851        sorted_tickets = sorted(editable_tickets,
852            key=lambda ticket: ticket.student.certcode +
853                ticket.student.display_fullname + str(ticket.level))
854        no = 1
855        tickets = []
856        passed = 0
857        failed = 0
858        # In AAUE only editable tickets can be printed
859        for ticket in sorted_tickets:
860            if None in (ticket.score, ticket.ca):
861                total = 'n/a'
862                grade = 'n/a'
863            else:
864                total = ticket.score + ticket.ca
865                grade = getGradeWeightFromScore(total, ticket.student)[0]
866                if grade == 'F':
867                    failed += 1
868                else:
869                    passed += 1
870            fullname = textwrap.fill(ticket.student.display_fullname, 30)
871            row = [no,
872                  ticket.student.matric_number,
873                  ticket.student.reg_number,
874                  fullname,
875                  ticket.student.translated_state,
876                  ticket.student.certcode,
877                  ticket.level,
878                  ticket.ca,
879                  ticket.score,
880                  total,
881                  grade,
882                  ]
883            tickets.append(row)
884            no += 1
885        total = passed + failed
886        passed_perc = 0
887        failed_perc = 0
888        if total:
889            passed_perc = 100 * passed / total
890            failed_perc = 100 * failed / total
891        return header + tickets, [
892            total, passed, passed_perc, failed, failed_perc]
893
894class DownloadPreviousSessionScoresView(DownloadScoresView):
895    """View that exports scores.
896    """
897    grok.name('download_prev_scores')
898
899    def update(self):
900        self.current_academic_session = grok.getSite()[
901            'configuration'].current_academic_session
902        if self.context.__parent__.__parent__.score_editing_disabled:
903            self.flash(_('Score editing disabled.'), type="warning")
904            self.redirect(self.url(self.context))
905            return
906        if not self.current_academic_session:
907            self.flash(_('Current academic session not set.'), type="warning")
908            self.redirect(self.url(self.context))
909            return
910        site = grok.getSite()
911        exporter = getUtility(ICSVExporter, name='lecturer')
912        self.csv = exporter.export_filtered(site, filepath=None,
913                                 catalog='coursetickets',
914                                 session=self.current_academic_session-1,
915                                 level=None,
916                                 code=self.context.code)
917        return
918
919class AlumniRequestPasswordPage(StudentRequestPasswordPage):
920    """Captcha'd request password page for students.
921    """
922    grok.name('alumni_requestpw')
923    grok.require('waeup.Anonymous')
924    grok.template('alumni_requestpw')
925    form_fields = grok.AutoFields(IStudentRequestPW).select(
926        'lastname','number','email')
927    label = _('Search student record and send password for first-time login')
928
929    def _redirect_no_student(self):
930        self.flash(_('No student record found.'), type="warning")
931        self.redirect(self.application_url() + '/applicants/trans2017/register')
932        return
Note: See TracBrowser for help on using the repository browser.