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

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

Lecturers can edit current session and previous session courses whenever they like. All restrictions are removed.

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