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

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

IJMBE students shall see only two payment categories.

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