source: main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py @ 9929

Last change on this file since 9929 was 9924, checked in by Henrik Bettermann, 12 years ago

Add new permission waeup.editStudyLevel to allow course advisers to edit study levels after rejection.

Improve logging. Creation and deletion of course tickets is now properly logged.

  • Property svn:keywords set to Id
File size: 29.7 KB
Line 
13## $Id: viewlets.py 9924 2013-01-30 08:58:37Z 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 zope.i18n import translate
23from waeup.kofa.interfaces import (
24    IKofaObject, IExtFileStore, IFileStoreNameChooser)
25from waeup.kofa.interfaces import MessageFactory as _
26from waeup.kofa.utils.helpers import string_from_bytes, file_size
27from waeup.kofa.browser import DEFAULT_IMAGE_PATH
28from waeup.kofa.browser.viewlets import (
29    PrimaryNavTab, ManageActionButton, AddActionButton)
30from waeup.kofa.browser.layout import (
31    default_primary_nav_template, default_filedisplay_template,
32    default_fileupload_template)
33from waeup.kofa.students.workflow import (
34    ADMITTED, PAID, REQUESTED, RETURNING, CLEARED, REGISTERED,
35    VALIDATED)
36from waeup.kofa.students.browser import (
37    clearance_disabled_message,
38    StudentClearanceManageFormPage,
39    StudentBaseManageFormPage, StudentFilesUploadPage,
40    ExportPDFClearanceSlipPage, StudentsContainerPage,
41    StudentsContainerManagePage, StudentBaseDisplayFormPage,
42    StudentClearanceDisplayFormPage, StudentPersonalDisplayFormPage,
43    StudyCourseDisplayFormPage, StudyLevelDisplayFormPage,
44    CourseTicketDisplayFormPage, OnlinePaymentDisplayFormPage,
45    AccommodationManageFormPage, BedTicketDisplayFormPage,
46    StudentClearanceEditFormPage, StudentPersonalEditFormPage,
47    PaymentsManageFormPage)
48from waeup.kofa.students.interfaces import (
49    IStudentsContainer, IStudent, IStudentStudyCourse, IStudentAccommodation,
50    IStudentStudyLevel, ICourseTicket, IStudentOnlinePayment, IBedTicket,
51    IStudentPaymentsContainer,
52    )
53from waeup.kofa.utils.helpers import get_fileformat
54
55grok.context(IKofaObject) # Make IKofaObject the default context
56grok.templatedir('browser_templates')
57
58ALLOWED_FILE_EXTENSIONS = ('jpg', 'png', 'pdf', 'tif')
59
60class StudentManageSidebar(grok.ViewletManager):
61    grok.name('left_studentmanage')
62
63class StudentManageLink(grok.Viewlet):
64    """A link displayed in the student box which shows up for StudentNavigation
65    objects.
66
67    """
68    grok.baseclass()
69    grok.viewletmanager(StudentManageSidebar)
70    grok.context(IKofaObject)
71    grok.view(Interface)
72    grok.order(5)
73    grok.require('waeup.viewStudent')
74
75    link = 'index'
76    text = _(u'Base Data')
77
78    def render(self):
79        url = self.view.url(self.context.student, self.link)
80        # Here we know that the cookie has been set
81        lang = self.request.cookies.get('kofa.language')
82        text = translate(self.text, 'waeup.kofa',
83            target_language=lang)
84        return u'<li><a href="%s">%s</a></li>' % (
85                url, text)
86
87class StudentManageApplicationLink(StudentManageLink):
88    grok.order(1)
89    link = 'application_slip'
90    text = _(u'Application Slip')
91
92    def render(self):
93        slip = getUtility(IExtFileStore).getFileByContext(
94            self.context.student, attr=self.link)
95        if slip:
96            url = self.view.url(self.context.student,self.link)
97            return u'<li><a href="%s">%s</a></li>' % (
98                    url, self.text)
99        return ''
100
101class StudentManageBaseLink(StudentManageLink):
102    grok.order(2)
103    link = 'index'
104    text = _(u'Base Data')
105
106class StudentManageClearanceLink(StudentManageLink):
107    grok.order(3)
108    link = 'view_clearance'
109    text = _(u'Clearance Data')
110
111class StudentManagePersonalLink(StudentManageLink):
112    grok.order(4)
113    link = 'view_personal'
114    text = _(u'Personal Data')
115
116class StudentManageStudyCourseLink(StudentManageLink):
117    grok.order(5)
118    link = 'studycourse'
119    text = _(u'Study Course')
120
121class StudentManagePaymentsLink(StudentManageLink):
122    grok.order(6)
123    grok.require('waeup.payStudent')
124    link = 'payments'
125    text = _(u'Payments')
126
127class StudentManageAccommodationLink(StudentManageLink):
128    grok.order(7)
129    grok.require('waeup.handleAccommodation')
130    link = 'accommodation'
131    text = _(u'Accommodation')
132
133class StudentManageHistoryLink(StudentManageLink):
134    grok.order(8)
135    link = 'history'
136    text = _(u'History')
137
138
139class StudentsContainerManageActionButton(ManageActionButton):
140    grok.order(1)
141    grok.context(IStudentsContainer)
142    grok.view(StudentsContainerPage)
143    grok.require('waeup.manageStudent')
144    text = _('Manage student section')
145
146class StudentsContainerAddActionButton(AddActionButton):
147    grok.order(1)
148    grok.context(IStudentsContainer)
149    grok.view(StudentsContainerManagePage)
150    grok.require('waeup.manageStudent')
151    text = _('Add student')
152    target = 'addstudent'
153
154class ContactActionButton(ManageActionButton):
155    grok.order(5)
156    grok.context(IStudent)
157    grok.view(StudentBaseDisplayFormPage)
158    grok.require('waeup.manageStudent')
159    icon = 'actionicon_mail.png'
160    text = _('Send email')
161    target = 'contactstudent'
162
163class StudentBaseManageActionButton(ManageActionButton):
164    grok.order(1)
165    grok.context(IStudent)
166    grok.view(StudentBaseDisplayFormPage)
167    grok.require('waeup.manageStudent')
168    text = _('Manage')
169    target = 'manage_base'
170
171class StudentTrigTransActionButton(ManageActionButton):
172    grok.order(2)
173    grok.context(IStudent)
174    grok.view(StudentBaseDisplayFormPage)
175    grok.require('waeup.triggerTransition')
176    icon = 'actionicon_trigtrans.png'
177    text = _(u'Trigger transition')
178    target = 'trigtrans'
179
180class StudentLoginAsActionButton(ManageActionButton):
181    grok.order(3)
182    grok.context(IStudent)
183    grok.view(StudentBaseDisplayFormPage)
184    grok.require('waeup.loginAsStudent')
185    icon = 'actionicon_mask.png'
186    text = _(u'Login as student')
187    target = 'loginasstep1'
188
189class AdmissionSlipActionButton(ManageActionButton):
190    grok.order(4)
191    grok.context(IStudent)
192    grok.view(StudentBaseDisplayFormPage)
193    grok.require('waeup.viewStudent')
194    icon = 'actionicon_pdf.png'
195    text = _('Download admission letter')
196    target = 'admission_slip.pdf'
197
198class StudentTransfernButton(ManageActionButton):
199    grok.order(6)
200    grok.context(IStudent)
201    grok.view(StudentBaseDisplayFormPage)
202    grok.require('waeup.manageStudent')
203    text = _('Transfer student')
204    target = 'transfer'
205    icon = 'actionicon_redo.png'
206
207class StudentDeactivateActionButton(ManageActionButton):
208    grok.order(7)
209    grok.context(IStudent)
210    grok.view(StudentBaseDisplayFormPage)
211    grok.require('waeup.manageStudent')
212    text = _('Deactivate account')
213    target = 'deactivate'
214    icon = 'actionicon_traffic_lights_red.png'
215
216    @property
217    def target_url(self):
218        if self.context.suspended:
219            return ''
220        return self.view.url(self.view.context, self.target)
221
222    @property
223    def onclick(self):
224        return "return window.confirm(%s);" % _(
225            "'A history message will be added. Are you sure?'")
226
227class StudentActivateActionButton(ManageActionButton):
228    grok.order(7)
229    grok.context(IStudent)
230    grok.view(StudentBaseDisplayFormPage)
231    grok.require('waeup.manageStudent')
232    text = _('Activate account')
233    target = 'activate'
234    icon = 'actionicon_traffic_lights_green.png'
235
236    @property
237    def target_url(self):
238        if not self.context.suspended:
239            return ''
240        return self.view.url(self.view.context, self.target)
241
242    @property
243    def onclick(self):
244        return "return window.confirm(%s);" % _(
245            "'A history message will be added. Are you sure?'")
246
247class StudentClearanceManageActionButton(ManageActionButton):
248    grok.order(1)
249    grok.context(IStudent)
250    grok.view(StudentClearanceDisplayFormPage)
251    grok.require('waeup.manageStudent')
252    text = _('Manage')
253    target = 'manage_clearance'
254
255class StudentClearActionButton(ManageActionButton):
256    grok.order(2)
257    grok.context(IStudent)
258    grok.view(StudentClearanceDisplayFormPage)
259    grok.require('waeup.clearStudent')
260    text = _('Clear student')
261    target = 'clear'
262    icon = 'actionicon_accept.png'
263
264    @property
265    def target_url(self):
266        if clearance_disabled_message(self.context):
267            return ''
268        if self.context.state != REQUESTED:
269            return ''
270        return self.view.url(self.view.context, self.target)
271
272class StudentRejectClearanceActionButton(ManageActionButton):
273    grok.order(3)
274    grok.context(IStudent)
275    grok.view(StudentClearanceDisplayFormPage)
276    grok.require('waeup.clearStudent')
277    text = _('Reject clearance')
278    target = 'reject_clearance'
279    icon = 'actionicon_reject.png'
280
281    @property
282    def target_url(self):
283        if clearance_disabled_message(self.context):
284            return ''
285        if self.context.state not in (REQUESTED, CLEARED):
286            return ''
287        return self.view.url(self.view.context, self.target)
288
289class ClearanceSlipActionButton(ManageActionButton):
290    grok.order(4)
291    grok.context(IStudent)
292    grok.view(StudentClearanceDisplayFormPage)
293    grok.require('waeup.viewStudent')
294    icon = 'actionicon_pdf.png'
295    text = _('Download clearance slip')
296    target = 'clearance_slip.pdf'
297
298class ClearanceViewActionButton(ManageActionButton):
299    grok.order(1)
300    grok.context(IStudent)
301    grok.view(StudentClearanceEditFormPage)
302    grok.require('waeup.viewStudent')
303    icon = 'actionicon_view.png'
304    text = _('View')
305    target = 'view_clearance'
306
307class PersonalViewActionButton(ManageActionButton):
308    grok.order(1)
309    grok.context(IStudent)
310    grok.view(StudentPersonalEditFormPage)
311    grok.require('waeup.viewStudent')
312    icon = 'actionicon_view.png'
313    text = _('View')
314    target = 'view_personal'
315
316class StudentPersonalManageActionButton(ManageActionButton):
317    grok.order(1)
318    grok.context(IStudent)
319    grok.view(StudentPersonalDisplayFormPage)
320    grok.require('waeup.manageStudent')
321    text = _('Manage')
322    target = 'manage_personal'
323
324class StudentPersonalEditActionButton(ManageActionButton):
325    grok.order(2)
326    grok.context(IStudent)
327    grok.view(StudentPersonalDisplayFormPage)
328    grok.require('waeup.handleStudent')
329    text = _('Edit')
330    target = 'edit_personal'
331
332class StudyCourseManageActionButton(ManageActionButton):
333    grok.order(1)
334    grok.context(IStudentStudyCourse)
335    grok.view(StudyCourseDisplayFormPage)
336    grok.require('waeup.manageStudent')
337    text = _('Manage')
338    target = 'manage'
339
340    @property
341    def target_url(self):
342        if self.context.is_current:
343            return self.view.url(self.view.context, self.target)
344        return False
345
346class StudyLevelManageActionButton(ManageActionButton):
347    grok.order(1)
348    grok.context(IStudentStudyLevel)
349    grok.view(StudyLevelDisplayFormPage)
350    grok.require('waeup.manageStudent')
351    text = _('Manage')
352    target = 'manage'
353
354    @property
355    def target_url(self):
356        is_current = self.context.__parent__.is_current
357        if not is_current:
358            return ''
359        return self.view.url(self.view.context, self.target)
360
361class StudentValidateCoursesActionButton(ManageActionButton):
362    grok.order(3)
363    grok.context(IStudentStudyLevel)
364    grok.view(StudyLevelDisplayFormPage)
365    grok.require('waeup.validateStudent')
366    text = _('Validate courses')
367    target = 'validate_courses'
368    icon = 'actionicon_accept.png'
369
370    @property
371    def target_url(self):
372        is_current = self.context.__parent__.is_current
373        if self.context.student.state != REGISTERED or \
374            str(self.context.__parent__.current_level) != self.context.__name__ or\
375            not is_current:
376            return ''
377        return self.view.url(self.view.context, self.target)
378
379class StudentRejectCoursesActionButton(ManageActionButton):
380    grok.order(4)
381    grok.context(IStudentStudyLevel)
382    grok.view(StudyLevelDisplayFormPage)
383    grok.require('waeup.validateStudent')
384    text = _('Reject courses')
385    target = 'reject_courses'
386    icon = 'actionicon_reject.png'
387
388    @property
389    def target_url(self):
390        is_current = self.context.__parent__.is_current
391        if self.context.student.state not in (VALIDATED, REGISTERED) or \
392            str(self.context.__parent__.current_level) != self.context.__name__ or\
393            not is_current:
394            return ''
395        return self.view.url(self.view.context, self.target)
396
397class CourseRegistrationSlipActionButton(ManageActionButton):
398    grok.order(5)
399    grok.context(IStudentStudyLevel)
400    grok.view(StudyLevelDisplayFormPage)
401    grok.require('waeup.viewStudent')
402    icon = 'actionicon_pdf.png'
403    text = _('Download course registration slip')
404    target = 'course_registration_slip.pdf'
405
406    @property
407    def target_url(self):
408        is_current = self.context.__parent__.is_current
409        if not is_current:
410            return ''
411        return self.view.url(self.view.context, self.target)
412
413class CourseTicketManageActionButton(ManageActionButton):
414    grok.order(1)
415    grok.context(ICourseTicket)
416    grok.view(CourseTicketDisplayFormPage)
417    grok.require('waeup.manageStudent')
418    text = _('Manage')
419    target = 'manage'
420
421#class OnlinePaymentManageActionButton(ManageActionButton):
422#    grok.order(1)
423#    grok.context(IStudentPaymentsContainer)
424#    grok.view(PaymentsDisplayFormPage)
425#    grok.require('waeup.manageStudent')
426#    text = 'Manage payments'
427#    target = 'manage'
428
429class PaymentReceiptActionButton(ManageActionButton):
430    grok.order(9) # This button should always be the last one.
431    grok.context(IStudentOnlinePayment)
432    grok.view(OnlinePaymentDisplayFormPage)
433    grok.require('waeup.viewStudent')
434    icon = 'actionicon_pdf.png'
435    text = _('Download payment slip')
436    target = 'payment_slip.pdf'
437
438    @property
439    def target_url(self):
440        #if self.context.p_state != 'paid':
441        #    return ''
442        return self.view.url(self.view.context, self.target)
443
444class ApprovePaymentActionButton(ManageActionButton):
445    grok.order(8)
446    grok.context(IStudentOnlinePayment)
447    grok.view(OnlinePaymentDisplayFormPage)
448    grok.require('waeup.managePortal')
449    icon = 'actionicon_accept.png'
450    text = _('Approve payment')
451    target = 'approve'
452
453    @property
454    def target_url(self):
455        if self.context.p_state == 'paid':
456            return ''
457        return self.view.url(self.view.context, self.target)
458
459class AddBedTicketActionButton(ManageActionButton):
460    grok.order(1)
461    grok.context(IStudentAccommodation)
462    grok.view(AccommodationManageFormPage)
463    grok.require('waeup.handleAccommodation')
464    icon = 'actionicon_home.png'
465    text = _('Book accommodation')
466    target = 'add'
467
468class BedTicketSlipActionButton(ManageActionButton):
469    grok.order(1)
470    grok.context(IBedTicket)
471    grok.view(BedTicketDisplayFormPage)
472    grok.require('waeup.handleAccommodation')
473    icon = 'actionicon_pdf.png'
474    text = _('Download bed allocation slip')
475    target = 'bed_allocation_slip.pdf'
476
477class RelocateStudentActionButton(ManageActionButton):
478    grok.order(2)
479    grok.context(IBedTicket)
480    grok.view(BedTicketDisplayFormPage)
481    grok.require('waeup.manageHostels')
482    icon = 'actionicon_reload.png'
483    text = _('Relocate student')
484    target = 'relocate'
485
486class StudentBaseActionButton(ManageActionButton):
487    grok.order(1)
488    grok.context(IStudent)
489    grok.view(StudentBaseDisplayFormPage)
490    grok.require('waeup.handleStudent')
491    text = _('Edit')
492    target = 'edit_base'
493
494class StudentPasswordActionButton(ManageActionButton):
495    grok.order(2)
496    grok.context(IStudent)
497    grok.view(StudentBaseDisplayFormPage)
498    grok.require('waeup.handleStudent')
499    icon = 'actionicon_key.png'
500    text = _('Change password')
501    target = 'change_password'
502
503class StudentPassportActionButton(ManageActionButton):
504    grok.order(3)
505    grok.context(IStudent)
506    grok.view(StudentBaseDisplayFormPage)
507    grok.require('waeup.handleStudent')
508    icon = 'actionicon_portrait.png'
509    text = _('Change portrait')
510    target = 'change_portrait'
511
512    @property
513    def target_url(self):
514        if self.context.state != ADMITTED:
515            return ''
516        return self.view.url(self.view.context, self.target)
517
518class StudentClearanceStartActionButton(ManageActionButton):
519    grok.order(1)
520    grok.context(IStudent)
521    grok.view(StudentClearanceDisplayFormPage)
522    grok.require('waeup.handleStudent')
523    icon = 'actionicon_start.gif'
524    text = _('Start clearance')
525    target = 'start_clearance'
526
527    @property
528    def target_url(self):
529        if self.context.state != ADMITTED:
530            return ''
531        return self.view.url(self.view.context, self.target)
532
533class StudentClearanceEditActionButton(ManageActionButton):
534    grok.order(1)
535    grok.context(IStudent)
536    grok.view(StudentClearanceDisplayFormPage)
537    grok.require('waeup.handleStudent')
538    text = _('Edit')
539    target = 'cedit'
540
541    @property
542    def target_url(self):
543        if self.context.clearance_locked:
544            return ''
545        return self.view.url(self.view.context, self.target)
546
547class StartSessionActionButton(ManageActionButton):
548    grok.order(1)
549    grok.context(IStudentStudyCourse)
550    grok.view(StudyCourseDisplayFormPage)
551    grok.require('waeup.handleStudent')
552    icon = 'actionicon_start.gif'
553    text = _('Start new session')
554    target = 'start_session'
555
556    @property
557    def target_url(self):
558        if self.context.next_session_allowed and self.context.is_current:
559            return self.view.url(self.view.context, self.target)
560        return False
561
562class AddStudyLevelActionButton(AddActionButton):
563    grok.order(1)
564    grok.context(IStudentStudyCourse)
565    grok.view(StudyCourseDisplayFormPage)
566    grok.require('waeup.handleStudent')
567    text = _('Add course list')
568    target = 'add'
569
570    @property
571    def target_url(self):
572        student = self.view.context.student
573        condition1 = student.state != PAID
574        condition2 = str(student['studycourse'].current_level) in \
575            self.view.context.keys()
576        condition3 = not self.context.is_current
577        if condition1 or condition2 or condition3:
578            return ''
579        return self.view.url(self.view.context, self.target)
580
581class StudyLevelEditActionButton(ManageActionButton):
582    grok.order(2)
583    grok.context(IStudentStudyLevel)
584    grok.view(StudyLevelDisplayFormPage)
585    grok.require('waeup.editStudyLevel')
586    text = _('Edit course list')
587    target = 'edit'
588
589    @property
590    def target_url(self):
591        student = self.view.context.student
592        condition1 = student.state == PAID
593        condition2 = self.view.context.is_current_level
594        is_current = self.context.__parent__.is_current
595        if condition1 and condition2 and is_current:
596            return self.view.url(self.view.context, self.target)
597        return ''
598
599class AddPaymentActionButton(AddActionButton):
600    grok.order(1)
601    grok.context(IStudentPaymentsContainer)
602    grok.view(PaymentsManageFormPage)
603    grok.require('waeup.payStudent')
604    text = _('Add current session payment ticket')
605    target = 'addop'
606
607class AddPreviousPaymentActionButton(AddActionButton):
608    grok.order(2)
609    grok.context(IStudentPaymentsContainer)
610    grok.view(PaymentsManageFormPage)
611    grok.require('waeup.payStudent')
612    text = _('Add previous session payment ticket')
613    target = 'addpp'
614
615    @property
616    def target_url(self):
617        student = self.view.context.student
618        if student.before_payment:
619            return ''
620        return self.view.url(self.view.context, self.target)
621
622class AddBalancePaymentActionButton(AddActionButton):
623    grok.order(3)
624    grok.context(IStudentPaymentsContainer)
625    grok.view(PaymentsManageFormPage)
626    grok.require('waeup.payStudent')
627    text = _('Add balance payment ticket')
628    target = 'addbp'
629
630    @property
631    def target_url(self):
632        return self.view.url(self.view.context, self.target)
633
634class StudentsTab(PrimaryNavTab):
635    """Students tab in primary navigation.
636    """
637
638    grok.context(IKofaObject)
639    grok.order(4)
640    grok.require('waeup.viewStudentsTab')
641
642    pnav = 4
643    tab_title = _(u'Students')
644
645    @property
646    def link_target(self):
647        return self.view.application_url('students')
648
649class PrimaryStudentNavManager(grok.ViewletManager):
650    """Viewlet manager for the primary navigation tab.
651    """
652    grok.name('primary_nav_student')
653
654class PrimaryStudentNavTab(grok.Viewlet):
655    """Base for primary student nav tabs.
656    """
657    grok.baseclass()
658    grok.viewletmanager(PrimaryStudentNavManager)
659    template = default_primary_nav_template
660    grok.order(1)
661    grok.require('waeup.Authenticated')
662    pnav = 0
663    tab_title = u'Some Text'
664
665    @property
666    def link_target(self):
667        return self.view.application_url()
668
669    @property
670    def active(self):
671        view_pnav = getattr(self.view, 'pnav', 0)
672        if view_pnav == self.pnav:
673            return 'active'
674        return ''
675
676class MyStudentDataTab(PrimaryStudentNavTab):
677    """MyData dropdown tab in primary navigation.
678    """
679    grok.order(3)
680    grok.require('waeup.viewMyStudentDataTab')
681    grok.template('mydatadropdowntabs')
682    pnav = 4
683    tab_title = _(u'My Data')
684
685    @property
686    def active(self):
687        view_pnav = getattr(self.view, 'pnav', 0)
688        if view_pnav == self.pnav:
689            return 'active dropdown'
690        return 'dropdown'
691
692    @property
693    def targets(self):
694        student = grok.getSite()['students'][self.request.principal.id]
695        student_url = self.view.url(student)
696        app_slip = getUtility(IExtFileStore).getFileByContext(
697            student, 'application_slip')
698        targets = []
699        if app_slip:
700            targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
701        targets += [
702            {'url':student_url, 'title':'Base Data'},
703            {'url':student_url + '/view_clearance',
704             'title':_('Clearance Data')},
705            {'url':student_url + '/view_personal', 'title':_('Personal Data')},
706            {'url':student_url + '/studycourse', 'title':_('Study Course')},
707            {'url':student_url + '/payments', 'title':_('Payments')},
708            {'url':student_url + '/accommodation',
709             'title':_('Accommodation Data')},
710            {'url':student_url + '/history', 'title':_('History')},
711            ]
712        return targets
713
714def handle_file_delete(context, view, download_name):
715    """Handle deletion of student file.
716
717    """
718    store = getUtility(IExtFileStore)
719    store.deleteFileByContext(context, attr=download_name)
720    context.writeLogMessage(view, 'deleted: %s' % download_name)
721    view.flash(_('${a} deleted.', mapping = {'a':download_name}))
722    return
723
724def handle_file_upload(upload, context, view, max_size, download_name=None):
725    """Handle upload of student file.
726
727    Returns `True` in case of success or `False`.
728
729    Please note that file pointer passed in (`upload`) most probably
730    points to end of file when leaving this function.
731    """
732    # Check some file requirements first
733    size = file_size(upload)
734    if size > max_size:
735        view.flash(_('Uploaded file is too big.'))
736        return False
737    upload.seek(0) # file pointer moved when determining size
738    file_format = get_fileformat(None, upload.read(512))
739    upload.seek(0) # same here
740    if file_format is None:
741        view.flash(_('Could not determine file type.'))
742        return False
743    basename, expected_ext = os.path.splitext(download_name)
744    if expected_ext:
745        if '.' + file_format != expected_ext:
746            view.flash(_('${a} file extension expected.',
747                mapping = {'a':expected_ext[1:]}))
748            return False
749    else:
750        if not file_format in ALLOWED_FILE_EXTENSIONS:
751            view.flash(
752                _('Only the following extensions are allowed: ${a}',
753                mapping = {'a':', '.join(ALLOWED_FILE_EXTENSIONS)}))
754            return False
755        download_name += '.' + file_format
756    store = getUtility(IExtFileStore)
757    file_id = IFileStoreNameChooser(context).chooseName(attr=download_name)
758    store.createFile(file_id, upload)
759    context.writeLogMessage(view, 'uploaded: %s (%s)' % (
760        download_name,upload.filename))
761    view.flash(_('File ${a} uploaded.', mapping = {'a':download_name}))
762    return True
763
764class FileManager(grok.ViewletManager):
765    """Viewlet manager for uploading files, preferably scanned images.
766    """
767    grok.name('files')
768
769class FileDisplay(grok.Viewlet):
770    """Base file display viewlet.
771    """
772    grok.baseclass()
773    grok.context(IStudent)
774    grok.viewletmanager(FileManager)
775    grok.view(StudentClearanceDisplayFormPage)
776    template = default_filedisplay_template
777    grok.order(1)
778    grok.require('waeup.viewStudent')
779    label = _(u'File')
780    title = _(u'Scan')
781    download_name = u'filename.jpg'
782
783    @property
784    def file_exists(self):
785        image = getUtility(IExtFileStore).getFileByContext(
786            self.context, attr=self.download_name)
787        if image:
788            return True
789        else:
790            return False
791
792class FileUpload(FileDisplay):
793    """Base upload viewlet.
794    """
795    grok.baseclass()
796    grok.context(IStudent)
797    grok.viewletmanager(FileManager)
798    grok.view(StudentClearanceManageFormPage)
799    template = default_fileupload_template
800    grok.require('waeup.uploadStudentFile')
801    tab_redirect = '?tab2'
802    mus = 1024 * 150
803    upload_button =_('Upload new file')
804    delete_button = _('Delete attachment')
805    show_viewlet = True
806
807    @property
808    def input_name(self):
809        return "%s" % self.__name__
810
811    def update(self):
812        self.max_upload_size = string_from_bytes(self.mus)
813        delete_button = self.request.form.get(
814            'delete_%s' % self.input_name, None)
815        upload_button = self.request.form.get(
816            'upload_%s' % self.input_name, None)
817        if delete_button:
818            handle_file_delete(
819                context=self.context, view=self.view,
820                download_name=self.download_name)
821            self.view.redirect(
822                self.view.url(
823                    self.context, self.view.__name__) + self.tab_redirect)
824            return
825        if upload_button:
826            upload = self.request.form.get(self.input_name, None)
827            if upload:
828                # We got a fresh upload
829                handle_file_upload(upload,
830                    self.context, self.view, self.mus, self.download_name)
831                self.view.redirect(
832                    self.view.url(
833                        self.context, self.view.__name__) + self.tab_redirect)
834            else:
835                self.view.flash(_('No local file selected.'))
836                self.view.redirect(
837                    self.view.url(
838                        self.context, self.view.__name__) + self.tab_redirect)
839        return
840
841class PassportDisplay(FileDisplay):
842    """Passport display viewlet.
843    """
844    grok.order(1)
845    grok.context(IStudent)
846    grok.view(StudentBaseDisplayFormPage)
847    grok.require('waeup.viewStudent')
848    grok.template('imagedisplay')
849    label = _(u'Passport Picture')
850    download_name = u'passport.jpg'
851
852class PassportUploadManage(FileUpload):
853    """Passport upload viewlet for officers.
854    """
855    grok.order(1)
856    grok.context(IStudent)
857    grok.view(StudentBaseManageFormPage)
858    grok.require('waeup.manageStudent')
859    grok.template('imageupload')
860    label = _(u'Passport Picture (jpg only)')
861    mus = 1024 * 50
862    download_name = u'passport.jpg'
863    tab_redirect = '?tab2'
864
865class PassportUploadEdit(PassportUploadManage):
866    """Passport upload viewlet for students.
867    """
868    grok.view(StudentFilesUploadPage)
869    grok.require('waeup.uploadStudentFile')
870
871class BirthCertificateDisplay(FileDisplay):
872    """Birth Certificate display viewlet.
873    """
874    grok.order(1)
875    label = _(u'Birth Certificate')
876    title = _(u'Birth Certificate Scan')
877    download_name = u'birth_certificate'
878
879class BirthCertificateSlip(BirthCertificateDisplay):
880    grok.view(ExportPDFClearanceSlipPage)
881
882class BirthCertificateUpload(FileUpload):
883    """Birth Certificate upload viewlet.
884    """
885    grok.order(1)
886    label = _(u'Birth Certificate')
887    title = _(u'Birth Certificate Scan')
888    mus = 1024 * 150
889    download_name = u'birth_certificate'
890    tab_redirect = '?tab2'
891
892class Image(grok.View):
893    """Renders images for students.
894    """
895    grok.baseclass()
896    grok.name('none.jpg')
897    grok.context(IStudent)
898    grok.require('waeup.viewStudent')
899    download_name = u'none.jpg'
900
901    def render(self):
902        # A filename chooser turns a context into a filename suitable
903        # for file storage.
904        image = getUtility(IExtFileStore).getFileByContext(
905            self.context, attr=self.download_name)
906        if image is None:
907            # show placeholder image
908            self.response.setHeader('Content-Type', 'image/jpeg')
909            return open(DEFAULT_IMAGE_PATH, 'rb').read()
910        dummy,ext = os.path.splitext(image.name)
911        if ext == '.jpg':
912            self.response.setHeader('Content-Type', 'image/jpeg')
913        elif ext == '.png':
914            self.response.setHeader('Content-Type', 'image/png')
915        elif ext == '.pdf':
916            self.response.setHeader('Content-Type', 'application/pdf')
917        elif ext == '.tif':
918            self.response.setHeader('Content-Type', 'image/tiff')
919        return image
920
921class Passport(Image):
922    """Renders jpeg passport picture.
923    """
924    grok.name('passport.jpg')
925    download_name = u'passport.jpg'
926    grok.context(IStudent)
927
928class ApplicationSlipImage(Image):
929    """Renders application slip scan.
930    """
931    grok.name('application_slip')
932    download_name = u'application_slip'
933
934class BirthCertificateImage(Image):
935    """Renders birth certificate scan.
936    """
937    grok.name('birth_certificate')
938    download_name = u'birth_certificate'
Note: See TracBrowser for help on using the repository browser.