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

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

Change order.

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