source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/students/tests/test_browser.py @ 14212

Last change on this file since 14212 was 13856, checked in by Henrik Bettermann, 9 years ago

Add FinancialClearanceOfficer? role. Only Financial Clearance Officers are allowed to print slips with signature fields.

  • Property svn:keywords set to Id
File size: 21.5 KB
Line 
1## $Id: test_browser.py 13856 2016-05-04 07:14:34Z 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 shutil
20import tempfile
21from time import time
22from datetime import datetime, timedelta
23from StringIO import StringIO
24from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
25from zope.component.hooks import setSite, clearSite
26from zope.component import getUtility, createObject
27from zope.interface import verify
28from zope.securitypolicy.interfaces import IPrincipalRoleManager
29from waeup.kofa.app import University
30from waeup.kofa.students.tests.test_browser import (
31    StudentsFullSetup, SAMPLE_IMAGE)
32from waeup.kofa.testing import FunctionalTestCase
33from waeup.kofa.interfaces import (
34    IExtFileStore, IFileStoreNameChooser)
35from waeup.kofa.schoolgrades import ResultEntry
36from waeup.kofa.students.batching import StudentProcessor
37from waeup.kofa.students.interfaces import IStudentsUtils
38from waeup.kofa.browser.tests.test_pdf import samples_dir
39from kofacustom.nigeria.students.batching import NigeriaStudentProcessor
40from kofacustom.nigeria.testing import FunctionalLayer
41from kofacustom.nigeria.utils.utils import NigeriaKofaUtils
42from kofacustom.nigeria.students.interfaces import (
43    INigeriaStudentStudyCourse, INigeriaStudent,
44    INigeriaStudentStudyLevel, INigeriaCourseTicket)
45
46
47STUDENT_SAMPLE_DATA = open(
48    os.path.join(os.path.dirname(__file__), 'sample_student_data.csv'),
49    'rb').read()
50
51STUDENT_HEADER_FIELDS = STUDENT_SAMPLE_DATA.split(
52    '\n')[0].split(',')
53
54class StudentProcessorTest(FunctionalTestCase):
55    """Perform some batching tests.
56    """
57
58    layer = FunctionalLayer
59
60    def setUp(self):
61        super(StudentProcessorTest, self).setUp()
62        # Setup a sample site for each test
63        app = University()
64        self.dc_root = tempfile.mkdtemp()
65        app['datacenter'].setStoragePath(self.dc_root)
66
67        # Prepopulate the ZODB...
68        self.getRootFolder()['app'] = app
69        # we add the site immediately after creation to the
70        # ZODB. Catalogs and other local utilities are not setup
71        # before that step.
72        self.app = self.getRootFolder()['app']
73        # Set site here. Some of the following setup code might need
74        # to access grok.getSite() and should get our new app then
75        setSite(app)
76
77        self.processor_base = StudentProcessor()
78        self.processor = NigeriaStudentProcessor()
79        self.workdir = tempfile.mkdtemp()
80        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
81        open(self.csv_file, 'wb').write(STUDENT_SAMPLE_DATA)
82
83    def tearDown(self):
84        super(StudentProcessorTest, self).tearDown()
85        shutil.rmtree(self.workdir)
86        shutil.rmtree(self.dc_root)
87        clearSite()
88        return
89
90    def test_import(self):
91        num, num_warns, fin_file, fail_file = self.processor.doImport(
92            self.csv_file, STUDENT_HEADER_FIELDS)
93        #print open(fail_file).read()
94        self.assertEqual(num_warns,0)
95        assert len(self.app['students'].keys()) == 3
96        # Also fst_sit_results have been properly imported (tested only here!)
97        self.assertEqual(
98            self.app['students']['K1000000'].fst_sit_results[0].__dict__,
99            {'grade': 'A1', 'subject': 'visual_art'})
100        self.assertEqual(
101            self.app['students']['K1000000'].fst_sit_results[1].__dict__,
102            {'grade': 'C6', 'subject': 'applied_electricity'})
103        shutil.rmtree(os.path.dirname(fin_file))
104
105
106class StudentUITests(StudentsFullSetup):
107    """Tests for customized student class views and pages
108    """
109
110    layer = FunctionalLayer
111
112    def setUp(self):
113        super(StudentUITests, self).setUp()
114
115    def test_classes(self):
116        # Let's see if objects created in the customized
117        # portal really implement the customized interfaces
118        verify.verifyObject(INigeriaStudent, self.student)
119        verify.verifyObject(
120            INigeriaStudentStudyCourse, self.student['studycourse'])
121        studylevel = createObject(u'waeup.StudentStudyLevel')
122        verify.verifyObject(INigeriaStudentStudyLevel, studylevel)
123        ticket = createObject(u'waeup.CourseTicket')
124        verify.verifyObject(INigeriaCourseTicket, ticket)
125        IWorkflowState(self.student).setState('returning')
126        # Let's see if next_session_allowed works as expected
127        # A, ug_ft, 100
128        self.assertTrue(self.student['studycourse'].next_session_allowed)
129        # Zero, ug_ft, 100
130        self.student['studycourse'].current_verdict = '0' # Zero!
131        self.assertTrue(self.student['studycourse'].next_session_allowed)
132        # Zero, ug_ft, 200
133        self.student['studycourse'].current_level = 200
134        self.assertFalse(self.student['studycourse'].next_session_allowed)
135        # Zero, de_ft, 200
136        self.student['studycourse'].certificate.study_mode = 'de_ft'
137        self.assertTrue(self.student['studycourse'].next_session_allowed)
138        # Zero, ph_ft, 300
139        self.student['studycourse'].certificate.study_mode = 'ph_ft'
140        self.student['studycourse'].current_level = 300
141        self.assertTrue(self.student['studycourse'].next_session_allowed)
142        # Zero, ph_ft, 400
143        self.student['studycourse'].current_level = 400
144        self.assertFalse(self.student['studycourse'].next_session_allowed)
145
146        # Now we convert the certificate into a postgraduate certificate
147        IWorkflowState(self.student).setState('school fee paid')
148        self.certificate.study_mode = 'pg_ft'
149        # ... and voila next session registration is allowed
150        self.assertTrue(self.student['studycourse'].next_session_allowed)
151
152    def test_manage_access(self):
153        self.student.nationality = u'DE'
154        # Managers can access the pages of students
155        # and can perform actions
156        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
157        # The student created in the base package is an ug student
158        self.browser.open(self.student_path)
159        self.browser.getLink("Clearance Data").click()
160        self.assertEqual(self.browser.headers['Status'], '200 Ok')
161        self.assertEqual(self.browser.url, self.clearance_path)
162        self.browser.getLink("Manage").click()
163        self.assertEqual(self.browser.headers['Status'], '200 Ok')
164        self.assertEqual(self.browser.url, self.manage_clearance_path)
165        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
166        self.browser.getControl("Save").click()
167        self.assertMatches('...Form has been saved...',
168                           self.browser.contents)
169        self.assertMatches('...First Sitting Record...',
170                           self.browser.contents)
171        # Managers can open clearance slip of ug students
172        self.browser.open(self.student_path + '/clearance_slip.pdf')
173        self.assertEqual(self.browser.headers['Status'], '200 Ok')
174        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
175        # There is no pg field in the clearance form
176        self.assertFalse('Second Higher Education Record'
177            in self.browser.contents)
178        # Now we change the study mode ...
179        self.certificate.study_mode = 'pg_ft'
180        self.browser.open(self.clearance_path)
181        # ... and additional pg clearance fields appear
182        self.assertMatches('...Second Higher Education Record...',
183                           self.browser.contents)
184        # But also fields from the ug form are displayed
185        self.assertMatches('...First Sitting Record...',
186                           self.browser.contents)
187        # Managers can open clearance slip of pg students
188        self.browser.open(self.student_path + '/clearance_slip.pdf')
189        self.assertEqual(self.browser.headers['Status'], '200 Ok')
190        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
191        # Managers can edit personal data. No fields are required.
192        self.browser.open(self.manage_personal_path)
193        self.browser.getControl("Save").click()
194        self.assertMatches('...Form has been saved...',
195                           self.browser.contents)
196
197    def test_logging(self):
198        self.student.nationality = u'DE'
199        valid_subj = NigeriaKofaUtils().EXAM_SUBJECTS_DICT.keys()[0]
200        valid_grade = NigeriaKofaUtils().EXAM_GRADES[0][0]
201        result_entry = ResultEntry(valid_subj, valid_grade)
202        self.student.fst_sit_results = [result_entry,]
203        # Managers can access the pages of students
204        # and can perform actions
205        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
206        # The student created in the base package is an ug student
207        self.browser.open(self.student_path)
208        self.browser.getLink("Clearance Data").click()
209        self.browser.getLink("Manage").click()
210        self.browser.getControl("Save").click()
211        logfile = os.path.join(
212            self.app['datacenter'].storage, 'logs', 'students.log')
213        logcontent = open(logfile).read()
214        self.assertFalse('saved: fst_sit_results' in logcontent)
215
216    def test_student_access(self):
217        # Students can edit clearance data
218        IWorkflowState(self.student).setState('cleared')
219        self.browser.open(self.login_path)
220        self.browser.getControl(name="form.login").value = self.student_id
221        self.browser.getControl(name="form.password").value = 'spwd'
222        self.browser.getControl("Login").click()
223        # Even in state admitted students can't change the portait if
224        # application slip exists.
225        IWorkflowState(self.student).setState('admitted')
226        self.browser.open(self.student_path)
227        self.assertTrue('Change portrait' in self.browser.contents)
228        file_store = getUtility(IExtFileStore)
229        applicant_slip = 'My application slip'
230        file_id = IFileStoreNameChooser(self.student).chooseName(
231            attr="application_slip.pdf")
232        file_store.createFile(file_id, StringIO(applicant_slip))
233        self.browser.open(self.student_path)
234        self.assertFalse('Change portrait' in self.browser.contents)
235        self.browser.open(self.student_path + '/change_portrait')
236        self.assertTrue('The requested form is locked' in self.browser.contents)
237
238        # Student can view and edit clearance data if clearance has started ...
239        IWorkflowInfo(self.student).fireTransition('start_clearance')
240        self.student.officer_comment = u'Fill properly'
241        self.browser.getLink("Clearance Data").click()
242        self.assertTrue("Officer's Comment" in self.browser.contents)
243        # Students can't edit officer's comment
244        self.browser.getLink("Edit").click()
245        self.assertFalse("Officer's Comment" in self.browser.contents)
246        self.assertTrue('Save' in self.browser.contents)
247        # ... and request clearance if nationality field has been filled.
248        self.browser.getControl("Save and request clearance").click()
249        self.assertMatches('...Required input is missing...',
250                           self.browser.contents)
251        self.student.nationality = u'DE'
252        self.browser.open(self.edit_clearance_path)
253        self.browser.getControl("Save and request clearance").click()
254        self.assertMatches('...Clearance has been requested...',
255                           self.browser.contents)
256
257        # Students can edit personal data. Some fields are required.
258        self.browser.open(self.personal_path)
259        self.assertTrue('Updated' in self.browser.contents)
260        self.browser.getLink("Edit").click()
261        self.assertEqual(self.browser.headers['Status'], '200 Ok')
262        self.assertEqual(self.browser.url, self.edit_personal_path)
263        self.browser.getControl("Save").click()
264        self.assertMatches('...Required input is missing...',
265                           self.browser.contents)
266        self.browser.getControl(name="form.perm_address").value = 'My address!'
267        self.browser.getControl("Save").click()
268        self.assertMatches('...Required input is missing...',
269                           self.browser.contents)
270        # Ok, let's give up and fill the rest.
271        self.browser.getControl(name="form.next_kin_name").value = 'My Mutti'
272        self.browser.getControl(name="form.next_kin_relation").value = 'mother'
273        self.browser.getControl(name="form.next_kin_address").value = 'sweet home'
274        self.browser.getControl(name="form.next_kin_phone.country").value = ['+234']
275        self.browser.getControl(name="form.next_kin_phone.ext").value = '45678'
276        self.browser.getControl("Save").click()
277        self.assertMatches('...Form has been saved...',
278                           self.browser.contents)
279
280    def test_manage_upload_file(self):
281        # Managers can upload a file via the StudentClearanceManageFormPage
282        # The image is stored even if form has errors
283        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
284        self.browser.open(self.manage_clearance_path)
285        # Managers can add and delete a file
286        self.browser.open(self.manage_clearance_path)
287        image = open(SAMPLE_IMAGE, 'rb')
288        ctrl = self.browser.getControl(name='birthcertificateupload')
289        file_ctrl = ctrl.mech_control
290        file_ctrl.add_file(image, filename='my_acceptance_letter.jpg')
291        self.browser.getControl(
292            name='upload_acceptanceletterupload').click()
293        # Uups, we used the wrong 'Browse' field
294        self.assertFalse(
295            '<a target="image" href="acc_let">'
296            in self.browser.contents)
297        ctrl = self.browser.getControl(name='acceptanceletterupload')
298        file_ctrl = ctrl.mech_control
299        image.seek(0)
300        file_ctrl.add_file(image, filename='my_acceptance_letter.jpg')
301        self.browser.getControl(
302            name='upload_acceptanceletterupload').click()
303        self.assertTrue(
304            'http://localhost/app/students/K1000000/acc_let'
305            in self.browser.contents)
306        self.browser.getControl(
307            name='delete_acceptanceletterupload').click()
308        self.assertTrue(
309            'acc_let deleted'
310            in self.browser.contents)
311
312    def test_student_expired_personal_data(self):
313        # Login
314        IWorkflowState(self.student).setState('school fee paid')
315        delta = timedelta(days=180)
316        self.student.personal_updated = datetime.utcnow() - delta
317        self.browser.open(self.login_path)
318        self.browser.getControl(name="form.login").value = self.student_id
319        self.browser.getControl(name="form.password").value = 'spwd'
320        self.browser.getControl("Login").click()
321        self.assertEqual(self.browser.url, self.student_path)
322        self.assertTrue(
323            'You logged in' in self.browser.contents)
324        # Students don't see personal_updated field in edit form
325        self.browser.open(self.edit_personal_path)
326        self.assertFalse('Updated' in self.browser.contents)
327        self.browser.open(self.personal_path)
328        self.assertTrue('Updated' in self.browser.contents)
329        self.browser.getLink("Logout").click()
330        delta = timedelta(days=181)
331        self.student.personal_updated = datetime.utcnow() - delta
332        self.browser.open(self.login_path)
333        self.browser.getControl(name="form.login").value = self.student_id
334        self.browser.getControl(name="form.password").value = 'spwd'
335        self.browser.getControl("Login").click()
336        self.assertEqual(self.browser.url, self.edit_personal_path)
337        self.assertTrue(
338            'Your personal data record is outdated.' in self.browser.contents)
339
340    def test_lga_nationality(self):
341        self.student.nationality = u'DE'
342        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
343        self.browser.open(self.manage_clearance_path)
344        self.browser.getControl(name="form.lga").value = ['abia_aba_north']
345        self.browser.getControl("Save").click()
346        self.assertTrue(
347            'Nationalty and LGA are contradictory.' in self.browser.contents)
348        self.browser.getControl(name="form.nationality").value = ['NG']
349        self.browser.getControl("Save").click()
350        self.assertTrue(
351            'Form has been saved' in self.browser.contents)
352
353    def test_financial_clearance(self):
354        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
355        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
356        self.app['users']['mrbursary'].title = u'Carlo Pitter'
357        # Clearance officers needs to get
358        # the StudentsOfficer site role
359        prmglobal = IPrincipalRoleManager(self.app)
360        prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrbursary')
361        # Assign BursaryOfficer role
362        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
363        # and FinancialClearanceOfficer role
364        prmglobal.assignRoleToPrincipal(
365            'waeup.FinancialClearanceOfficer', 'mrbursary')
366        # Login
367        self.browser.open(self.login_path)
368        self.browser.getControl(name="form.login").value = 'mrbursary'
369        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
370        self.browser.getControl("Login").click()
371        # BO can see his roles
372        self.browser.getLink("My Roles").click()
373        self.assertMatches(
374            '...<div>Bursary Officer</div>...',
375            self.browser.contents)
376        # BO can view student record
377        self.browser.open(self.student_path)
378        # BO can see clearance button ...
379        self.assertTrue(
380            'Clear student financially' in self.browser.contents)
381        # ... but not withdraw button
382        self.assertFalse(
383            'Withdraw financial clearance' in self.browser.contents)
384        # BO can clear student
385        self.browser.getLink("Clear student financially").click()
386        self.assertTrue(
387            'Student has been financially cleared' in self.browser.contents)
388        # Name of BO and date have been stored
389        self.assertEqual(self.student.financially_cleared_by, 'Carlo Pitter')
390        self.assertMatches(
391            '<YYYY-MM-DD hh:mm:ss>',
392            self.student.financial_clearance_date.strftime(
393                "%Y-%m-%d %H:%M:%S"))
394        # BO can't see clearance button ...
395        self.assertFalse(
396            'Clear student financially' in self.browser.contents)
397        # ... but withdraw button and can withdraw clearance
398        self.browser.getLink("Withdraw financial clearance").click()
399        self.assertTrue(
400            'Financial clearance withdrawn' in self.browser.contents)
401        # Name of BO and date have been deleted
402        self.assertEqual(self.student.financially_cleared_by, None)
403        self.assertEqual(self.student.financial_clearance_date, None)
404        # Clearance is logged
405        logfile = os.path.join(
406            self.app['datacenter'].storage, 'logs', 'students.log')
407        logcontent = open(logfile).read()
408        self.assertTrue(
409            'mrbursary - kofacustom.nigeria.students.browser.ClearStudentFinancially'
410            ' - K1000000 - financially cleared' in logcontent)
411        self.assertTrue(
412            'mrbursary - kofacustom.nigeria.students.browser.WithdrawFinancialClearance'
413            ' - K1000000 - financial clearance withdrawn' in logcontent)
414        # Clearance is also stored in the history
415        self.browser.open(self.history_path)
416        self.assertMatches(
417            '...2016-01-16 15:50:48 WAT - Financially cleared by Carlo Pitter...',
418            self.browser.contents)
419        self.assertMatches(
420            '...2016-01-16 15:50:48 WAT - Financial clearance withdrawn by Carlo Pitter...',
421            self.browser.contents)
422
423    def test_fiancial_clearance_pdf_slip(self):
424        payment1 = createObject(u'waeup.StudentOnlinePayment')
425        timestamp = ("%d" % int(time()*10000))[1:]
426        payment1.p_id = "LSCNEW-2-4153206270" # the longest possible p_id
427        payment1.p_category = 'schoolfee'
428        payment1.p_item = u'My School Fee'
429        payment1.p_session = 2015
430        payment1.p_level = 100
431        payment1.p_current = True
432        payment1.amount_auth = 23456.9
433        payment1.approve()
434        payment2 = createObject(u'waeup.StudentOnlinePayment')
435        timestamp = ("%d" % int(time()*10000))[1:]
436        payment2.p_id = "p%s" % timestamp
437        payment2.p_category = 'clearance'
438        payment2.p_item = u'My Clearance Fee'
439        payment2.p_session = 2015
440        payment2.p_level = 100
441        payment2.p_current = True
442        payment2.amount_auth = 5678.6
443        payment2.approve()
444        self.student['payments'][payment1.p_id] = payment1
445        self.student['payments'][payment2.p_id] = payment2
446        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
447        self.browser.open(self.student_path + '/clear_financially')
448        self.browser.getLink("Download fee payment history").click()
449        self.assertEqual(self.browser.headers['Status'], '200 Ok')
450        self.assertEqual(self.browser.headers['Content-Type'],
451                         'application/pdf')
452        path = os.path.join(samples_dir(), 'fee_payment_history.pdf')
453        open(path, 'wb').write(self.browser.contents)
454        print "Sample PDF fee_payment_history.pdf written to %s" % path
455        return
Note: See TracBrowser for help on using the repository browser.