## $Id: browser.py 15503 2019-07-19 06:06:17Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
"""UI components for basic applicants and related components.
"""
import grok
import os
from zope.component import getUtility, getAdapter
from zope.i18n import translate
from hurry.workflow.interfaces import IWorkflowState
from waeup.kofa.interfaces import (
IExtFileStore, IFileStoreNameChooser, IKofaUtils)
from zope.formlib.textwidgets import BytesDisplayWidget
from waeup.kofa.utils.helpers import string_from_bytes, file_size
from waeup.kofa.applicants.browser import (
ApplicantCheckStatusPage, ApplicantBaseDisplayFormPage)
from waeup.kofa.applicants.workflow import STARTED, PAID
from waeup.kofa.applicants.viewlets import PDFActionButton
from waeup.kofa.applicants.interfaces import IApplicantRegisterUpdate
from waeup.kofa.browser.layout import UtilityView
from waeup.kofa.students.interfaces import IStudentsUtils
from waeup.kofa.interfaces import IPDF
from waeup.kofa.browser.viewlets import ManageActionButton
from waeup.aaue.interfaces import MessageFactory as _
from kofacustom.nigeria.applicants.browser import (
NigeriaApplicantDisplayFormPage,
NigeriaApplicantManageFormPage,
NigeriaApplicantEditFormPage,
NigeriaPDFApplicationSlip,
NigeriaApplicantRegistrationPage,
NigeriaExportPDFPaymentSlipPage,
)
from kofacustom.nigeria.applicants.interfaces import OMIT_DISPLAY_FIELDS
from waeup.aaue.applicants.interfaces import (
ICustomUGApplicant,
ICustomUGApplicantEdit,
ITranscriptApplicant,
ICertificateRequest,
ICustomApplicant
)
UG_OMIT_FIELDS = (
'hq_type', 'hq_fname', 'hq_matric_no',
'hq_degree', 'hq_school', 'hq_session', 'hq_disc',
'hq_type2', 'hq_fname2', 'hq_matric_no2',
'hq_degree2', 'hq_school2', 'hq_session2', 'hq_disc2',
'hq_type3', 'hq_fname3', 'hq_matric_no3',
'hq_degree3', 'hq_school3', 'hq_session3', 'hq_disc3',
'nysc_year',
'nysc_location',
'nysc_lga',
'employer',
'emp_position',
'emp_start',
'emp_end',
'emp_reason',
'employer2',
'emp2_position',
'emp2_start',
'emp2_end',
'emp2_reason',
'former_matric',
)
UG_OMIT_DISPLAY_FIELDS = OMIT_DISPLAY_FIELDS + (
'jamb_subjects_list', 'master_sheet_number') + UG_OMIT_FIELDS
UG_OMIT_PDF_FIELDS = UG_OMIT_DISPLAY_FIELDS + UG_OMIT_FIELDS + (
'alr_fname', 'alr_no', 'alr_date',
'alr_results', 'notice')
UG_OMIT_MANAGE_FIELDS = (
'special_application','jamb_subjects_list',) + UG_OMIT_FIELDS
UG_OMIT_EDIT_FIELDS = UG_OMIT_MANAGE_FIELDS + OMIT_DISPLAY_FIELDS + (
'student_id',
'notice',
'jamb_age',
'jamb_subjects',
'jamb_score',
'jamb_reg_number',
'aggregate',
'master_sheet_number',
'screening_venue',
'screening_score',
'screening_date'
)
UDE_OMIT_FIELDS = (
'nysc_year',
'nysc_location',
'nysc_lga',
'employer',
'emp_position',
'emp_start',
'emp_end',
'emp_reason',
'employer2',
'emp2_position',
'emp2_start',
'emp2_end',
'emp2_reason',
'former_matric',
)
UDE_OMIT_DISPLAY_FIELDS = OMIT_DISPLAY_FIELDS + (
'jamb_subjects_list', 'master_sheet_number') + UDE_OMIT_FIELDS
UDE_OMIT_PDF_FIELDS = UDE_OMIT_DISPLAY_FIELDS + UDE_OMIT_FIELDS + (
#'alr_fname', 'alr_no', 'alr_date', 'alr_results',
'hq_type2', 'hq_fname2', 'hq_matric_no2',
'hq_degree2', 'hq_school2', 'hq_session2', 'hq_disc2',
'hq_type3', 'hq_fname3', 'hq_matric_no3',
'hq_degree3', 'hq_school3', 'hq_session3', 'hq_disc3',
'notice')
UDE_OMIT_MANAGE_FIELDS = (
'special_application','jamb_subjects_list',) + UDE_OMIT_FIELDS
UDE_OMIT_EDIT_FIELDS = UDE_OMIT_MANAGE_FIELDS + OMIT_DISPLAY_FIELDS + (
'student_id',
'notice',
'jamb_age',
'jamb_subjects',
'jamb_score',
'jamb_reg_number',
'aggregate',
'master_sheet_number',
'screening_venue',
'screening_score',
'screening_date'
)
#UG_OMIT_PDF_FIELDS = tuple([
# element for element in UG_OMIT_PDF_FIELDS if not element == 'phone'])
#UG_OMIT_PDF_FIELDS += (
# 'reg_number','alr_fname', 'alr_no', 'alr_date',
# 'alr_results', 'notice'
# )
PG_OMIT_FIELDS = (
'fst_sit_fname',
'fst_sit_no',
'fst_sit_date',
'fst_sit_type',
'fst_sit_results',
'scd_sit_fname',
'scd_sit_no',
'scd_sit_date',
'scd_sit_type',
'scd_sit_results',
#'programme_type',
'jamb_age',
'jamb_subjects',
'jamb_score',
'jamb_reg_number',
'aggregate'
)
PG_OMIT_DISPLAY_FIELDS = OMIT_DISPLAY_FIELDS + (
'jamb_subjects_list',) + PG_OMIT_FIELDS
PG_OMIT_PDF_FIELDS = PG_OMIT_DISPLAY_FIELDS + PG_OMIT_FIELDS + (
'reg_number','alr_fname', 'alr_no', 'alr_date',
'alr_results', 'notice',
'nysc_year',
'nysc_location',
'nysc_lga',
'former_matric',
)
PG_OMIT_MANAGE_FIELDS = (
'special_application','jamb_subjects_list',) + PG_OMIT_FIELDS
PG_OMIT_EDIT_FIELDS = PG_OMIT_MANAGE_FIELDS + OMIT_DISPLAY_FIELDS + (
'student_id',
'notice',
)
PTEE_OMIT_FIELDS = (
'jamb_age',
'jamb_subjects',
'jamb_score',
'jamb_reg_number',
'aggregate'
)
PTEE_OMIT_DISPLAY_FIELDS = OMIT_DISPLAY_FIELDS + (
'jamb_subjects_list',) + PTEE_OMIT_FIELDS
PTEE_OMIT_PDF_FIELDS = PTEE_OMIT_DISPLAY_FIELDS + PTEE_OMIT_FIELDS + (
'reg_number','alr_fname', 'alr_no', 'alr_date',
'alr_results', 'notice',
'nysc_year',
'nysc_location',
'nysc_lga',
'employer',
'emp_position',
'emp_start',
'emp_end',
'emp_reason',
'employer2',
'emp2_position',
'emp2_start',
'emp2_end',
'emp2_reason',
'former_matric',
)
PTEE_OMIT_MANAGE_FIELDS = (
'special_application','jamb_subjects_list',) + PTEE_OMIT_FIELDS
PTEE_OMIT_EDIT_FIELDS = PTEE_OMIT_MANAGE_FIELDS + OMIT_DISPLAY_FIELDS + (
'student_id',
'notice',
)
UPDATE_OMIT_FIELDS = (
'firstname',
'middlename',
'lastname',
'sex',
'lga',
'course1',
)
MAX_FILE_UPLOAD_SIZE = 1024 * 500
def handle_file_upload(upload, context, view, attr=None):
"""Handle upload of applicant files.
Returns `True` in case of success or `False`.
Please note that file pointer passed in (`upload`) most probably
points to end of file when leaving this function.
"""
size = file_size(upload)
if size > MAX_FILE_UPLOAD_SIZE:
view.flash(_('Uploaded file is too big!'))
return False
dummy, ext = os.path.splitext(upload.filename)
ext.lower()
if ext != '.pdf':
view.flash(_('pdf file extension expected.'))
return False
upload.seek(0) # file pointer moved when determining size
store = getUtility(IExtFileStore)
file_id = IFileStoreNameChooser(context).chooseName(attr=attr)
store.createFile(file_id, upload)
return True
class CustomApplicantDisplayFormPage(NigeriaApplicantDisplayFormPage):
"""A display view for applicant data.
"""
@property
def file_links(self):
html = ''
pdf = getUtility(IExtFileStore).getFileByContext(
self.context, attr='stateresult.pdf')
if pdf:
html += 'Statement of Result'
return html
@property
def form_fields(self):
if self.target is not None and self.target == 'trans':
form_fields = grok.AutoFields(ITranscriptApplicant).omit(
'locked', 'suspended')
form_fields['dispatch_address'].custom_widget = BytesDisplayWidget
form_fields['perm_address'].custom_widget = BytesDisplayWidget
return form_fields
if self.target is not None and self.target == 'cert':
form_fields = grok.AutoFields(ICertificateRequest).omit(
'locked', 'suspended')
#form_fields['dispatch_address'].custom_widget = BytesDisplayWidget
#form_fields['perm_address'].custom_widget = BytesDisplayWidget
return form_fields
# AAUE is using the same interface for all regular applications.
form_fields = grok.AutoFields(ICustomUGApplicant)
if self.target is not None and self.target.startswith('pg'):
for field in PG_OMIT_DISPLAY_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('ptee',):
for field in PTEE_OMIT_DISPLAY_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('bridge', 'ude',):
for field in UDE_OMIT_DISPLAY_FIELDS:
form_fields = form_fields.omit(field)
else:
for field in UG_OMIT_DISPLAY_FIELDS:
form_fields = form_fields.omit(field)
form_fields['perm_address'].custom_widget = BytesDisplayWidget
form_fields['notice'].custom_widget = BytesDisplayWidget
if not getattr(self.context, 'student_id'):
form_fields = form_fields.omit('student_id')
if not getattr(self.context, 'screening_score'):
form_fields = form_fields.omit('screening_score')
if not getattr(self.context, 'screening_venue') or \
self.context.state not in ('submitted', 'admitted', 'created'):
form_fields = form_fields.omit('screening_venue')
if not getattr(self.context, 'screening_date') or \
self.context.state not in ('submitted', 'admitted', 'created'):
form_fields = form_fields.omit('screening_date')
return form_fields
def getCourseAdmitted(self):
"""Return link, title and code in html format to the certificate
admitted.
"""
if self.layout.isApplicant():
return ''
course_admitted = self.context.course_admitted
if getattr(course_admitted, '__parent__',None):
url = self.url(course_admitted)
title = course_admitted.title
code = course_admitted.code
return '%s - %s' %(url,code,title)
return ''
def update(self):
super(CustomApplicantDisplayFormPage, self).update()
self.extraform_url = self.url(self.context, 'stateresult.pdf')
return
class CustomPDFActionButton(PDFActionButton):
@property
def target_url(self):
if self.context.state in ('initialized', 'started', 'paid') \
or self.context.special or self.view.target in ('trans', 'cert'):
return
return self.view.url(self.view.context, self.target)
class CustomPDFApplicationSlip(NigeriaPDFApplicationSlip):
column_two_fields = ('applicant_id', 'reg_number',
'firstname', 'middlename', 'lastname', 'sex', 'date_of_birth')
#two_columns_design_fields = [
# 'fst_sit_fname', 'fst_sit_no', 'fst_sit_date',
# 'fst_sit_type', 'fst_sit_results',
# 'scd_sit_fname', 'scd_sit_no', 'scd_sit_date',
# 'scd_sit_type', 'scd_sit_results']
def _getCourseAdmittedLink(self, view):
return None
def _getDeptAndFaculty(self):
return [None, None]
@property
def note(self):
note = getattr(self.context.__parent__, 'application_slip_notice', None)
if note:
return '
' + note
if self.context.sex == 'm':
pronoun = 'he'
else:
pronoun = 'she'
return '''
The applicant has acknowledged that, if discovered at any time that %s does not possess
any of the qualifications which %s claims %s has obtained, %s will be expelled from the
University not be re-admitted for the same or any other programme, even if %s has
upgraded previous qualifications or possess additional qualifications.
''' % (
pronoun, pronoun, pronoun, pronoun, pronoun)
@property
def form_fields(self):
# AAUE is using the same interface for all regular applications.
form_fields = grok.AutoFields(ICustomUGApplicant)
if self.target is not None and self.target.startswith('pg'):
for field in PG_OMIT_PDF_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('ptee',):
for field in PTEE_OMIT_PDF_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('bridge', 'ude',):
for field in UDE_OMIT_PDF_FIELDS:
form_fields = form_fields.omit(field)
else:
for field in UG_OMIT_PDF_FIELDS:
form_fields = form_fields.omit(field)
if not getattr(self.context, 'student_id'):
form_fields = form_fields.omit('student_id')
if not getattr(self.context, 'screening_score'):
form_fields = form_fields.omit('screening_score')
if not getattr(self.context, 'screening_venue'):
form_fields = form_fields.omit('screening_venue')
if not getattr(self.context, 'screening_date'):
form_fields = form_fields.omit('screening_date')
return form_fields
class CustomApplicantManageFormPage(NigeriaApplicantManageFormPage):
"""A full edit view for applicant data.
"""
@property
def form_fields(self):
if self.target is not None and self.target == 'trans':
form_fields = grok.AutoFields(ITranscriptApplicant)
form_fields['applicant_id'].for_display = True
return form_fields
if self.target is not None and self.target == 'cert':
form_fields = grok.AutoFields(ICertificateRequest)
form_fields['applicant_id'].for_display = True
return form_fields
# AAUE is using the same interface for all regular applications.
form_fields = grok.AutoFields(ICustomUGApplicant)
if self.target is not None and self.target.startswith('pg'):
for field in PG_OMIT_MANAGE_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('ptee',):
for field in PTEE_OMIT_MANAGE_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('bridge', 'ude',):
for field in UDE_OMIT_MANAGE_FIELDS:
form_fields = form_fields.omit(field)
else:
for field in UG_OMIT_MANAGE_FIELDS:
form_fields = form_fields.omit(field)
form_fields['student_id'].for_display = True
form_fields['applicant_id'].for_display = True
return form_fields
def update(self):
super(CustomApplicantManageFormPage, self).update()
upload_stateresult = self.request.form.get('form.stateresult', None)
if upload_stateresult:
# We got a fresh stateresult upload
success = handle_file_upload(
upload_stateresult, self.context, self, attr='stateresult.pdf')
if success:
self.context.writeLogMessage(self, 'saved: stateresult')
else:
self.upload_success = False
self.max_file_upload_size = string_from_bytes(MAX_FILE_UPLOAD_SIZE)
return
class CustomApplicantEditFormPage(NigeriaApplicantEditFormPage):
"""An applicant-centered edit view for applicant data.
"""
def unremovable(self, ticket):
return True
def dataNotComplete(self):
store = getUtility(IExtFileStore)
# Temporarily enable passport upload also for cert and trans
# applications.
if self.context.__parent__.with_picture:
if not store.getFileByContext(self.context, attr=u'passport.jpg'):
return _('No passport picture uploaded.')
if not self.target[:4] in ('cert', 'tran') and \
not self.request.form.get('confirm_passport', False):
return _('Passport picture confirmation box not ticked.')
if self.target in ('trans', 'cert') and \
not store.getFileByContext(self.context, attr=u'stateresult.pdf'):
return _('No statement of result pdf file uploaded.')
return False
# AAUE applicants never see the 'Remove Selected Tickets' button.
@property
def display_actions(self):
# If the form is unlocked, applicants are allowed to save the form
# and remove unused tickets.
actions = [[_('Save')], []]
# Only in state started they can also add tickets.
if self.context.state == STARTED:
actions = [[_('Save')],
[_('Add online payment ticket')]]
# In state paid, they can submit the data and further add tickets
# if the application is special.
elif self.context.special and self.context.state == PAID:
actions = [[_('Save'), _('Finally Submit')],
[_('Add online payment ticket')]]
elif self.context.state == PAID:
actions = [[_('Save'), _('Finally Submit')], []]
return actions
@property
def form_fields(self):
if self.target is not None and self.target == 'trans':
form_fields = grok.AutoFields(ITranscriptApplicant).omit(
'locked', 'suspended')
form_fields['applicant_id'].for_display = True
form_fields['reg_number'].for_display = True
form_fields['place_of_birth'].field.required = True
form_fields['date_of_birth'].field.required = True
form_fields['nationality'].field.required = True
form_fields['email'].field.required = True
form_fields['phone'].field.required = True
form_fields['perm_address'].field.required = True
form_fields['dispatch_address'].field.required = True
form_fields['entry_mode'].field.required = True
form_fields['entry_session'].field.required = True
form_fields['end_session'].field.required = True
form_fields['course_studied'].field.required = True
return form_fields
if self.target is not None and self.target == 'cert':
form_fields = grok.AutoFields(ICertificateRequest).omit(
'locked', 'suspended')
form_fields['applicant_id'].for_display = True
form_fields['reg_number'].for_display = True
form_fields['place_of_birth'].field.required = True
form_fields['date_of_birth'].field.required = True
form_fields['nationality'].field.required = True
form_fields['email'].field.required = True
form_fields['phone'].field.required = True
form_fields['entry_session'].field.required = True
form_fields['end_session'].field.required = True
form_fields['course_studied'].field.required = True
form_fields['certificate_type'].field.required = True
# Additional omissions
if self.context.__parent__.code == 'cert5':
for field in ('firstname', 'middlename', 'lastname'):
form_fields[field].for_display = True
return form_fields
# AAUE is using the same interface for all regular applications.
form_fields = grok.AutoFields(ICustomUGApplicantEdit)
if self.target is not None and self.target.startswith('pg'):
for field in PG_OMIT_EDIT_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('ptee',):
for field in PTEE_OMIT_EDIT_FIELDS:
form_fields = form_fields.omit(field)
elif self.target is not None and self.target in ('bridge', 'ude',):
for field in UDE_OMIT_EDIT_FIELDS:
form_fields = form_fields.omit(field)
else:
for field in UG_OMIT_EDIT_FIELDS:
form_fields = form_fields.omit(field)
# Additional omissions
if self.target is not None and self.target in ('ude', 'utme'):
for field in UPDATE_OMIT_FIELDS:
form_fields[field].for_display = True
form_fields['applicant_id'].for_display = True
form_fields['reg_number'].for_display = True
return form_fields
def update(self):
if self.context.locked or (
self.context.__parent__.expired and
self.context.__parent__.strict_deadline):
self.emit_lock_message()
return
if getattr(
self.context.course1, 'code', 'nocourse') == self.request.form.get(
'form.course2', None):
self.flash(_('2nd choice course must differ from 1st choice course.'),
type='danger')
self.redirect(self.url(self.context))
return
if getattr(
self.context.course1, 'code', 'nocourse') == self.request.form.get(
'form.course3', None):
self.flash(_('3rd choice course must differ from 1st choice course.'),
type='danger')
self.redirect(self.url(self.context))
return
super(CustomApplicantEditFormPage, self).update()
upload_stateresult = self.request.form.get('form.stateresult', None)
if upload_stateresult:
# We got a fresh stateresult upload
success = handle_file_upload(
upload_stateresult, self.context, self, attr='stateresult.pdf')
if not success:
self.upload_success = False
self.max_file_upload_size = string_from_bytes(MAX_FILE_UPLOAD_SIZE)
return
class CustomApplicantRegistrationPage(NigeriaApplicantRegistrationPage):
"""Captcha'd registration page for applicants.
"""
@property
def form_fields(self):
form_fields = None
if self.context.mode == 'update':
form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
'lastname','reg_number','email')
target = getattr(self.context, 'prefix', None)
if target in ('trans', 'cert'):
form_fields.get('reg_number').field.title = u'Matriculation Number'
else: #if self.context.mode == 'create':
form_fields = grok.AutoFields(ICustomUGApplicantEdit).select(
'firstname', 'middlename', 'lastname', 'email', 'phone')
return form_fields
def _redirect(self, email, password, applicant_id):
# Forward email and credentials to landing page.
self.redirect(self.url(self.context, 'registration_complete',
data = dict(email=email, password=password,
applicant_id=applicant_id)))
return
@property
def _postfix(self):
"""Alumni records have to be imported into several containers.
Therefore a string must be added to their registration number
to make it unique.
"""
if self.context.prefix in ('trans', 'cert'):
return self.context.code
return ''
class CustomExportPDFPaymentSlipPage(NigeriaExportPDFPaymentSlipPage):
@property
def payment_slip_download_warning(self):
return ''
class CustomApplicantCheckStatusPage(ApplicantCheckStatusPage):
"""Captcha'd status checking page for applicants.
"""
grok.template('applicantcheckstatus')
class ScreeningInvitationActionButton(ManageActionButton):
grok.order(8) # This button should always be the last one.
grok.context(ICustomApplicant)
grok.view(CustomApplicantDisplayFormPage)
grok.require('waeup.viewApplication')
icon = 'actionicon_pdf.png'
text = _('Download screening invitation letter')
target = 'screening_invitation.pdf'
@property
def target_url(self):
if not self.context.screening_date or not self.context.state in (
'submitted', 'admitted', 'created'):
return ''
return self.view.url(self.view.context, self.target)
class ExportScreeningInvitationLetter(UtilityView, grok.View):
"""Deliver a slip with only screening data.
This form page is available only in AAUE.
"""
grok.context(ICustomApplicant)
grok.name('screening_invitation.pdf')
grok.require('waeup.viewApplication')
prefix = 'form'
label = u'Screening Invitation Letter'
form_fields = []
@property
def note(self):
if self.context.screening_date:
year = self.context.__parent__.year
session = '%s/%s' % (year, year + 1)
sdate = self.context.screening_date
stime = ''
if '@' in self.context.screening_date:
sdate = self.context.screening_date.split('@')[0].strip()
stime = self.context.screening_date.split('@')[1].strip()
return """
Dear %s,
You are invited to the Ambrose Alli University %s Admissions Screening Exercise.
Date: %s
Time: %s
Venue: %s
Please bring this letter of invitation and the downloaded application form along with you on your screening date.
You are expected to be available 30 minutes before the commencement of your Screening.
""" % (
self.context.display_fullname,
session,
sdate,
stime,
self.context.screening_venue)
return
@property
def title(self):
return None
def update(self):
if not self.context.screening_date or not self.context.state in (
'submitted', 'admitted', 'created'):
self.flash(_('Forbidden'), type="warning")
self.redirect(self.url(self.context))
def render(self):
applicantview = ApplicantBaseDisplayFormPage(self.context, self.request)
students_utils = getUtility(IStudentsUtils)
return students_utils.renderPDF(self,'screening_data.pdf',
self.context, applicantview, note=self.note)
class StateResult(grok.View):
"""Renders the pdf form extension for applicants.
"""
grok.name('stateresult.pdf')
grok.context(ICustomApplicant)
grok.require('waeup.viewApplication')
def render(self):
pdf = getUtility(IExtFileStore).getFileByContext(
self.context, attr='stateresult.pdf')
self.response.setHeader('Content-Type', 'application/pdf')
return pdf