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

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

getGradeWeightFromScore is now the same in the base package.

  • Property svn:keywords set to Id
File size: 35.6 KB
Line 
1## $Id: browser.py 14354 2016-12-16 17:36:07Z 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                # Attention: The payment slip does not contain any information
255                # whether the fee was added or not.
256                # We can only draw conclusions from from the student's entry
257                # session whether the fee had been included.
258                id_card_fee = gateway_net_amt(academic_session.id_card_fee)
259                text += ('School Fee, '
260                         '%s Naira Student ID Card Fee, '
261                         '%s Naira Student Union Dues, '
262                         '%s Naira Student Welfare Assurance Fee and '
263                         % (id_card_fee, union_fee, welfare_fee))
264            else:
265                text += ('School Fee, '
266                         '%s Naira Student Union Dues, '
267                         '%s Naira Student Welfare Assurance Fee and '
268                         % (union_fee, welfare_fee))
269        elif self.context.p_category in (
270            'clearance_incl', 'clearance_medical_incl') and academic_session:
271            matric_gown_fee = gateway_net_amt(academic_session.matric_gown_fee)
272            lapel_fee = gateway_net_amt(academic_session.lapel_fee)
273            text += ('Acceptance Fee, '
274                     '%s Naira Matriculation Gown Fee, '
275                     '%s Naira Lapel/File Fee and '
276                     % (matric_gown_fee, lapel_fee))
277        return text + '250.0 Naira Transaction Charge.'
278
279class CustomStudyLevelDisplayFormPage(StudyLevelDisplayFormPage):
280    """ Page to display student study levels
281    """
282    grok.context(ICustomStudentStudyLevel)
283    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
284        'total_credits', 'gpa', 'level', 'imported_gpa', 'imported_cgpa')
285    form_fields[
286        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
287
288    @property
289    def show_results(self):
290        isStudent = getattr(
291            self.request.principal, 'user_type', None) == 'student'
292        # Temporarily disabled on 1/12/2016
293        if isStudent:
294            return False
295        #if isStudent and self.context.student.state != RETURNING \
296        #    and self.context.student.current_level == self.context.level:
297        #    return False
298        return True
299
300class CustomStudyLevelManageFormPage(StudyLevelManageFormPage):
301    """ Page to edit the student study level data
302    """
303    grok.context(ICustomStudentStudyLevel)
304
305class CustomStudyLevelEditFormPage(StudyLevelEditFormPage):
306    """ Page to edit the student study level data by students
307    """
308    grok.context(ICustomStudentStudyLevel)
309
310class CustomExportPDFCourseRegistrationSlip(
311    NigeriaExportPDFCourseRegistrationSlip):
312    """Deliver a PDF slip of the context.
313    """
314    grok.context(ICustomStudentStudyLevel)
315    form_fields = grok.AutoFields(ICustomStudentStudyLevel).omit(
316        'level_session', 'level_verdict',
317        'validated_by', 'validation_date', 'gpa', 'level',
318        'imported_gpa', 'imported_cgpa')
319
320    omit_fields = ('password', 'suspended', 'suspended_comment',
321        'phone', 'adm_code', 'sex', 'email', 'date_of_birth',
322        'department', 'current_mode', 'current_level', 'flash_notice')
323
324    @property
325    def show_results(self):
326        isStudent = getattr(
327            self.request.principal, 'user_type', None) == 'student'
328        # Temporarily disabled on 1/12/2016
329        if isStudent:
330            return False
331        #if isStudent and self.context.student.state != RETURNING \
332        #    and self.context.student.current_level == self.context.level:
333        #    return False
334        return True
335
336    def update(self):
337        if self.context.student.state != REGISTERED \
338            and self.context.student.current_level == self.context.level:
339            self.flash(_('Forbidden'), type="warning")
340            self.redirect(self.url(self.context))
341            return
342
343    @property
344    def label(self):
345        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
346        lang = self.request.cookies.get('kofa.language', portal_language)
347        level_title = translate(self.context.level_title, 'waeup.kofa',
348            target_language=lang)
349        line0 = ''
350        if self.context.student.is_postgrad:
351            line0 = 'SCHOOL OF POSTGRADUATE STUDIES\n'
352        elif self.context.student.current_mode.endswith('_pt'):
353            line0 = 'DIRECTORATE OF PART-TIME DEGREE PROGRAMMES\n'
354        line1 = translate(_('Course Registration Slip'),
355            target_language=portal_language) \
356            + ' %s' % level_title
357        line2 = translate(_('Session'),
358            target_language=portal_language) \
359            + ' %s' % self.context.getSessionString
360        return '%s%s\n%s' % (line0, line1, line2)
361
362    @property
363    def title(self):
364        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
365        return translate(_('Units Registered'), target_language=portal_language)
366
367    def _signatures(self):
368        if self.context.student.current_mode.endswith('_pt') \
369            or self.context.student.current_mode == 'found':
370            return (
371                [('I have selected the course on the advise of my Head of '
372                 'Department. <br>', _('Student\'s Signature'), '<br>')],
373                [('This student has satisfied the department\'s requirements. '
374                 'I recommend to approve the course registration. <br>',
375                 _('Head of Department\'s Signature'), '<br>')],
376                [('' , _('Deputy Registrar\'s Signature'), '<br>')],
377                [('', _('Director\'s Signature'))]
378                )
379        if self.context.student.current_mode in (
380            'de_ft', 'ug_ft', 'dp_ft', 'transfer'):
381            return ([_('Academic Adviser\'s Signature'),
382                _('Faculty Officer\'s Signature'),
383                _('Student\'s Signature')],)
384
385        if self.context.student.current_mode in ('special_pg_ft', 'special_pg_pt'):
386            return (
387                [('I declare that all items of information supplied above are correct:' ,
388                    _('Student\'s Signature'), '<br>')],
389                [('We approved the above registration:',
390                    _('Major Supervisor (Name / Signature)'), '')],
391                [('', _('Co-Supervisor (Name / Signature)'), '')],
392                [('', _('Head of Department'), '<br>')],
393                [('The student has satisfied the conditions for renewal of '
394                  'registration for graduate school programme in this university:',
395                  _('Secretary <br /> (School of Postgraduate Studies)'), '')],
396                [('', _('Dean <br /> (School of Postgraduate Studies)'), '')],
397                )
398        return None
399
400
401    def render(self):
402        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
403        Sem = translate(_('Sem.'), target_language=portal_language)
404        Code = translate(_('Code'), target_language=portal_language)
405        Title = translate(_('Title'), target_language=portal_language)
406        Cred = translate(_('Cred.'), target_language=portal_language)
407        if self.show_results:
408            Score = translate(_('Score'), target_language=portal_language)
409            #CA = translate(_('CA'), target_language=portal_language)
410            Grade = translate(_('Grade'), target_language=portal_language)
411        Signature = translate(_('Lecturer\'s Signature'), 'waeup.aaue',
412            target_language=portal_language)
413        studentview = StudentBasePDFFormPage(self.context.student,
414            self.request, self.omit_fields)
415        students_utils = getUtility(IStudentsUtils)
416
417        tabledata = []
418        tableheader = []
419        contenttitle = []
420        for i in range(1,7):
421            tabledata.append(sorted(
422                [value for value in self.context.values() if value.semester == i],
423                key=lambda value: str(value.semester) + value.code))
424            if self.show_results:
425                tableheader.append([(Code,'code', 2.0),
426                                   (Title,'title', 7),
427                                   (Cred, 'credits', 1.5),
428                                   (Score, 'score', 1.4),
429                                   #(CA, 'ca', 1.4),
430                                   (Grade, 'grade', 1.4),
431                                   (Signature, 'dummy', 3),
432                                   ])
433            else:
434                tableheader.append([(Code,'code', 2.0),
435                                   (Title,'title', 7),
436                                   (Cred, 'credits', 1.5),
437                                   (Signature, 'dummy', 3),
438                                   ])
439        if len(self.label.split('\n')) == 3:
440            topMargin = 1.9
441        elif len(self.label.split('\n')) == 2:
442            topMargin = 1.7
443        else:
444            topMargin = 1.5
445        return students_utils.renderPDF(
446            self, 'course_registration_slip.pdf',
447            self.context.student, studentview,
448            tableheader=tableheader,
449            tabledata=tabledata,
450            signatures=self._signatures(),
451            topMargin=topMargin,
452            omit_fields=self.omit_fields
453            )
454
455class CustomStudyCourseTranscriptPage(StudyCourseTranscriptPage):
456    """ Page to display the student's transcript.
457    """
458    grok.require('waeup.viewStudent')
459
460class CustomExportPDFTranscriptSlip(ExportPDFTranscriptSlip):
461    """Deliver a PDF slip of the context.
462    """
463#    grok.require('waeup.viewStudent')
464
465    note = _("""
466<br /><br /><br /><br />
467<font size='10'>
468<strong>Note:</strong> This copy is subject to correction for typographical errors and ratification by the departmental board.
469</font>
470""")
471
472    def _sigsInFooter(self):
473        return []
474
475    def _signatures(self):
476        return ([(
477            'Mrs. Uniamikogbo, S.O., mnim, manupa <br /> Prin. Asst Registrar  <br /> '
478            'Exams, Records and Data Processing Division <br /> For: Registrar')],)
479
480    def render(self):
481        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
482        Term = translate(_('Sem.'), target_language=portal_language)
483        Code = translate(_('Code'), target_language=portal_language)
484        Title = translate(_('Title'), target_language=portal_language)
485        Cred = translate(_('Credits'), target_language=portal_language)
486        Score = translate(_('Score'), target_language=portal_language)
487        Grade = translate(_('Grade'), target_language=portal_language)
488        studentview = StudentBasePDFFormPage(self.context.student,
489            self.request, self.omit_fields)
490        students_utils = getUtility(IStudentsUtils)
491
492        tableheader = [(Code,'code', 2.5),
493                         (Title,'title', 7),
494                         (Term, 'semester', 1.5),
495                         (Cred, 'credits', 1.5),
496                         (Score, 'total_score', 1.5),
497                         (Grade, 'grade', 1.5),
498                         ]
499
500        return students_utils.renderPDFTranscript(
501            self, 'transcript.pdf',
502            self.context.student, studentview,
503            omit_fields=self.omit_fields,
504            tableheader=tableheader,
505            signatures=self._signatures(),
506            sigs_in_footer=self._sigsInFooter(),
507            note = self.note,
508            no_passport=True
509            )
510
511class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
512    """Deliver a PDF Admission slip.
513    """
514
515    @property
516    def label(self):
517        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
518        return translate(_('e-Admission Slip \n'),
519            target_language=portal_language) \
520            + ' %s' % self.context.display_fullname
521
522class CustomExportPDFClearanceSlip(NigeriaExportPDFClearanceSlip):
523    """Deliver a PDF slip of the context.
524    """
525
526    @property
527    def label(self):
528        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
529        return translate(_('Verification/Clearance Slip\n'),
530            target_language=portal_language) \
531            + ' %s' % self.context.display_fullname
532
533    @property
534    def form_fields(self):
535        if self.context.is_postgrad:
536            form_fields = grok.AutoFields(
537                ICustomPGStudentClearance).omit('clearance_locked')
538        else:
539            form_fields = grok.AutoFields(
540                ICustomUGStudentClearance).omit('clearance_locked')
541        if not getattr(self.context, 'officer_comment'):
542            form_fields = form_fields.omit('officer_comment')
543        form_fields = form_fields.omit('def_adm')
544        return form_fields
545
546class StudentGetMatricNumberPage(UtilityView, grok.View):
547    """ Construct and set the matriculation number.
548    """
549    grok.context(IStudent)
550    grok.name('get_matric_number')
551    grok.require('waeup.viewStudent')
552
553    def update(self):
554        students_utils = getUtility(IStudentsUtils)
555        msg, mnumber = students_utils.setMatricNumber(self.context)
556        if msg:
557            self.flash(msg, type="danger")
558        else:
559            self.flash(_('Matriculation number %s assigned.' % mnumber))
560            self.context.writeLogMessage(self, '%s assigned' % mnumber)
561        self.redirect(self.url(self.context))
562        return
563
564    def render(self):
565        return
566
567class ExportPDFMatricNumberSlip(UtilityView, grok.View):
568    """Deliver a PDF notification slip.
569    """
570    grok.context(ICustomStudent)
571    grok.name('matric_number_slip.pdf')
572    grok.require('waeup.viewStudent')
573    prefix = 'form'
574
575    form_fields = grok.AutoFields(ICustomStudent).select(
576        'student_id', 'matric_number')
577    omit_fields = ('date_of_birth', 'current_level', 'flash_notice')
578
579    @property
580    def title(self):
581        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
582        return translate(_('Matriculation Number'), 'waeup.kofa',
583            target_language=portal_language)
584
585    @property
586    def label(self):
587        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
588        return translate(_('Matriculation Number Slip\n'),
589            target_language=portal_language) \
590            + ' %s' % self.context.display_fullname
591
592    def render(self):
593        if self.context.state not in (PAID,) or not self.context.is_fresh \
594            or not self.context.matric_number:
595            self.flash('Not allowed.', type="danger")
596            self.redirect(self.url(self.context))
597            return
598        students_utils = getUtility(IStudentsUtils)
599        pre_text = _('Congratulations! Your acceptance fee and school fees ' +
600                     'payments have been received and your matriculation ' +
601                     'number generated with details as follows.')
602        return students_utils.renderPDFAdmissionLetter(self,
603            self.context.student, omit_fields=self.omit_fields,
604            pre_text=pre_text, post_text='')
605
606class ExportPersonalDataSlip(UtilityView, grok.View):
607    """Deliver a PDF notification slip.
608    """
609    grok.context(ICustomStudent)
610    grok.name('personal_data_slip.pdf')
611    grok.require('waeup.viewStudent')
612    prefix = 'form'
613    note = None
614
615    form_fields = grok.AutoFields(ICustomStudentPersonal).omit('personal_updated')
616    omit_fields = ('suspended', 'suspended_comment', 'adm_code',
617                   'certificate', 'flash_notice')
618
619    @property
620    def title(self):
621        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
622        return translate(_('Personal Data'), 'waeup.kofa',
623            target_language=portal_language)
624
625    @property
626    def label(self):
627        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
628        return translate(_('Personal Data Slip\n'),
629            target_language=portal_language) \
630            + ' %s' % self.context.display_fullname
631
632    def render(self):
633        studentview = StudentBasePDFFormPage(self.context.student,
634            self.request, self.omit_fields)
635        students_utils = getUtility(IStudentsUtils)
636        return students_utils.renderPDF(self, 'personal_data_slip.pdf',
637            self.context.student, studentview, note=self.note,
638            omit_fields=self.omit_fields)
639
640class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
641    """ Page to manage bed tickets.
642    This manage form page is for both students and students officers.
643    """
644    with_hostel_selection = True
645
646class CustomBedTicketAddPage(BedTicketAddPage):
647    with_ac = False
648
649class CustomStudentFilesUploadPage(StudentFilesUploadPage):
650    """ View to upload files by student. Inherit from same class in
651    base package, not from kofacustom.nigeria which
652    requires that no application slip exists.
653    """
654
655class CustomCourseTicketDisplayFormPage(CourseTicketDisplayFormPage):
656    """ Page to display course tickets
657    """
658    form_fields = grok.AutoFields(ICustomCourseTicket)
659
660class CustomCourseTicketManageFormPage(CourseTicketManageFormPage):
661    """ Page to manage course tickets
662    """
663    form_fields = grok.AutoFields(ICustomCourseTicket)
664    form_fields['title'].for_display = True
665    form_fields['fcode'].for_display = True
666    form_fields['dcode'].for_display = True
667    form_fields['semester'].for_display = True
668    form_fields['passmark'].for_display = True
669    form_fields['credits'].for_display = True
670    form_fields['mandatory'].for_display = False
671    form_fields['automatic'].for_display = True
672    form_fields['carry_over'].for_display = True
673
674class CustomEditScoresPage(EditScoresPage):
675    """Page that filters and lists students.
676    """
677    grok.template('editscorespage')
678
679
680    def _extract_uploadfile(self, uploadfile):
681        """Get a mapping of student-ids to scores.
682
683        The mapping is constructed by reading contents from `uploadfile`.
684
685        We expect uploadfile to be a regular CSV file with columns
686        ``student_id``, ``score`` and ``ca`` (other cols are ignored).
687        """
688        result = dict()
689        data = StringIO(uploadfile.read())  # ensure we have something seekable
690        reader = csv.DictReader(data)
691        for row in reader:
692            if not ('student_id' in row and 'score' in row and 'ca' in row):
693                continue
694            result[row['student_id']] = (row['score'], row['ca'])
695        return result
696
697    def _update_scores(self, form):
698        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
699        error = ''
700        if 'UPDATE_FILE' in form:
701            if form['uploadfile']:
702                try:
703                    formvals = self._extract_uploadfile(form['uploadfile'])
704                except:
705                    self.flash(
706                        _('Uploaded file contains illegal data. Ignored'),
707                        type="danger")
708                    return False
709            else:
710                self.flash(
711                    _('No file provided.'), type="danger")
712                return False
713        else:
714            formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
715        for ticket in self.editable_tickets:
716            ticket_error = False
717            score = ticket.score
718            ca = ticket.ca
719            sid = ticket.student.student_id
720            if formvals[sid][0] == '':
721                score = None
722            if formvals[sid][1] == '':
723                ca = None
724            try:
725                if formvals[sid][0]:
726                    score = int(formvals[sid][0])
727                if formvals[sid][1]:
728                    ca = int(formvals[sid][1])
729            except ValueError:
730                error += '%s, ' % ticket.student.display_fullname
731                ticket_error = True
732            if not ticket_error and ticket.score != score:
733                try:
734                    ticket.score = score
735                except TooBig:
736                    error += '%s, ' % ticket.student.display_fullname
737                    ticket_error = True
738                    pass
739                ticket.student.__parent__.logger.info(
740                    '%s - %s %s/%s score updated (%s)' %
741                    (ob_class, ticket.student.student_id,
742                     ticket.level, ticket.code, score))
743            if not ticket_error and ticket.ca != ca:
744                try:
745                    ticket.ca = ca
746                except TooBig:
747                    error += '%s, ' % ticket.student.display_fullname
748                    pass
749                ticket.student.__parent__.logger.info(
750                    '%s - %s %s/%s ca updated (%s)' %
751                    (ob_class, ticket.student.student_id,
752                     ticket.level, ticket.code, ca))
753        if error:
754            self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
755              % error.strip(', ')), type="danger")
756        return True
757
758class EditPreviousSessionScoresPage(CustomEditScoresPage):
759
760    grok.name('edit_prev_scores')
761
762    def update(self,  *args, **kw):
763        form = self.request.form
764        self.current_academic_session = grok.getSite()[
765            'configuration'].current_academic_session
766        if self.context.__parent__.__parent__.score_editing_disabled:
767            self.flash(_('Score editing disabled.'), type="warning")
768            self.redirect(self.url(self.context))
769            return
770        if not self.current_academic_session:
771            self.flash(_('Current academic session not set.'), type="warning")
772            self.redirect(self.url(self.context))
773            return
774        previous_session = self.current_academic_session - 1
775        self.session_title = academic_sessions_vocab.getTerm(
776            previous_session).title
777        self.tickets = self._searchCatalog(previous_session)
778        if not self.tickets:
779            self.flash(_('No student found.'), type="warning")
780            self.redirect(self.url(self.context))
781            return
782        self.editable_tickets = [
783            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
784        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
785            return
786        if not self.editable_tickets:
787            return
788        success = self._update_scores(form)
789        if success:
790            self.flash(_('You successfully updated course results.'))
791        return
792
793class CustomExportPDFScoresSlip(ExportPDFScoresSlip):
794    """Deliver a PDF slip of course tickets for a lecturer.
795    """
796
797    def data(self, session):
798        cat = queryUtility(ICatalog, name='coursetickets_catalog')
799        coursetickets = cat.searchResults(
800            session=(session, session),
801            code=(self.context.code, self.context.code)
802            )
803        # In AAUE only editable tickets can be printed
804        editable_tickets = [
805            ticket for ticket in coursetickets if ticket.editable_by_lecturer]
806        header = [[_(''),
807                   _('Matric No.'),
808                   _('Reg. No.'),
809                   _('Fullname'),
810                   _('Status'),
811                   _('Course of\nStudies'),
812                   _('Level'),
813                   _('Exam\nScore'),
814                   _(' CA  '),
815                   _('Total '),
816                   _('Grade'),
817                   ],]
818        sorted_tickets = sorted(editable_tickets,
819            key=lambda ticket: ticket.student.certcode +
820                ticket.student.display_fullname + str(ticket.level))
821        no = 1
822        tickets = []
823        passed = 0
824        failed = 0
825        # In AAUE only editable tickets can be printed
826        for ticket in sorted_tickets:
827            if None in (ticket.score, ticket.ca):
828                total = 'n/a'
829                grade = 'n/a'
830            else:
831                total = ticket.score + ticket.ca
832                grade = getGradeWeightFromScore(total, ticket.student)[0]
833                if grade == 'F':
834                    failed += 1
835                else:
836                    passed += 1
837            fullname = textwrap.fill(ticket.student.display_fullname, 30)
838            row = [no,
839                  ticket.student.matric_number,
840                  ticket.student.reg_number,
841                  fullname,
842                  ticket.student.translated_state,
843                  ticket.student.certcode,
844                  ticket.level,
845                  ticket.ca,
846                  ticket.score,
847                  total,
848                  grade,
849                  ]
850            tickets.append(row)
851            no += 1
852        total = passed + failed
853        passed_perc = 0
854        failed_perc = 0
855        if total:
856            passed_perc = 100 * passed / total
857            failed_perc = 100 * failed / total
858        return header + tickets, [
859            total, passed, passed_perc, failed, failed_perc]
860
861class DownloadPreviousSessionScoresView(DownloadScoresView):
862    """View that exports scores.
863    """
864    grok.name('download_prev_scores')
865
866    def update(self):
867        self.current_academic_session = grok.getSite()[
868            'configuration'].current_academic_session
869        if self.context.__parent__.__parent__.score_editing_disabled:
870            self.flash(_('Score editing disabled.'), type="warning")
871            self.redirect(self.url(self.context))
872            return
873        if not self.current_academic_session:
874            self.flash(_('Current academic session not set.'), type="warning")
875            self.redirect(self.url(self.context))
876            return
877        site = grok.getSite()
878        exporter = getUtility(ICSVExporter, name='lecturer')
879        self.csv = exporter.export_filtered(site, filepath=None,
880                                 catalog='coursetickets',
881                                 session=self.current_academic_session-1,
882                                 level=None,
883                                 code=self.context.code)
884        return
885
886class AlumniRequestPasswordPage(StudentRequestPasswordPage):
887    """Captcha'd request password page for students.
888    """
889    grok.name('alumni_requestpw')
890    grok.require('waeup.Anonymous')
891    grok.template('alumni_requestpw')
892    form_fields = grok.AutoFields(IStudentRequestPW).select(
893        'lastname','number','email')
894    label = _('Search student record and send password for first-time login')
895
896    def _redirect_no_student(self):
897        self.flash(_('No student record found.'), type="warning")
898        self.redirect(self.application_url() + '/applicants/trans2017/register')
899        return
Note: See TracBrowser for help on using the repository browser.