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

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

We do not need a second plainactionbutton pagetemplate.

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