## $Id: viewlets.py 7741 2012-03-01 09:24:27Z 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
##
import os
import grok
from zope.component import getUtility
from zope.interface import Interface
from zope.i18n import translate
from waeup.sirp.interfaces import (
ISIRPObject, IExtFileStore, IFileStoreNameChooser, ISIRPUtils)
from waeup.sirp.interfaces import MessageFactory as _
from waeup.sirp.utils.helpers import string_from_bytes, file_size
from waeup.sirp.browser import DEFAULT_IMAGE_PATH
from waeup.sirp.browser.viewlets import (
PrimaryNavTab, ManageActionButton, AddActionButton)
from waeup.sirp.browser.layout import default_primary_nav_template
from waeup.sirp.students.workflow import (ADMITTED, PAID,
CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED)
from waeup.sirp.students.browser import (
StudentClearanceManageFormPage,
write_log_message, StudentBaseManageFormPage,
StudentFilesUploadPage, ExportPDFClearanceSlipPage, StudentsContainerPage,
StudentsContainerManagePage, StudentBaseDisplayFormPage,
StudentClearanceDisplayFormPage, StudentPersonalDisplayFormPage,
StudyCourseDisplayFormPage, StudyLevelDisplayFormPage,
CourseTicketDisplayFormPage, OnlinePaymentDisplayFormPage,
AccommodationManageFormPage, BedTicketDisplayFormPage,)
from waeup.sirp.students.interfaces import (
IStudentsContainer, IStudent, IStudentClearance,
IStudentStudyCourse, IStudentAccommodation, IStudentStudyLevel,
ICourseTicket, IStudentOnlinePayment, IBedTicket,
)
from waeup.sirp.interfaces import MessageFactory as _
grok.context(ISIRPObject) # Make ISIRPObject the default context
grok.templatedir('browser_templates')
ALLOWED_FILE_EXTENSIONS = ('jpg', 'png', 'pdf', 'tif')
class StudentManageSidebar(grok.ViewletManager):
grok.name('left_studentmanage')
class StudentManageLink(grok.Viewlet):
"""A link displayed in the student box which shows up for StudentNavigation
objects.
"""
grok.baseclass()
grok.viewletmanager(StudentManageSidebar)
grok.context(ISIRPObject)
grok.view(Interface)
grok.order(5)
grok.require('waeup.viewStudent')
link = 'index'
text = _(u'Base Data')
def render(self):
url = self.view.url(self.context.getStudent(), self.link)
portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
lang = self.request.cookies.get('sirp.language', portal_language)
text = translate(self.text, 'waeup.sirp',
target_language=lang)
return u'
%s' % (
url, text)
class StudentManageApplicationLink(StudentManageLink):
grok.order(1)
link = 'application_slip'
text = _(u'Application Slip')
def render(self):
slip = getUtility(IExtFileStore).getFileByContext(
self.context.getStudent(), attr=self.link)
if slip:
url = self.view.url(self.context,self.link)
return u'%s' % (
url, self.text)
return ''
class StudentManageBaseLink(StudentManageLink):
grok.order(2)
link = 'index'
text = _(u'Base Data')
class StudentManageClearanceLink(StudentManageLink):
grok.order(3)
link = 'view_clearance'
text = _(u'Clearance Data')
class StudentManagePersonalLink(StudentManageLink):
grok.order(4)
link = 'view_personal'
text = _(u'Personal Data')
class StudentManageStudyCourseLink(StudentManageLink):
grok.order(5)
link = 'studycourse'
text = _(u'Study Course')
class StudentManagePaymentsLink(StudentManageLink):
grok.order(6)
grok.require('waeup.payStudent')
link = 'payments'
text = _(u'Payments')
class StudentManageAccommodationLink(StudentManageLink):
grok.order(7)
grok.require('waeup.handleAccommodation')
link = 'accommodation'
text = _(u'Accommodation')
class StudentManageHistoryLink(StudentManageLink):
grok.order(8)
link = 'history'
text = _(u'History')
class StudentsContainerManageActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentsContainer)
grok.view(StudentsContainerPage)
grok.require('waeup.manageStudent')
text = _('Manage student section')
class StudentsContainerAddActionButton(AddActionButton):
grok.order(1)
grok.context(IStudentsContainer)
grok.view(StudentsContainerManagePage)
grok.require('waeup.manageStudent')
text = _('Add student')
target = 'addstudent'
class ContactActionButton(ManageActionButton):
grok.order(4)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.manageStudent')
icon = 'actionicon_mail.png'
text = _('Send email')
target = 'contactstudent'
class StudentBaseManageActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.manageStudent')
text = _('Manage')
target = 'manage_base'
class StudentClearanceManageActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.manageStudent')
text = _('Manage')
target = 'edit_clearance'
class StudentClearActionButton(ManageActionButton):
grok.order(2)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.clearStudent')
text = _('Clear student')
target = 'clear'
icon = 'actionicon_accept.png'
@property
def target_url(self):
if self.context.state != REQUESTED:
return ''
return self.view.url(self.view.context, self.target)
class StudentRejectClearanceActionButton(ManageActionButton):
grok.order(3)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.clearStudent')
text = _('Reject clearance')
target = 'reject_clearance'
icon = 'actionicon_reject.png'
@property
def target_url(self):
if self.context.state not in (REQUESTED, CLEARED):
return ''
return self.view.url(self.view.context, self.target)
class ClearanceSlipActionButton(ManageActionButton):
grok.order(4)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.viewStudent')
icon = 'actionicon_pdf.png'
text = _('Download clearance slip')
target = 'clearance.pdf'
class StudentPersonalEditActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentPersonalDisplayFormPage)
grok.require('waeup.viewStudent')
text = _('Edit')
target = 'edit_personal'
class StudyCourseManageActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentStudyCourse)
grok.view(StudyCourseDisplayFormPage)
grok.require('waeup.manageStudent')
text = _('Manage')
target = 'manage'
class CourseRegistrationSlipActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentStudyLevel)
grok.view(StudyLevelDisplayFormPage)
grok.require('waeup.viewStudent')
icon = 'actionicon_pdf.png'
text = _('Download course registration slip')
target = 'course_registration.pdf'
class StudyLevelManageActionButton(ManageActionButton):
grok.order(2)
grok.context(IStudentStudyLevel)
grok.view(StudyLevelDisplayFormPage)
grok.require('waeup.manageStudent')
text = _('Manage')
target = 'manage'
class StudentValidateCoursesActionButton(ManageActionButton):
grok.order(3)
grok.context(IStudentStudyLevel)
grok.view(StudyLevelDisplayFormPage)
grok.require('waeup.validateStudent')
text = _('Validate courses')
target = 'validate_courses'
icon = 'actionicon_accept.png'
@property
def target_url(self):
if self.context.getStudent().state != REGISTERED or \
str(self.context.__parent__.current_level) != self.context.__name__:
return ''
return self.view.url(self.view.context, self.target)
class StudentRejectCoursesActionButton(ManageActionButton):
grok.order(4)
grok.context(IStudentStudyLevel)
grok.view(StudyLevelDisplayFormPage)
grok.require('waeup.validateStudent')
text = _('Reject courses')
target = 'reject_courses'
icon = 'actionicon_reject.png'
@property
def target_url(self):
if self.context.getStudent().state not in (VALIDATED, REGISTERED) or \
str(self.context.__parent__.current_level) != self.context.__name__:
return ''
return self.view.url(self.view.context, self.target)
class CourseTicketManageActionButton(ManageActionButton):
grok.order(1)
grok.context(ICourseTicket)
grok.view(CourseTicketDisplayFormPage)
grok.require('waeup.manageStudent')
text = _('Manage')
target = 'manage'
#class OnlinePaymentManageActionButton(ManageActionButton):
# grok.order(1)
# grok.context(IStudentPaymentsContainer)
# grok.view(PaymentsDisplayFormPage)
# grok.require('waeup.manageStudent')
# text = 'Manage payments'
# target = 'manage'
class PaymentReceiptActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentOnlinePayment)
grok.view(OnlinePaymentDisplayFormPage)
grok.require('waeup.viewStudent')
icon = 'actionicon_pdf.png'
text = _('Download payment receipt')
target = 'payment_receipt.pdf'
@property
def target_url(self):
if self.context.p_state != 'paid':
return ''
return self.view.url(self.view.context, self.target)
class RequestCallbackActionButton(ManageActionButton):
grok.order(2)
grok.context(IStudentOnlinePayment)
grok.view(OnlinePaymentDisplayFormPage)
grok.require('waeup.payStudent')
icon = 'actionicon_call.png'
text = _('Request callback')
target = 'callback'
@property
def target_url(self):
if self.context.p_state != 'unpaid':
return ''
return self.view.url(self.view.context, self.target)
class AddBedTicketActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentAccommodation)
grok.view(AccommodationManageFormPage)
grok.require('waeup.handleAccommodation')
icon = 'actionicon_home.png'
text = _('Book accommodation')
target = 'add'
class BedTicketSlipActionButton(ManageActionButton):
grok.order(1)
grok.context(IBedTicket)
grok.view(BedTicketDisplayFormPage)
grok.require('waeup.handleAccommodation')
icon = 'actionicon_pdf.png'
text = _('Download bed allocation slip')
target = 'bed_allocation.pdf'
class RelocateStudentActionButton(ManageActionButton):
grok.order(2)
grok.context(IBedTicket)
grok.view(BedTicketDisplayFormPage)
grok.require('waeup.manageHostels')
icon = 'actionicon_reload.png'
text = _('Relocate student')
target = 'relocate'
class StudentBaseActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.handleStudent')
text = _('Edit base data')
target = 'edit_base'
class StudentPasswordActionButton(ManageActionButton):
grok.order(2)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.handleStudent')
icon = 'actionicon_key.png'
text = _('Change password')
target = 'change_password'
class StudentPassportActionButton(ManageActionButton):
grok.order(3)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.handleStudent')
icon = 'actionicon_portrait.png'
text = _('Change portrait')
target = 'change_portrait'
@property
def target_url(self):
if self.context.state != ADMITTED:
return ''
return self.view.url(self.view.context, self.target)
class StudentClearanceStartActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.handleStudent')
icon = 'actionicon_start.gif'
text = _('Start clearance')
target = 'start_clearance'
@property
def target_url(self):
if self.context.state != ADMITTED:
return ''
return self.view.url(self.view.context, self.target)
class StudentClearanceEditActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudent)
grok.view(StudentClearanceDisplayFormPage)
grok.require('waeup.handleStudent')
text = _('Edit')
target = 'cedit'
@property
def target_url(self):
if self.context.clearance_locked:
return ''
return self.view.url(self.view.context, self.target)
class CourseRegistrationStartActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentStudyCourse)
grok.view(StudyCourseDisplayFormPage)
grok.require('waeup.handleStudent')
icon = 'actionicon_start.gif'
text = _('Start course registration')
target = 'start_course_registration'
@property
def target_url(self):
if not self.context.getStudent().state in (CLEARED,RETURNING):
return ''
return self.view.url(self.view.context, self.target)
class AddStudyLevelActionButton(AddActionButton):
grok.order(1)
grok.context(IStudentStudyCourse)
grok.view(StudyCourseDisplayFormPage)
grok.require('waeup.handleStudent')
text = _('Add course list')
target = 'add'
@property
def target_url(self):
student = self.view.context.getStudent()
condition1 = student.state != PAID
condition2 = str(student['studycourse'].current_level) in \
self.view.context.keys()
if condition1 or condition2:
return ''
return self.view.url(self.view.context, self.target)
class StudyLevelEditActionButton(ManageActionButton):
grok.order(1)
grok.context(IStudentStudyLevel)
grok.view(StudyLevelDisplayFormPage)
grok.require('waeup.handleStudent')
text = _('Add and remove courses')
target = 'edit'
@property
def target_url(self):
student = self.view.context.getStudent()
condition1 = student.state != PAID
condition2 = student[
'studycourse'].current_level != self.view.context.level
if condition1 or condition2:
return ''
return self.view.url(self.view.context, self.target)
class StudentsTab(PrimaryNavTab):
"""Students tab in primary navigation.
"""
grok.context(ISIRPObject)
grok.order(4)
grok.require('waeup.viewStudentsTab')
pnav = 4
tab_title = _(u'Students')
@property
def link_target(self):
return self.view.application_url('students')
class PrimaryStudentNavManager(grok.ViewletManager):
"""Viewlet manager for the primary navigation tab.
"""
grok.name('primary_nav_student')
class PrimaryStudentNavTab(grok.Viewlet):
"""Base for primary student nav tabs.
"""
grok.baseclass()
grok.viewletmanager(PrimaryStudentNavManager)
template = default_primary_nav_template
grok.order(1)
grok.require('waeup.Authenticated')
pnav = 0
tab_title = u'Some Text'
@property
def link_target(self):
return self.view.application_url()
@property
def active(self):
view_pnav = getattr(self.view, 'pnav', 0)
if view_pnav == self.pnav:
return 'active'
return ''
class MyStudentDataTab(PrimaryStudentNavTab):
"""MyData dropdown tab in primary navigation.
"""
grok.order(3)
grok.require('waeup.viewMyStudentDataTab')
grok.template('mydatadropdowntabs')
pnav = 4
tab_title = _(u'My Data')
@property
def active(self):
view_pnav = getattr(self.view, 'pnav', 0)
if view_pnav == self.pnav:
return 'active dropdown'
return 'dropdown'
@property
def targets(self):
student_url = self.view.application_url() + (
'/students/%s' % self.request.principal.id)
#app_slip = getUtility(IExtFileStore).getFileByContext(
# self.context.getStudent(), 'application_slip')
targets = []
#if app_slip:
# targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
targets += [
{'url':student_url, 'title':'Base Data'},
{'url':student_url + '/view_clearance', 'title':_('Clearance Data')},
{'url':student_url + '/view_personal', 'title':_('Personal Data')},
{'url':student_url + '/studycourse', 'title':_('Study Course')},
{'url':student_url + '/payments', 'title':_('Payments')},
{'url':student_url + '/accommodation', 'title':_('Accommodation Data')},
{'url':student_url + '/history', 'title':_('History')},
]
return targets
def handle_file_delete(context, view, download_name):
"""Handle deletion of student file.
"""
store = getUtility(IExtFileStore)
store.deleteFileByContext(context, attr=download_name)
write_log_message(view, 'deleted: %s' % download_name)
view.flash(_('${a} deleted.', mapping = {'a':download_name}))
return
def handle_file_upload(upload, context, view, max_size, download_name=None):
"""Handle upload of student file.
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.
"""
# Check some file requirements first
if upload.filename.count('.') == 0:
view.flash(_('File name has no extension.'))
return False
if upload.filename.count('.') > 1:
view.flash(_('File name contains more than one dot.'))
return False
basename, expected_ext = os.path.splitext(download_name)
dummy, ext = os.path.splitext(upload.filename)
ext.lower()
if expected_ext:
if ext != expected_ext:
view.flash(_('${a} file extension expected.',
mapping = {'a':expected_ext.replace('.','')}))
return False
else:
if not ext.replace('.','') in ALLOWED_FILE_EXTENSIONS:
view.flash(
_('Only the following extension are allowed: ${a}',
mapping = {'a':', '.join(ALLOWED_FILE_EXTENSIONS)}))
return False
download_name += ext
size = file_size(upload)
if size > max_size:
view.flash(_('Uploaded file is too big.'))
return False
upload.seek(0) # file pointer moved when determining size
store = getUtility(IExtFileStore)
file_id = IFileStoreNameChooser(context).chooseName(attr=download_name)
store.createFile(file_id, upload)
write_log_message(view, 'uploaded: %s (%s)' % (download_name,upload.filename))
view.flash(_('File ${a} uploaded.', mapping = {'a':download_name}))
return True
class FileManager(grok.ViewletManager):
"""Viewlet manager for uploading files, preferably scanned images.
"""
grok.name('files')
class FileDisplay(grok.Viewlet):
"""Base file display viewlet.
"""
grok.baseclass()
grok.context(IStudentClearance)
grok.viewletmanager(FileManager)
grok.view(StudentClearanceDisplayFormPage)
grok.template('filedisplay')
grok.order(1)
grok.require('waeup.viewStudent')
label = _(u'File')
title = _(u'Scan')
download_name = u'filename.jpg'
@property
def file_exists(self):
image = getUtility(IExtFileStore).getFileByContext(
self.context, attr=self.download_name)
if image:
return True
else:
return False
class FileUpload(FileDisplay):
"""Base upload viewlet.
"""
grok.baseclass()
grok.context(IStudentClearance)
grok.viewletmanager(FileManager)
grok.view(StudentClearanceManageFormPage)
grok.template('fileupload')
grok.require('waeup.uploadStudentFile')
tab_redirect = '?tab2'
mus = 1024 * 150
upload_button =_('Upload new file')
delete_button = _('Delete attachment')
@property
def input_name(self):
return "%s" % self.__name__
def update(self):
self.max_upload_size = string_from_bytes(self.mus)
delete_button = self.request.form.get(
'delete_%s' % self.input_name, None)
upload_button = self.request.form.get(
'upload_%s' % self.input_name, None)
if delete_button:
handle_file_delete(
context=self.context, view=self.view,
download_name=self.download_name)
self.view.redirect(
self.view.url(
self.context, self.view.__name__) + self.tab_redirect)
return
if upload_button:
upload = self.request.form.get(self.input_name, None)
if upload:
# We got a fresh upload
handle_file_upload(upload,
self.context, self.view, self.mus, self.download_name)
self.view.redirect(
self.view.url(
self.context, self.view.__name__) + self.tab_redirect)
else:
self.view.flash(_('No local file selected.'))
self.view.redirect(
self.view.url(
self.context, self.view.__name__) + self.tab_redirect)
return
class PassportDisplay(FileDisplay):
"""Passport display viewlet.
"""
grok.order(1)
grok.context(IStudent)
grok.view(StudentBaseDisplayFormPage)
grok.require('waeup.viewStudent')
grok.template('imagedisplay')
label = _(u'Passport Picture')
download_name = u'passport.jpg'
class PassportUploadManage(FileUpload):
"""Passport upload viewlet for officers.
"""
grok.order(1)
grok.context(IStudent)
grok.view(StudentBaseManageFormPage)
grok.require('waeup.manageStudent')
grok.template('imageupload')
label = _(u'Passport Picture (jpg only)')
mus = 1024 * 50
download_name = u'passport.jpg'
tab_redirect = '?tab2'
class PassportUploadEdit(PassportUploadManage):
"""Passport upload viewlet for students.
"""
grok.view(StudentFilesUploadPage)
grok.require('waeup.uploadStudentFile')
class BirthCertificateDisplay(FileDisplay):
"""Birth Certificate display viewlet.
"""
grok.order(1)
label = _(u'Birth Certificate')
title = _(u'Birth Certificate Scan')
download_name = u'birth_certificate'
class BirthCertificateSlip(BirthCertificateDisplay):
grok.view(ExportPDFClearanceSlipPage)
class BirthCertificateUpload(FileUpload):
"""Birth Certificate upload viewlet.
"""
grok.order(1)
label = _(u'Birth Certificate')
title = _(u'Birth Certificate Scan')
mus = 1024 * 150
download_name = u'birth_certificate'
tab_redirect = '?tab2'
class AcceptanceLetterDisplay(FileDisplay):
"""Acceptance Letter display viewlet.
"""
grok.order(1)
label = _(u'Acceptance Letter')
title = _(u'Acceptance Letter Scan')
download_name = u'acceptance_letter'
class AcceptanceLetterSlip(AcceptanceLetterDisplay):
grok.view(ExportPDFClearanceSlipPage)
class AcceptanceLetterUpload(FileUpload):
"""AcceptanceLetter upload viewlet.
"""
grok.order(2)
label = _(u'Acceptance Letter')
title = _(u'Acceptance Letter Scan')
mus = 1024 * 150
download_name = u'acceptance_letter'
tab_redirect = '?tab2'
class Image(grok.View):
"""Renders images for students.
"""
grok.baseclass()
grok.name('none.jpg')
grok.context(IStudentClearance)
grok.require('waeup.viewStudent')
download_name = u'none.jpg'
def render(self):
# A filename chooser turns a context into a filename suitable
# for file storage.
image = getUtility(IExtFileStore).getFileByContext(
self.context, attr=self.download_name)
if image is None:
# show placeholder image
self.response.setHeader('Content-Type', 'image/jpeg')
return open(DEFAULT_IMAGE_PATH, 'rb').read()
dummy,ext = os.path.splitext(image.name)
if ext == '.jpg':
self.response.setHeader('Content-Type', 'image/jpeg')
elif ext == '.png':
self.response.setHeader('Content-Type', 'image/png')
elif ext == '.pdf':
self.response.setHeader('Content-Type', 'application/pdf')
elif ext == '.tif':
self.response.setHeader('Content-Type', 'image/tiff')
return image
class Passport(Image):
"""Renders jpeg passport picture.
"""
grok.name('passport.jpg')
download_name = u'passport.jpg'
grok.context(IStudent)
class ApplicationSlipImage(Image):
"""Renders application slip scan.
"""
grok.name('application_slip')
download_name = u'application_slip'
class BirthCertificateImage(Image):
"""Renders birth certificate scan.
"""
grok.name('birth_certificate')
download_name = u'birth_certificate'
class AcceptanceLetterImage(Image):
"""Renders acceptance letter scan.
"""
grok.name('acceptance_letter')
download_name = u'acceptance_letter'