source: main/waeup.sirp/branches/henrik-bootstrap/src/waeup/sirp/students/viewlets.py @ 7786

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

Fix some tests. Two tests still fail since the registration state is not displayed in student site layout.

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