source: main/waeup.sirp/trunk/src/waeup/sirp/students/viewlets.py @ 7361

Last change on this file since 7361 was 7351, checked in by Henrik Bettermann, 13 years ago

The StudentApplication? class is deprecated. We want to store the application_slip pdf file file instead.

Prepare everything in the students package for downloading such a pdf file.

  • Property svn:keywords set to Id
File size: 14.7 KB
RevLine 
[7191]1## $Id: viewlets.py 7351 2011-12-15 12:04:13Z henrik $
2##
3## Copyright (C) 2011 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##
[7106]18import os
[6642]19import grok
[7097]20from zope.component import getUtility
[6642]21from zope.interface import Interface
[7097]22from waeup.sirp.interfaces import (
[7321]23    ISIRPObject, IExtFileStore, IFileStoreNameChooser)
[7097]24from waeup.sirp.utils.helpers import string_from_bytes, file_size
25from waeup.sirp.browser import DEFAULT_IMAGE_PATH
[7184]26from waeup.sirp.browser.viewlets import PrimaryNavTab
[7243]27from waeup.sirp.browser.layout import default_primary_nav_template
[7097]28from waeup.sirp.students.browser import (
[7108]29    StudentClearanceDisplayFormPage, StudentClearanceManageFormPage,
[7112]30    write_log_message, StudentBaseManageFormPage, StudentBaseDisplayFormPage,
[7280]31    StudentFilesUploadPage, ExportPDFClearanceSlipPage)
[7112]32from waeup.sirp.students.interfaces import IStudent, IStudentClearance
[6642]33
[7321]34grok.context(ISIRPObject) # Make ISIRPObject the default context
[6687]35grok.templatedir('browser_templates')
[6642]36
[7123]37ALLOWED_FILE_EXTENSIONS = ('jpg', 'png', 'pdf', 'tif')
38
[6687]39class StudentManageSidebar(grok.ViewletManager):
40    grok.name('left_studentmanage')
[6642]41
[6687]42class StudentManageLink(grok.Viewlet):
[6660]43    """A link displayed in the student box which shows up for StudentNavigation
[6642]44    objects.
45
46    """
47    grok.baseclass()
[6687]48    grok.viewletmanager(StudentManageSidebar)
[7321]49    grok.context(ISIRPObject)
[6642]50    grok.view(Interface)
51    grok.order(5)
[6660]52    grok.require('waeup.viewStudent')
[6642]53
54    link = 'index'
55    text = u'Base Data'
56
57    def render(self):
58        url = self.view.url(self.context.getStudent(), self.link)
59        return u'<div class="portlet"><a href="%s">%s</a></div>' % (
60                url, self.text)
61
[6687]62class StudentManageBaseLink(StudentManageLink):
63    grok.order(1)
64    link = 'index'
65    text = u'Base Data'
66
[7340]67class StudentManageApplicationLink(StudentManageLink):
68    grok.order(2)
[7351]69    link = 'application_slip'
70    text = u'Application Slip'
[7340]71
72    def render(self):
[7351]73        slip = getUtility(IExtFileStore).getFileByContext(
74            self.context.getStudent(), attr=self.link)
75        if slip:
76            url = self.view.url(self.context,self.link)
[7340]77            return u'<div class="portlet"><a href="%s">%s</a></div>' % (
78                    url, self.text)
79        return ''
80
[6687]81class StudentManageClearanceLink(StudentManageLink):
[7340]82    grok.order(3)
[6687]83    link = 'view_clearance'
84    text = u'Clearance Data'
85
86class StudentManagePersonalLink(StudentManageLink):
[7340]87    grok.order(4)
[6687]88    link = 'view_personal'
89    text = u'Personal Data'
90
91class StudentManageStudyCourseLink(StudentManageLink):
[7340]92    grok.order(5)
[6687]93    link = 'studycourse'
94    text = u'Study Course'
95
96class StudentManagePaymentsLink(StudentManageLink):
[7340]97    grok.order(6)
[7181]98    grok.require('waeup.payStudent')
[6687]99    link = 'payments'
100    text = u'Payments'
101
102class StudentManageAccommodationLink(StudentManageLink):
[7340]103    grok.order(7)
[7181]104    grok.require('waeup.handleAccommodation')
[6687]105    link = 'accommodation'
106    text = u'Accommodation Data'
107
108class StudentManageHistoryLink(StudentManageLink):
[7340]109    grok.order(8)
[6687]110    link = 'history'
111    text = u'History'
112
113
114class StudentMenu(grok.ViewletManager):
115    grok.name('top_student')
116
117class StudentLink(grok.Viewlet):
118    """A link displayed in the student box which shows up for StudentNavigation
119    objects.
120
121    """
122    grok.baseclass()
123    grok.viewletmanager(StudentMenu)
[7321]124    grok.context(ISIRPObject)
[6687]125    grok.view(Interface)
126    grok.order(5)
127    grok.require('waeup.viewStudent')
128    template = grok.PageTemplateFile('browser_templates/plainactionbutton.pt')
129
130    link = 'index'
131    text = u'Base Data'
132
133    @property
134    def target_url(self):
135        """Get a URL to the target...
136        """
137        return self.view.url(self.context.getStudent(), self.link)
138
[6642]139class StudentBaseLink(StudentLink):
140    grok.order(1)
141    link = 'index'
142    text = u'Base Data'
143
[7351]144class ApplicationSlipLink(StudentLink):
[7340]145    grok.order(2)
[7351]146    link = 'application_slip'
147    text = u'Application Slip'
[7340]148
149    @property
150    def target_url(self):
[7351]151        slip = getUtility(IExtFileStore).getFileByContext(
152            self.context.getStudent(), attr=self.link)
153        if slip:
154            return self.view.url(self.context,self.link)
[7340]155        return
156
[6642]157class StudentClearanceLink(StudentLink):
[7340]158    grok.order(3)
[6642]159    link = 'view_clearance'
160    text = u'Clearance Data'
161
162class StudentPersonalLink(StudentLink):
[7340]163    grok.order(4)
[6642]164    link = 'view_personal'
165    text = u'Personal Data'
166
167class StudentStudyCourseLink(StudentLink):
[7340]168    grok.order(5)
[6642]169    link = 'studycourse'
170    text = u'Study Course'
171
172class StudentPaymentsLink(StudentLink):
[7340]173    grok.order(6)
[6642]174    link = 'payments'
175    text = u'Payments'
176
177class StudentAccommodationLink(StudentLink):
[7340]178    grok.order(7)
[6642]179    link = 'accommodation'
[6687]180    text = u'Accommodation'
[6642]181
182class StudentHistoryLink(StudentLink):
[7340]183    grok.order(8)
[6642]184    link = 'history'
185    text = u'History'
[6687]186
[7184]187class StudentsTab(PrimaryNavTab):
188    """Students tab in primary navigation.
189    """
190
[7321]191    grok.context(ISIRPObject)
[7184]192    grok.order(4)
[7240]193    grok.require('waeup.viewStudentsTab')
[7184]194
195    pnav = 4
196    tab_title = u'Students'
197
198    @property
199    def link_target(self):
200        return self.view.application_url('students')
201
[6687]202class PrimaryStudentNavManager(grok.ViewletManager):
203    """Viewlet manager for the primary navigation tab.
204    """
205    grok.name('primary_nav_student')
206
207class PrimaryStudentNavTab(grok.Viewlet):
208    """Base for primary student nav tabs.
209    """
210    grok.baseclass()
211    grok.viewletmanager(PrimaryStudentNavManager)
[7243]212    template = default_primary_nav_template
[6687]213    grok.order(1)
[7184]214    grok.require('waeup.Authenticated')
[6687]215    pnav = 0
216    tab_title = u'Some Text'
217
218    @property
219    def link_target(self):
220        return self.view.application_url()
221
222    @property
223    def active(self):
224        view_pnav = getattr(self.view, 'pnav', 0)
225        if view_pnav == self.pnav:
226            return 'active'
227        return ''
228
[7240]229class MyStudentDataTab(PrimaryStudentNavTab):
[6687]230    """MyData-tab in primary navigation.
231    """
232    grok.order(3)
[7240]233    grok.require('waeup.viewMyStudentDataTab')
[6687]234    pnav = 4
235    tab_title = u'My Data'
236
237    @property
238    def link_target(self):
239        rel_link = '/students/%s' % self.request.principal.id
[7097]240        return self.view.application_url() + rel_link
241
[7107]242def handle_file_delete(context, view, download_name):
243    """Handle deletion of student file.
244
245    """
246    store = getUtility(IExtFileStore)
247    store.deleteFileByContext(context, attr=download_name)
[7108]248    write_log_message(view, 'deleted: %s' % download_name)
[7123]249    view.flash('%s deleted.' % download_name)
[7107]250    return
251
[7106]252def handle_file_upload(upload, context, view, max_size, download_name=None):
253    """Handle upload of student file.
[7097]254
255    Returns `True` in case of success or `False`.
256
257    Please note that file pointer passed in (`upload`) most probably
258    points to end of file when leaving this function.
259    """
[7106]260    # Check some file requirements first
261    if upload.filename.count('.') == 0:
262        view.flash('File name has no extension.')
263        return False
264    if upload.filename.count('.') > 1:
265        view.flash('File name contains more than one dot.')
266        return False
267    basename, expected_ext = os.path.splitext(download_name)
268    dummy, ext = os.path.splitext(upload.filename)
269    ext.lower()
[7123]270    if expected_ext:
271        if ext != expected_ext:
272            view.flash('%s file extension expected.' %
273                expected_ext.replace('.',''))
274            return False
275    else:
276        if not ext.replace('.','') in ALLOWED_FILE_EXTENSIONS:
277            view.flash(
278                'Only the following extension are allowed: %s' %
279                ', '.join(ALLOWED_FILE_EXTENSIONS))
280            return False
281        download_name += ext
[7097]282    size = file_size(upload)
283    if size > max_size:
[7106]284        view.flash('Uploaded file is too big.')
[7097]285        return False
286    upload.seek(0) # file pointer moved when determining size
287    store = getUtility(IExtFileStore)
[7106]288    file_id = IFileStoreNameChooser(context).chooseName(attr=download_name)
[7097]289    store.createFile(file_id, upload)
[7108]290    write_log_message(view, 'uploaded: %s (%s)' % (download_name,upload.filename))
291    view.flash('File %s uploaded.' % download_name)
[7097]292    return True
293
294class FileManager(grok.ViewletManager):
295    """Viewlet manager for uploading files, preferably scanned images.
296    """
297    grok.name('files')
298
299class FileDisplay(grok.Viewlet):
300    """Base file display viewlet.
301    """
302    grok.baseclass()
[7100]303    grok.context(IStudentClearance)
[7097]304    grok.viewletmanager(FileManager)
305    grok.view(StudentClearanceDisplayFormPage)
306    grok.template('filedisplay')
307    grok.order(1)
308    grok.require('waeup.viewStudent')
[7319]309    label = u'File'
[7127]310    title = u'Scan'
[7106]311    download_name = u'filename.jpg'
[7097]312
[7107]313    @property
314    def file_exists(self):
315        image = getUtility(IExtFileStore).getFileByContext(
316            self.context, attr=self.download_name)
317        if image:
318            return True
319        else:
320            return False
321
[7097]322class FileUpload(FileDisplay):
323    """Base upload viewlet.
324    """
325    grok.baseclass()
[7100]326    grok.context(IStudentClearance)
[7097]327    grok.viewletmanager(FileManager)
328    grok.view(StudentClearanceManageFormPage)
329    grok.template('fileupload')
[7127]330    grok.require('waeup.uploadStudentFile')
[7134]331    tab_redirect = ''
[7097]332    mus = 1024 * 150
333
[7117]334    @property
335    def input_name(self):
336        return "%s" % self.__name__
337
[7097]338    def update(self):
339        self.max_upload_size = string_from_bytes(self.mus)
[7117]340        delete_button = self.request.form.get(
341            'delete_%s' % self.input_name, None)
342        upload_button = self.request.form.get(
343            'upload_%s' % self.input_name, None)
[7108]344        if delete_button:
[7107]345            handle_file_delete(
346                context=self.context, view=self.view,
347                download_name=self.download_name)
348            self.view.redirect(
[7134]349                self.view.url(
350                    self.context, self.view.__name__) + self.tab_redirect)
[7107]351            return
[7108]352        if upload_button:
353            upload = self.request.form.get(self.input_name, None)
354            if upload:
355                # We got a fresh upload
[7111]356                handle_file_upload(upload,
357                    self.context, self.view, self.mus, self.download_name)
358                self.view.redirect(
[7134]359                    self.view.url(
360                        self.context, self.view.__name__) + self.tab_redirect)
[7117]361            else:
362                self.view.flash('No local file selected.')
363                self.view.redirect(
[7134]364                    self.view.url(
365                        self.context, self.view.__name__) + self.tab_redirect)
[7097]366        return
367
[7112]368class PassportDisplay(FileDisplay):
369    """Passport display viewlet.
370    """
371    grok.order(1)
372    grok.context(IStudent)
373    grok.view(StudentBaseDisplayFormPage)
374    grok.require('waeup.viewStudent')
375    grok.template('imagedisplay')
[7319]376    label = u'Passport Picture'
[7112]377    download_name = u'passport.jpg'
378
379class PassportUploadManage(FileUpload):
380    """Passport upload viewlet for officers.
381    """
382    grok.order(1)
383    grok.context(IStudent)
384    grok.view(StudentBaseManageFormPage)
[7136]385    grok.require('waeup.manageStudent')
[7112]386    grok.template('imageupload')
[7319]387    label = u'Passport Picture (jpg only)'
[7112]388    mus = 1024 * 50
389    download_name = u'passport.jpg'
[7134]390    tab_redirect = '#tab-2'
[7112]391
392class PassportUploadEdit(PassportUploadManage):
393    """Passport upload viewlet for students.
394    """
[7114]395    grok.view(StudentFilesUploadPage)
[7127]396    grok.require('waeup.uploadStudentFile')
[7112]397
[7097]398class BirthCertificateDisplay(FileDisplay):
[7112]399    """Birth Certificate display viewlet.
[7097]400    """
401    grok.order(1)
[7319]402    label = u'Birth Certificate'
[7127]403    title = u'Birth Certificate Scan'
[7123]404    download_name = u'birth_certificate'
[7097]405
[7280]406class BirthCertificateSlip(BirthCertificateDisplay):
407    grok.view(ExportPDFClearanceSlipPage)
408
[7127]409class BirthCertificateUpload(FileUpload):
[7097]410    """Birth Certificate upload viewlet.
411    """
412    grok.order(1)
[7319]413    label = u'Birth Certificate'
[7127]414    title = u'Birth Certificate Scan'
[7097]415    mus = 1024 * 150
[7123]416    download_name = u'birth_certificate'
[7134]417    tab_redirect = '#tab-2'
[7097]418
[7111]419class AcceptanceLetterDisplay(FileDisplay):
[7112]420    """Acceptance Letter display viewlet.
[7111]421    """
422    grok.order(1)
[7319]423    label = u'Acceptance Letter'
[7127]424    title = u'Acceptance Letter Scan'
[7123]425    download_name = u'acceptance_letter'
[7111]426
[7280]427class AcceptanceLetterSlip(AcceptanceLetterDisplay):
428    grok.view(ExportPDFClearanceSlipPage)
429
[7127]430class AcceptanceLetterUpload(FileUpload):
[7111]431    """AcceptanceLetter upload viewlet.
432    """
[7112]433    grok.order(2)
[7319]434    label = u'Acceptance Letter'
[7127]435    title = u'Acceptance Letter Scan'
[7111]436    mus = 1024 * 150
[7123]437    download_name = u'acceptance_letter'
[7134]438    tab_redirect = '#tab-2'
[7111]439
[7097]440class Image(grok.View):
[7351]441    """Renders images for students.
[7097]442    """
[7106]443    grok.baseclass()
[7097]444    grok.name('none.jpg')
[7112]445    grok.context(IStudentClearance)
[7097]446    grok.require('waeup.viewStudent')
[7106]447    download_name = u'none.jpg'
[7097]448
449    def render(self):
450        # A filename chooser turns a context into a filename suitable
451        # for file storage.
452        image = getUtility(IExtFileStore).getFileByContext(
[7106]453            self.context, attr=self.download_name)
[7097]454        if image is None:
455            # show placeholder image
[7123]456            self.response.setHeader('Content-Type', 'image/jpeg')
[7097]457            return open(DEFAULT_IMAGE_PATH, 'rb').read()
[7123]458        dummy,ext = os.path.splitext(image.name)
459        if ext == '.jpg':
460            self.response.setHeader('Content-Type', 'image/jpeg')
461        elif ext == '.png':
462            self.response.setHeader('Content-Type', 'image/png')
463        elif ext == '.pdf':
464            self.response.setHeader('Content-Type', 'application/pdf')
465        elif ext == '.tif':
466            self.response.setHeader('Content-Type', 'image/tiff')
[7097]467        return image
468
[7112]469class Passport(Image):
470    """Renders jpeg passport picture.
471    """
472    grok.name('passport.jpg')
473    download_name = u'passport.jpg'
474    grok.context(IStudent)
475
[7351]476class ApplicationSlipImage(Image):
477    """Renders application slip scan.
478    """
479    grok.name('application_slip')
480    download_name = u'application_slip'
481
[7097]482class BirthCertificateImage(Image):
[7351]483    """Renders birth certificate scan.
[7097]484    """
[7123]485    grok.name('birth_certificate')
486    download_name = u'birth_certificate'
[7111]487
488class AcceptanceLetterImage(Image):
[7351]489    """Renders acceptance letter scan.
[7111]490    """
[7123]491    grok.name('acceptance_letter')
492    download_name = u'acceptance_letter'
Note: See TracBrowser for help on using the repository browser.