source: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py @ 10944

Last change on this file since 10944 was 10704, checked in by Henrik Bettermann, 11 years ago

Set date_of_birth.

  • Property svn:keywords set to Id
File size: 179.5 KB
Line 
1## $Id: test_browser.py 10704 2013-11-06 08:17:23Z 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##
18"""
19Test the student-related UI components.
20"""
21import shutil
22import tempfile
23import pytz
24import base64
25from datetime import datetime, timedelta, date
26from StringIO import StringIO
27import os
28import grok
29from zc.async.testing import wait_for_result
30from zope.event import notify
31from zope.component import createObject, queryUtility, getUtility
32from zope.component.hooks import setSite, clearSite
33from zope.catalog.interfaces import ICatalog
34from zope.security.interfaces import Unauthorized
35from zope.securitypolicy.interfaces import IPrincipalRoleManager
36from zope.testbrowser.testing import Browser
37from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
38from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
39from waeup.kofa.app import University
40from waeup.kofa.payments.interfaces import IPayer
41from waeup.kofa.students.interfaces import IStudentsUtils
42from waeup.kofa.students.student import Student
43from waeup.kofa.students.studylevel import StudentStudyLevel
44from waeup.kofa.university.faculty import Faculty
45from waeup.kofa.university.department import Department
46from waeup.kofa.interfaces import IUserAccount, IJobManager
47from waeup.kofa.authentication import LocalRoleSetEvent
48from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
49from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
50from waeup.kofa.browser.tests.test_pdf import samples_dir
51
52PH_LEN = 15911  # Length of placeholder file
53
54SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
55SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
56
57def lookup_submit_value(name, value, browser):
58    """Find a button with a certain value."""
59    for num in range(0, 100):
60        try:
61            button = browser.getControl(name=name, index=num)
62            if button.value.endswith(value):
63                return button
64        except IndexError:
65            break
66    return None
67
68class StudentsFullSetup(FunctionalTestCase):
69    # A test case that only contains a setup and teardown
70    #
71    # Complete setup for students handlings is rather complex and
72    # requires lots of things created before we can start. This is a
73    # setup that does all this, creates a university, creates PINs,
74    # etc.  so that we do not have to bother with that in different
75    # test cases.
76
77    layer = FunctionalLayer
78
79    def setUp(self):
80        super(StudentsFullSetup, self).setUp()
81
82        # Setup a sample site for each test
83        app = University()
84        self.dc_root = tempfile.mkdtemp()
85        app['datacenter'].setStoragePath(self.dc_root)
86
87        # Prepopulate the ZODB...
88        self.getRootFolder()['app'] = app
89        # we add the site immediately after creation to the
90        # ZODB. Catalogs and other local utilities are not setup
91        # before that step.
92        self.app = self.getRootFolder()['app']
93        # Set site here. Some of the following setup code might need
94        # to access grok.getSite() and should get our new app then
95        setSite(app)
96
97        # Add student with subobjects
98        student = createObject('waeup.Student')
99        student.firstname = u'Anna'
100        student.lastname = u'Tester'
101        student.reg_number = u'123'
102        student.matric_number = u'234'
103        student.sex = u'm'
104        student.email = 'aa@aa.ng'
105        student.phone = u'1234'
106        student.date_of_birth = date(1981, 2, 4)
107        self.app['students'].addStudent(student)
108        self.student_id = student.student_id
109        self.student = self.app['students'][self.student_id]
110
111        # Set password
112        IUserAccount(
113            self.app['students'][self.student_id]).setPassword('spwd')
114
115        self.login_path = 'http://localhost/app/login'
116        self.container_path = 'http://localhost/app/students'
117        self.manage_container_path = self.container_path + '/@@manage'
118        self.add_student_path = self.container_path + '/addstudent'
119        self.student_path = self.container_path + '/' + self.student_id
120        self.manage_student_path = self.student_path + '/manage_base'
121        self.trigtrans_path = self.student_path + '/trigtrans'
122        self.clearance_path = self.student_path + '/view_clearance'
123        self.personal_path = self.student_path + '/view_personal'
124        self.edit_clearance_path = self.student_path + '/cedit'
125        self.manage_clearance_path = self.student_path + '/manage_clearance'
126        self.edit_personal_path = self.student_path + '/edit_personal'
127        self.manage_personal_path = self.student_path + '/manage_personal'
128        self.studycourse_path = self.student_path + '/studycourse'
129        self.payments_path = self.student_path + '/payments'
130        self.acco_path = self.student_path + '/accommodation'
131        self.history_path = self.student_path + '/history'
132
133        # Create 5 access codes with prefix'PWD'
134        pin_container = self.app['accesscodes']
135        pin_container.createBatch(
136            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
137        pins = pin_container['PWD-1'].values()
138        self.pwdpins = [x.representation for x in pins]
139        self.existing_pwdpin = self.pwdpins[0]
140        parts = self.existing_pwdpin.split('-')[1:]
141        self.existing_pwdseries, self.existing_pwdnumber = parts
142        # Create 5 access codes with prefix 'CLR'
143        pin_container.createBatch(
144            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
145        pins = pin_container['CLR-1'].values()
146        pins[0].owner = u'Hans Wurst'
147        self.existing_clrac = pins[0]
148        self.existing_clrpin = pins[0].representation
149        parts = self.existing_clrpin.split('-')[1:]
150        self.existing_clrseries, self.existing_clrnumber = parts
151        # Create 2 access codes with prefix 'HOS'
152        pin_container.createBatch(
153            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
154        pins = pin_container['HOS-1'].values()
155        self.existing_hosac = pins[0]
156        self.existing_hospin = pins[0].representation
157        parts = self.existing_hospin.split('-')[1:]
158        self.existing_hosseries, self.existing_hosnumber = parts
159
160        # Populate university
161        self.certificate = createObject('waeup.Certificate')
162        self.certificate.code = u'CERT1'
163        self.certificate.application_category = 'basic'
164        self.certificate.study_mode = 'ug_ft'
165        self.certificate.start_level = 100
166        self.certificate.end_level = 500
167        self.certificate.school_fee_1 = 40000.0
168        self.certificate.school_fee_2 = 20000.0
169        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
170        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
171        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
172            self.certificate)
173        self.course = createObject('waeup.Course')
174        self.course.code = 'COURSE1'
175        self.course.semester = 1
176        self.course.credits = 10
177        self.course.passmark = 40
178        self.app['faculties']['fac1']['dep1'].courses.addCourse(
179            self.course)
180        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
181            self.course, level=100)
182
183        # Configure university and hostels
184        self.app['hostels'].accommodation_states = ['admitted']
185        self.app['hostels'].accommodation_session = 2004
186        delta = timedelta(days=10)
187        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
188        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
189        self.app['configuration'].carry_over = True
190        configuration = createObject('waeup.SessionConfiguration')
191        configuration.academic_session = 2004
192        configuration.clearance_fee = 3456.0
193        configuration.transcript_fee = 4567.0
194        configuration.booking_fee = 123.4
195        configuration.maint_fee = 987.0
196        self.app['configuration'].addSessionConfiguration(configuration)
197
198        # Create a hostel with two beds
199        hostel = Hostel()
200        hostel.hostel_id = u'hall-1'
201        hostel.hostel_name = u'Hall 1'
202        hostel.maint_fee = 876.0
203        self.app['hostels'].addHostel(hostel)
204        bed = Bed()
205        bed.bed_id = u'hall-1_A_101_A'
206        bed.bed_number = 1
207        bed.owner = NOT_OCCUPIED
208        bed.bed_type = u'regular_male_fr'
209        self.app['hostels'][hostel.hostel_id].addBed(bed)
210        bed = Bed()
211        bed.bed_id = u'hall-1_A_101_B'
212        bed.bed_number = 2
213        bed.owner = NOT_OCCUPIED
214        bed.bed_type = u'regular_female_fr'
215        self.app['hostels'][hostel.hostel_id].addBed(bed)
216
217        # Set study course attributes of test student
218        self.student['studycourse'].certificate = self.certificate
219        self.student['studycourse'].current_session = 2004
220        self.student['studycourse'].entry_session = 2004
221        self.student['studycourse'].current_verdict = 'A'
222        self.student['studycourse'].current_level = 100
223        # Update the catalog
224        notify(grok.ObjectModifiedEvent(self.student))
225
226        # Put the prepopulated site into test ZODB and prepare test
227        # browser
228        self.browser = Browser()
229        self.browser.handleErrors = False
230
231    def tearDown(self):
232        super(StudentsFullSetup, self).tearDown()
233        clearSite()
234        shutil.rmtree(self.dc_root)
235
236
237
238class StudentsContainerUITests(StudentsFullSetup):
239    # Tests for StudentsContainer class views and pages
240
241    layer = FunctionalLayer
242
243    def test_anonymous_access(self):
244        # Anonymous users can't access students containers
245        self.assertRaises(
246            Unauthorized, self.browser.open, self.container_path)
247        self.assertRaises(
248            Unauthorized, self.browser.open, self.manage_container_path)
249        return
250
251    def test_manage_access(self):
252        # Managers can access the view page of students
253        # containers and can perform actions
254        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
255        self.browser.open(self.container_path)
256        self.assertEqual(self.browser.headers['Status'], '200 Ok')
257        self.assertEqual(self.browser.url, self.container_path)
258        self.browser.getLink("Manage student section").click()
259        self.assertEqual(self.browser.headers['Status'], '200 Ok')
260        self.assertEqual(self.browser.url, self.manage_container_path)
261        return
262
263    def test_add_search_delete_students(self):
264        # Managers can add search and remove students
265        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
266        self.browser.open(self.manage_container_path)
267        self.browser.getLink("Add student").click()
268        self.assertEqual(self.browser.headers['Status'], '200 Ok')
269        self.assertEqual(self.browser.url, self.add_student_path)
270        self.browser.getControl(name="form.firstname").value = 'Bob'
271        self.browser.getControl(name="form.lastname").value = 'Tester'
272        self.browser.getControl(name="form.reg_number").value = '123'
273        self.browser.getControl("Create student record").click()
274        self.assertTrue('Registration number exists already'
275            in self.browser.contents)
276        self.browser.getControl(name="form.reg_number").value = '1234'
277        self.browser.getControl("Create student record").click()
278        self.assertTrue('Student record created' in self.browser.contents)
279
280        # Registration and matric numbers must be unique
281        self.browser.getLink("Manage").click()
282        self.browser.getControl(name="form.reg_number").value = '123'
283        self.browser.getControl("Save").click()
284        self.assertMatches('...Registration number exists...',
285                           self.browser.contents)
286        self.browser.getControl(name="form.reg_number").value = '789'
287        self.browser.getControl(name="form.matric_number").value = '234'
288        self.browser.getControl("Save").click()
289        self.assertMatches('...Matriculation number exists...',
290                           self.browser.contents)
291
292        # We can find a student with a certain student_id
293        self.browser.open(self.container_path)
294        self.browser.getControl("Find student(s)").click()
295        self.assertTrue('Empty search string' in self.browser.contents)
296        self.browser.getControl(name="searchtype").value = ['student_id']
297        self.browser.getControl(name="searchterm").value = self.student_id
298        self.browser.getControl("Find student(s)").click()
299        self.assertTrue('Anna Tester' in self.browser.contents)
300
301        # We can find a student in a certain session
302        self.browser.open(self.container_path)
303        self.browser.getControl(name="searchtype").value = ['current_session']
304        self.browser.getControl(name="searchterm").value = '2004'
305        self.browser.getControl("Find student(s)").click()
306        self.assertTrue('Anna Tester' in self.browser.contents)
307        # Session fileds require integer values
308        self.browser.open(self.container_path)
309        self.browser.getControl(name="searchtype").value = ['current_session']
310        self.browser.getControl(name="searchterm").value = '2004/2005'
311        self.browser.getControl("Find student(s)").click()
312        self.assertTrue('Only year dates allowed' in self.browser.contents)
313        self.browser.open(self.manage_container_path)
314        self.browser.getControl(name="searchtype").value = ['current_session']
315        self.browser.getControl(name="searchterm").value = '2004/2005'
316        self.browser.getControl("Find student(s)").click()
317        self.assertTrue('Only year dates allowed' in self.browser.contents)
318
319        # We can find a student in a certain study_mode
320        self.browser.open(self.container_path)
321        self.browser.getControl(name="searchtype").value = ['current_mode']
322        self.browser.getControl(name="searchterm").value = 'ug_ft'
323        self.browser.getControl("Find student(s)").click()
324        self.assertTrue('Anna Tester' in self.browser.contents)
325
326        # We can find a student in a certain department
327        self.browser.open(self.container_path)
328        self.browser.getControl(name="searchtype").value = ['depcode']
329        self.browser.getControl(name="searchterm").value = 'dep1'
330        self.browser.getControl("Find student(s)").click()
331        self.assertTrue('Anna Tester' in self.browser.contents)
332
333        # We can find a student by searching for all kind of name parts
334        self.browser.open(self.manage_container_path)
335        self.browser.getControl("Find student(s)").click()
336        self.assertTrue('Empty search string' in self.browser.contents)
337        self.browser.getControl(name="searchtype").value = ['fullname']
338        self.browser.getControl(name="searchterm").value = 'Anna Tester'
339        self.browser.getControl("Find student(s)").click()
340        self.assertTrue('Anna Tester' in self.browser.contents)
341        self.browser.open(self.manage_container_path)
342        self.browser.getControl(name="searchtype").value = ['fullname']
343        self.browser.getControl(name="searchterm").value = 'Anna'
344        self.browser.getControl("Find student(s)").click()
345        self.assertTrue('Anna Tester' in self.browser.contents)
346        self.browser.open(self.manage_container_path)
347        self.browser.getControl(name="searchtype").value = ['fullname']
348        self.browser.getControl(name="searchterm").value = 'Tester'
349        self.browser.getControl("Find student(s)").click()
350        self.assertTrue('Anna Tester' in self.browser.contents)
351        self.browser.open(self.manage_container_path)
352        self.browser.getControl(name="searchtype").value = ['fullname']
353        self.browser.getControl(name="searchterm").value = 'An'
354        self.browser.getControl("Find student(s)").click()
355        self.assertFalse('Anna Tester' in self.browser.contents)
356        self.browser.open(self.manage_container_path)
357        self.browser.getControl(name="searchtype").value = ['fullname']
358        self.browser.getControl(name="searchterm").value = 'An*'
359        self.browser.getControl("Find student(s)").click()
360        self.assertTrue('Anna Tester' in self.browser.contents)
361        self.browser.open(self.manage_container_path)
362        self.browser.getControl(name="searchtype").value = ['fullname']
363        self.browser.getControl(name="searchterm").value = 'tester'
364        self.browser.getControl("Find student(s)").click()
365        self.assertTrue('Anna Tester' in self.browser.contents)
366        self.browser.open(self.manage_container_path)
367        self.browser.getControl(name="searchtype").value = ['fullname']
368        self.browser.getControl(name="searchterm").value = 'Tester Ana'
369        self.browser.getControl("Find student(s)").click()
370        self.assertFalse('Anna Tester' in self.browser.contents)
371        self.browser.open(self.manage_container_path)
372        self.browser.getControl(name="searchtype").value = ['fullname']
373        self.browser.getControl(name="searchterm").value = 'Tester Anna'
374        self.browser.getControl("Find student(s)").click()
375        self.assertTrue('Anna Tester' in self.browser.contents)
376        # The old searchterm will be used again
377        self.browser.getControl("Find student(s)").click()
378        self.assertTrue('Anna Tester' in self.browser.contents)
379
380        # We can find suspended students
381        self.student.suspended = True
382        notify(grok.ObjectModifiedEvent(self.student))
383        self.browser.open(self.manage_container_path)
384        self.browser.getControl(name="searchtype").value = ['suspended']
385        self.browser.getControl("Find student(s)").click()
386        self.assertTrue('Anna Tester' in self.browser.contents)
387        self.browser.open(self.container_path)
388        self.browser.getControl(name="searchtype").value = ['suspended']
389        self.browser.getControl("Find student(s)").click()
390        self.assertTrue('Anna Tester' in self.browser.contents)
391
392        # The catalog is informed when studycourse objects have been
393        # edited
394        self.browser.open(self.studycourse_path + '/manage')
395        self.browser.getControl(name="form.current_session").value = ['2010']
396        self.browser.getControl(name="form.entry_session").value = ['2010']
397        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
398        self.browser.getControl("Save").click()
399
400        # We can find the student in the new session
401        self.browser.open(self.manage_container_path)
402        self.browser.getControl(name="searchtype").value = ['current_session']
403        self.browser.getControl(name="searchterm").value = '2010'
404        self.browser.getControl("Find student(s)").click()
405        self.assertTrue('Anna Tester' in self.browser.contents)
406
407        ctrl = self.browser.getControl(name='entries')
408        ctrl.getControl(value=self.student_id).selected = True
409        self.browser.getControl("Remove selected", index=0).click()
410        self.assertTrue('Successfully removed' in self.browser.contents)
411        self.browser.getControl(name="searchtype").value = ['student_id']
412        self.browser.getControl(name="searchterm").value = self.student_id
413        self.browser.getControl("Find student(s)").click()
414        self.assertTrue('No student found' in self.browser.contents)
415
416        self.browser.open(self.container_path)
417        self.browser.getControl(name="searchtype").value = ['student_id']
418        self.browser.getControl(name="searchterm").value = self.student_id
419        self.browser.getControl("Find student(s)").click()
420        self.assertTrue('No student found' in self.browser.contents)
421        return
422
423class OfficerUITests(StudentsFullSetup):
424    # Tests for Student class views and pages
425
426    def test_student_properties(self):
427        self.student['studycourse'].current_level = 100
428        self.assertEqual(self.student.current_level, 100)
429        self.student['studycourse'].current_session = 2011
430        self.assertEqual(self.student.current_session, 2011)
431        self.student['studycourse'].current_verdict = 'A'
432        self.assertEqual(self.student.current_verdict, 'A')
433        return
434
435    def test_studylevelmanagepage(self):
436        studylevel = StudentStudyLevel()
437        studylevel.level = 100
438        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
439        self.student['studycourse'].addStudentStudyLevel(
440            cert,studylevel)
441        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
442        self.browser.open(self.studycourse_path + '/100/manage')
443        self.assertEqual(self.browser.url, self.studycourse_path + '/100/manage')
444        self.assertEqual(self.browser.headers['Status'], '200 Ok')
445
446    def test_basic_auth(self):
447        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
448        self.browser.open('http://localhost/app')
449        self.browser.getLink("Logout").click()
450        self.assertTrue('You have been logged out' in self.browser.contents)
451        # But we are still logged in since we've used basic authentication here.
452        # Wikipedia says: Existing browsers retain authentication information
453        # until the tab or browser is closed or the user clears the history.
454        # HTTP does not provide a method for a server to direct clients to
455        # discard these cached credentials. This means that there is no
456        # effective way for a server to "log out" the user without closing
457        # the browser. This is a significant defect that requires browser
458        # manufacturers to support a "logout" user interface element ...
459        self.assertTrue('Manager' in self.browser.contents)
460
461    def test_basic_auth_base64(self):
462        auth_token = base64.b64encode('mgr:mgrpw')
463        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
464        self.browser.open(self.manage_container_path)
465        self.assertEqual(self.browser.headers['Status'], '200 Ok')
466
467    def test_manage_access(self):
468        # Managers can access the pages of students
469        # and can perform actions
470        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
471        self.browser.open(self.student_path)
472        self.assertEqual(self.browser.headers['Status'], '200 Ok')
473        self.assertEqual(self.browser.url, self.student_path)
474        self.browser.getLink("Trigger").click()
475        self.assertEqual(self.browser.headers['Status'], '200 Ok')
476        # Managers can trigger transitions
477        self.browser.getControl(name="transition").value = ['admit']
478        self.browser.getControl("Save").click()
479        # Managers can edit base
480        self.browser.open(self.student_path)
481        self.browser.getLink("Manage").click()
482        self.assertEqual(self.browser.url, self.manage_student_path)
483        self.assertEqual(self.browser.headers['Status'], '200 Ok')
484        self.browser.getControl(name="form.firstname").value = 'John'
485        self.browser.getControl(name="form.lastname").value = 'Tester'
486        self.browser.getControl(name="form.reg_number").value = '345'
487        self.browser.getControl(name="password").value = 'secret'
488        self.browser.getControl(name="control_password").value = 'secret'
489        self.browser.getControl("Save").click()
490        self.assertMatches('...Form has been saved...',
491                           self.browser.contents)
492        self.browser.open(self.student_path)
493        self.browser.getLink("Clearance Data").click()
494        self.assertEqual(self.browser.headers['Status'], '200 Ok')
495        self.assertEqual(self.browser.url, self.clearance_path)
496        self.browser.getLink("Manage").click()
497        self.assertEqual(self.browser.headers['Status'], '200 Ok')
498        self.assertEqual(self.browser.url, self.manage_clearance_path)
499        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
500        self.browser.getControl("Save").click()
501        self.assertMatches('...Form has been saved...',
502                           self.browser.contents)
503
504        self.browser.open(self.student_path)
505        self.browser.getLink("Personal Data").click()
506        self.assertEqual(self.browser.headers['Status'], '200 Ok')
507        self.assertEqual(self.browser.url, self.personal_path)
508        self.browser.getLink("Manage").click()
509        self.assertEqual(self.browser.headers['Status'], '200 Ok')
510        self.assertEqual(self.browser.url, self.manage_personal_path)
511        self.browser.open(self.personal_path)
512        self.assertTrue('Updated' in self.browser.contents)
513        self.browser.getLink("Edit").click()
514        self.assertEqual(self.browser.headers['Status'], '200 Ok')
515        self.assertEqual(self.browser.url, self.edit_personal_path)
516        self.browser.getControl("Save").click()
517        # perm_address is required in IStudentPersonalEdit
518        self.assertMatches('...Required input is missing...',
519                           self.browser.contents)
520        self.browser.getControl(name="form.perm_address").value = 'My address!'
521        self.browser.getControl("Save").click()
522        self.assertMatches('...Form has been saved...',
523                           self.browser.contents)
524
525        # Managers can browse all subobjects
526        self.browser.open(self.student_path)
527        self.browser.getLink("Payments").click()
528        self.assertEqual(self.browser.headers['Status'], '200 Ok')
529        self.assertEqual(self.browser.url, self.payments_path)
530        self.browser.open(self.student_path)
531        self.browser.getLink("Accommodation").click()
532        self.assertEqual(self.browser.headers['Status'], '200 Ok')
533        self.assertEqual(self.browser.url, self.acco_path)
534        self.browser.open(self.student_path)
535        self.browser.getLink("History").click()
536        self.assertEqual(self.browser.headers['Status'], '200 Ok')
537        self.assertEqual(self.browser.url, self.history_path)
538        self.assertMatches('...Admitted by Manager...',
539                           self.browser.contents)
540        # Only the Application Slip does not exist
541        self.assertFalse('Application Slip' in self.browser.contents)
542        return
543
544    def test_manage_contact_student(self):
545        # Managers can contact student
546        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
547        self.student.email = None
548        self.browser.open(self.student_path)
549        self.browser.getLink("Send email").click()
550        self.browser.getControl(name="form.subject").value = 'Important subject'
551        self.browser.getControl(name="form.body").value = 'Hello!'
552        self.browser.getControl("Send message now").click()
553        self.assertTrue('An smtp server error occurred' in self.browser.contents)
554        self.student.email = 'xx@yy.zz'
555        self.browser.getControl("Send message now").click()
556        self.assertTrue('Your message has been sent' in self.browser.contents)
557        return
558
559    def test_manage_remove_department(self):
560        # Lazy student is studying CERT1
561        lazystudent = Student()
562        lazystudent.firstname = u'Lazy'
563        lazystudent.lastname = u'Student'
564        self.app['students'].addStudent(lazystudent)
565        student_id = lazystudent.student_id
566        student_path = self.container_path + '/' + student_id
567        lazystudent['studycourse'].certificate = self.certificate
568        notify(grok.ObjectModifiedEvent(lazystudent))
569        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
570        self.browser.open(student_path + '/studycourse')
571        self.assertTrue('CERT1' in self.browser.contents)
572        # After some years the department is removed
573        del self.app['faculties']['fac1']['dep1']
574        # So CERT1 does no longer exist and lazy student's
575        # certificate reference is removed too
576        self.browser.open(student_path + '/studycourse')
577        self.assertEqual(self.browser.headers['Status'], '200 Ok')
578        self.assertEqual(self.browser.url, student_path + '/studycourse')
579        self.assertFalse('CERT1' in self.browser.contents)
580        self.assertMatches('...<div>--</div>...',
581                           self.browser.contents)
582
583    def test_manage_upload_file(self):
584        # Managers can upload a file via the StudentClearanceManageFormPage
585        # The image is stored even if form has errors
586        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
587        self.browser.open(self.manage_clearance_path)
588        # No birth certificate has been uploaded yet
589        # Browsing the link shows a placerholder image
590        self.browser.open('birth_certificate')
591        self.assertEqual(
592            self.browser.headers['content-type'], 'image/jpeg')
593        self.assertEqual(len(self.browser.contents), PH_LEN)
594        # Create a pseudo image file and select it to be uploaded in form
595        # as birth certificate
596        self.browser.open(self.manage_clearance_path)
597        image = open(SAMPLE_IMAGE, 'rb')
598        ctrl = self.browser.getControl(name='birthcertificateupload')
599        file_ctrl = ctrl.mech_control
600        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
601        # The Save action does not upload files
602        self.browser.getControl("Save").click() # submit form
603        self.assertFalse(
604            '<a target="image" href="birth_certificate">'
605            in self.browser.contents)
606        # ... but the correct upload submit button does
607        image = open(SAMPLE_IMAGE)
608        ctrl = self.browser.getControl(name='birthcertificateupload')
609        file_ctrl = ctrl.mech_control
610        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
611        self.browser.getControl(
612            name='upload_birthcertificateupload').click()
613        # There is a correct <img> link included
614        self.assertTrue(
615            '<a target="image" href="birth_certificate">'
616            in self.browser.contents)
617        # Browsing the link shows a real image
618        self.browser.open('birth_certificate')
619        self.assertEqual(
620            self.browser.headers['content-type'], 'image/jpeg')
621        self.assertEqual(len(self.browser.contents), 2787)
622        # Reuploading a file which is bigger than 150k will raise an error
623        self.browser.open(self.manage_clearance_path)
624        # An image > 150K
625        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 75)
626        ctrl = self.browser.getControl(name='birthcertificateupload')
627        file_ctrl = ctrl.mech_control
628        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
629        self.browser.getControl(
630            name='upload_birthcertificateupload').click()
631        self.assertTrue(
632            'Uploaded file is too big' in self.browser.contents)
633        # we do not rely on filename extensions given by uploaders
634        image = open(SAMPLE_IMAGE, 'rb') # a jpg-file
635        ctrl = self.browser.getControl(name='birthcertificateupload')
636        file_ctrl = ctrl.mech_control
637        # tell uploaded file is bmp
638        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
639        self.browser.getControl(
640            name='upload_birthcertificateupload').click()
641        self.assertTrue(
642            # jpg file was recognized
643            'File birth_certificate.jpg uploaded.' in self.browser.contents)
644        # File names must meet several conditions
645        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
646        ctrl = self.browser.getControl(name='birthcertificateupload')
647        file_ctrl = ctrl.mech_control
648        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
649        self.browser.getControl(
650            name='upload_birthcertificateupload').click()
651        self.assertTrue('Only the following extensions are allowed'
652            in self.browser.contents)
653        # Managers can delete files
654        self.browser.getControl(name='delete_birthcertificateupload').click()
655        self.assertTrue(
656            'birth_certificate deleted' in self.browser.contents)
657
658        # Managers can upload a file via the StudentBaseManageFormPage
659        self.browser.open(self.manage_student_path)
660        image = open(SAMPLE_IMAGE_BMP, 'rb')
661        ctrl = self.browser.getControl(name='passportuploadmanage')
662        file_ctrl = ctrl.mech_control
663        file_ctrl.add_file(image, filename='my_photo.bmp')
664        self.browser.getControl(
665            name='upload_passportuploadmanage').click()
666        self.assertTrue('jpg file extension expected'
667            in self.browser.contents)
668        ctrl = self.browser.getControl(name='passportuploadmanage')
669        file_ctrl = ctrl.mech_control
670        image = open(SAMPLE_IMAGE, 'rb')
671        file_ctrl.add_file(image, filename='my_photo.jpg')
672        self.browser.getControl(
673            name='upload_passportuploadmanage').click()
674        self.assertTrue(
675            '<img align="middle" height="125px" src="passport.jpg" />'
676            in self.browser.contents)
677        # We remove the passport file again
678        self.browser.open(self.manage_student_path)
679        self.browser.getControl('Delete').click()
680        self.browser.open(self.student_path + '/clearance_slip.pdf')
681        self.assertEqual(self.browser.headers['Status'], '200 Ok')
682        self.assertEqual(self.browser.headers['Content-Type'],
683                         'application/pdf')
684
685    def test_manage_course_lists(self):
686        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
687        self.browser.open(self.student_path)
688        self.browser.getLink("Study Course").click()
689        self.assertEqual(self.browser.headers['Status'], '200 Ok')
690        self.assertEqual(self.browser.url, self.studycourse_path)
691        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
692        self.browser.getLink("Manage").click()
693        self.assertTrue('Manage study course' in self.browser.contents)
694        # Before we can select a level, the certificate must
695        # be selected and saved
696        self.browser.getControl(name="form.certificate").value = ['CERT1']
697        self.browser.getControl(name="form.current_session").value = ['2004']
698        self.browser.getControl(name="form.current_verdict").value = ['A']
699        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
700        self.browser.getControl("Save").click()
701        # Now we can save also the current level which depends on start and end
702        # level of the certificate
703        self.browser.getControl(name="form.current_level").value = ['100']
704        self.browser.getControl("Save").click()
705        # Managers can add and remove any study level (course list)
706        self.browser.getControl(name="addlevel").value = ['100']
707        self.browser.getControl("Add study level").click()
708        self.assertMatches(
709            '...You must select a session...', self.browser.contents)
710        self.browser.getControl(name="addlevel").value = ['100']
711        self.browser.getControl(name="level_session").value = ['2004']
712        self.browser.getControl("Add study level").click()
713        self.assertMatches('...<span>100</span>...', self.browser.contents)
714        self.assertEqual(self.student['studycourse']['100'].level, 100)
715        self.assertEqual(self.student['studycourse']['100'].level_session, 2004)
716        self.browser.getControl(name="addlevel").value = ['100']
717        self.browser.getControl(name="level_session").value = ['2004']
718        self.browser.getControl("Add study level").click()
719        self.assertMatches('...This level exists...', self.browser.contents)
720        self.browser.getControl("Remove selected").click()
721        self.assertMatches(
722            '...No study level selected...', self.browser.contents)
723        self.browser.getControl(name="val_id").value = ['100']
724        self.browser.getControl(name="level_session").value = ['2004']
725        self.browser.getControl("Remove selected").click()
726        self.assertMatches('...Successfully removed...', self.browser.contents)
727        # Removing levels is properly logged
728        logfile = os.path.join(
729            self.app['datacenter'].storage, 'logs', 'students.log')
730        logcontent = open(logfile).read()
731        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
732                        '- K1000000 - removed: 100' in logcontent)
733        # Add level again
734        self.browser.getControl(name="addlevel").value = ['100']
735        self.browser.getControl(name="level_session").value = ['2004']
736        self.browser.getControl("Add study level").click()
737
738        # Managers can view and manage course lists
739        self.browser.getLink("100").click()
740        self.assertMatches(
741            '...: Study Level 100 (Year 1)...', self.browser.contents)
742        self.browser.getLink("Manage").click()
743        self.browser.getControl(name="form.level_session").value = ['2002']
744        self.browser.getControl("Save").click()
745        self.browser.getControl("Remove selected").click()
746        self.assertMatches('...No ticket selected...', self.browser.contents)
747        ctrl = self.browser.getControl(name='val_id')
748        ctrl.getControl(value='COURSE1').selected = True
749        self.browser.getControl("Remove selected", index=0).click()
750        self.assertTrue('Successfully removed' in self.browser.contents)
751        # Removing course tickets is properly logged
752        logfile = os.path.join(
753            self.app['datacenter'].storage, 'logs', 'students.log')
754        logcontent = open(logfile).read()
755        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
756        '- K1000000 - removed: COURSE1 at 100' in logcontent)
757        self.browser.getLink("here").click()
758        self.browser.getControl(name="form.course").value = ['COURSE1']
759        self.course.credits = 100
760        self.browser.getControl("Add course ticket").click()
761        self.assertMatches(
762            '...Total credits exceed 50...', self.browser.contents)
763        self.course.credits = 10
764        self.browser.getControl("Add course ticket").click()
765        self.assertTrue('Successfully added' in self.browser.contents)
766        # We can do the same by adding the course on the manage page directly
767        del self.student['studycourse']['100']['COURSE1']
768        self.browser.getControl(name="course").value = 'COURSE1'
769        self.browser.getControl("Add course ticket").click()
770        self.assertTrue('Successfully added' in self.browser.contents)
771        self.browser.getLink("here").click()
772        self.browser.getControl(name="form.course").value = ['COURSE1']
773        self.browser.getControl("Add course ticket").click()
774        self.assertTrue('The ticket exists' in self.browser.contents)
775        self.browser.getControl("Cancel").click()
776        self.browser.getLink("COURSE1").click()
777        self.browser.getLink("Manage").click()
778        self.browser.getControl("Save").click()
779        self.assertTrue('Form has been saved' in self.browser.contents)
780        # Grade and weight have been determined
781        self.browser.open(self.studycourse_path + '/100/COURSE1')
782        self.assertFalse('Grade' in self.browser.contents)
783        self.assertFalse('Weight' in self.browser.contents)
784        self.student['studycourse']['100']['COURSE1'].score = 55
785        self.browser.open(self.studycourse_path + '/100/COURSE1')
786        self.assertTrue('Grade' in self.browser.contents)
787        self.assertTrue('Weight' in self.browser.contents)
788        self.assertEqual(self.student['studycourse']['100']['COURSE1'].grade, 'C')
789        self.assertEqual(self.student['studycourse']['100']['COURSE1'].weight, 3)
790        # We add another ticket to check if GPA will be correctly calculated
791        # (and rounded)
792        courseticket = createObject('waeup.CourseTicket')
793        courseticket.code = 'ANYCODE'
794        courseticket.title = u'Any TITLE'
795        courseticket.credits = 13
796        courseticket.score = 66
797        courseticket.semester = 1
798        courseticket.dcode = u'ANYDCODE'
799        courseticket.fcode = u'ANYFCODE'
800        self.student['studycourse']['100']['COURSE2'] = courseticket
801        self.browser.open(self.student_path + '/studycourse/100')
802        # total credits
803        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[1], 23)
804        # weigheted credits = 3 * 10 + 4 * 13
805        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
806        # sgpa = 82 / 23
807        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.565)
808        # Carry-over courses will be collected when next level is created
809        self.browser.open(self.student_path + '/studycourse/manage')
810        # Add next level
811        self.student['studycourse']['100']['COURSE1'].score = 10
812        self.browser.getControl(name="addlevel").value = ['200']
813        self.browser.getControl(name="level_session").value = ['2005']
814        self.browser.getControl("Add study level").click()
815        self.browser.getLink("200").click()
816        self.assertMatches(
817            '...: Study Level 200 (Year 2)...', self.browser.contents)
818        # Since COURSE1 has score 10 it becomes a carry-over course
819        # in level 200
820        self.assertEqual(
821            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
822        self.assertTrue(
823            self.student['studycourse']['200']['COURSE1'].carry_over)
824        # Passed and failed courses have been counted
825        self.assertEqual(
826            self.student['studycourse']['100'].passed_params,
827            (1, 1, 13, 10, ['COURSE1']))
828        self.assertEqual(
829            self.student['studycourse']['200'].passed_params,
830            (0, 0, 0, 0, []))
831        # And also cumulative params can be calculated. Meanwhile we have the
832        # following courses: COURSE1 and COURSE2 in level 100 and
833        # COURSE1 as carry-over course in level 200.
834        self.assertEqual(
835            self.student['studycourse']['100'].cumulative_params,
836            (2.261, 23, 52.0, 23, 13))
837        # COURSE1 in level 200 is not taken into consideration
838        # when calculating the gpa.
839        self.assertEqual(
840            self.student['studycourse']['200'].cumulative_params,
841            (2.261, 23, 52.0, 33, 13))
842        return
843
844    def test_gpa_calculation_with_carryover(self):
845        studylevel = createObject(u'waeup.StudentStudyLevel')
846        studylevel.level = 100
847        studylevel.level_session = 2005
848        self.student['studycourse'].entry_mode = 'ug_ft'
849        self.student['studycourse'].addStudentStudyLevel(
850            self.certificate, studylevel)
851        # First course has been added automatically.
852        # Set score above passmark.
853        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
854        # GPA is 1.
855        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
856        # Set score below passmark.
857        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
858        # GPA is still 0.
859        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
860        studylevel2 = createObject(u'waeup.StudentStudyLevel')
861        studylevel2.level = 200
862        studylevel2.level_session = 2006
863        self.student['studycourse'].addStudentStudyLevel(
864            self.certificate, studylevel2)
865        # Carry-over course has been autonatically added.
866        studylevel2['COURSE1'].score = 66
867        # The score of the carry-over course is now used for calculation of the
868        # GPA at level 100 ...
869        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
870        # ... but not at level 200
871        self.assertEqual(self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
872        return
873
874    def test_manage_payments(self):
875        # Managers can add online school fee payment tickets
876        # if certain requirements are met
877        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
878        self.browser.open(self.payments_path)
879        IWorkflowState(self.student).setState('cleared')
880        self.browser.getLink("Add current session payment ticket").click()
881        self.browser.getControl(name="form.p_category").value = ['schoolfee']
882        self.browser.getControl("Create ticket").click()
883        self.assertMatches('...ticket created...',
884                           self.browser.contents)
885        ctrl = self.browser.getControl(name='val_id')
886        value = ctrl.options[0]
887        self.browser.getLink(value).click()
888        self.assertMatches('...Amount Authorized...',
889                           self.browser.contents)
890        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
891        payment_url = self.browser.url
892
893        # The pdf payment slip can't yet be opened
894        #self.browser.open(payment_url + '/payment_slip.pdf')
895        #self.assertMatches('...Ticket not yet paid...',
896        #                   self.browser.contents)
897
898        # The same payment (with same p_item, p_session and p_category)
899        # can be initialized a second time if the former ticket is not yet paid.
900        self.browser.open(self.payments_path)
901        self.browser.getLink("Add current session payment ticket").click()
902        self.browser.getControl(name="form.p_category").value = ['schoolfee']
903        self.browser.getControl("Create ticket").click()
904        self.assertMatches('...Payment ticket created...',
905                           self.browser.contents)
906
907        # The ticket can be found in the payments_catalog
908        cat = queryUtility(ICatalog, name='payments_catalog')
909        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
910        self.assertTrue(len(results), 1)
911        self.assertTrue(results[0] is self.student['payments'][value])
912
913        # Managers can approve the payment
914        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
915        self.browser.open(payment_url)
916        self.browser.getLink("Approve payment").click()
917        self.assertMatches('...Payment approved...',
918                          self.browser.contents)
919        # Approval is logged in students.log ...
920        logfile = os.path.join(
921            self.app['datacenter'].storage, 'logs', 'students.log')
922        logcontent = open(logfile).read()
923        self.assertTrue(
924            'zope.mgr - students.browser.OnlinePaymentApprovePage '
925            '- K1000000 - schoolfee payment approved'
926            in logcontent)
927        # ... and in payments.log
928        logfile = os.path.join(
929            self.app['datacenter'].storage, 'logs', 'payments.log')
930        logcontent = open(logfile).read()
931        self.assertTrue(
932            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
933            in logcontent)
934
935        # The authorized amount has been stored in the access code
936        self.assertEqual(
937            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
938
939        # The catalog has been updated
940        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
941        self.assertTrue(len(results), 0)
942        results = list(cat.searchResults(p_state=('paid', 'paid')))
943        self.assertTrue(len(results), 1)
944        self.assertTrue(results[0] is self.student['payments'][value])
945
946        # Payments can't be approved twice
947        self.browser.open(payment_url + '/approve')
948        self.assertMatches('...This ticket has already been paid...',
949                          self.browser.contents)
950
951        # Now the first ticket is paid and no more ticket of same type
952        # (with same p_item, p_session and p_category) can be added
953        self.browser.open(self.payments_path)
954        self.browser.getLink("Add current session payment ticket").click()
955        self.browser.getControl(name="form.p_category").value = ['schoolfee']
956        self.browser.getControl("Create ticket").click()
957        self.assertMatches(
958            '...This type of payment has already been made...',
959            self.browser.contents)
960
961        # Managers can open the pdf payment slip
962        self.browser.open(payment_url)
963        self.browser.getLink("Download payment slip").click()
964        self.assertEqual(self.browser.headers['Status'], '200 Ok')
965        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
966
967        # Managers can remove online school fee payment tickets
968        self.browser.open(self.payments_path)
969        self.browser.getControl("Remove selected").click()
970        self.assertMatches('...No payment selected...', self.browser.contents)
971        ctrl = self.browser.getControl(name='val_id')
972        value = ctrl.options[0]
973        ctrl.getControl(value=value).selected = True
974        self.browser.getControl("Remove selected", index=0).click()
975        self.assertTrue('Successfully removed' in self.browser.contents)
976
977        # Managers can add online clearance payment tickets
978        self.browser.open(self.payments_path + '/addop')
979        self.browser.getControl(name="form.p_category").value = ['clearance']
980        self.browser.getControl("Create ticket").click()
981        self.assertMatches('...ticket created...',
982                           self.browser.contents)
983
984        # Managers can approve the payment
985        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
986        ctrl = self.browser.getControl(name='val_id')
987        value = ctrl.options[1] # The clearance payment is the second in the table
988        self.browser.getLink(value).click()
989        self.browser.open(self.browser.url + '/approve')
990        self.assertMatches('...Payment approved...',
991                          self.browser.contents)
992        expected = '''...
993        <td>
994          <span>Paid</span>
995        </td>...'''
996        self.assertMatches(expected,self.browser.contents)
997        # The new CLR-0 pin has been created
998        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
999        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1000        ac = self.app['accesscodes']['CLR-0'][pin]
1001        self.assertEqual(ac.owner, self.student_id)
1002        self.assertEqual(ac.cost, 3456.0)
1003
1004        # Managers can add online transcript payment tickets
1005        self.browser.open(self.payments_path + '/addop')
1006        self.browser.getControl(name="form.p_category").value = ['transcript']
1007        self.browser.getControl("Create ticket").click()
1008        self.assertMatches('...ticket created...',
1009                           self.browser.contents)
1010
1011        # Managers can approve the payment
1012        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1013        ctrl = self.browser.getControl(name='val_id')
1014        value = ctrl.options[2] # The clearance payment is the third in the table
1015        self.browser.getLink(value).click()
1016        self.browser.open(self.browser.url + '/approve')
1017        self.assertMatches('...Payment approved...',
1018                          self.browser.contents)
1019        expected = '''...
1020        <td>
1021          <span>Paid</span>
1022        </td>...'''
1023        self.assertMatches(expected,self.browser.contents)
1024        # The new CLR-0 pin has been created
1025        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1026        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1027        ac = self.app['accesscodes']['TSC-0'][pin]
1028        self.assertEqual(ac.owner, self.student_id)
1029        self.assertEqual(ac.cost, 4567.0)
1030        return
1031
1032    def test_manage_balance_payments(self):
1033
1034        # Login
1035        #self.browser.open(self.login_path)
1036        #self.browser.getControl(name="form.login").value = self.student_id
1037        #self.browser.getControl(name="form.password").value = 'spwd'
1038        #self.browser.getControl("Login").click()
1039
1040        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1041        self.browser.open(self.payments_path)
1042
1043        # Managers can add previous school fee payment tickets in any state.
1044        IWorkflowState(self.student).setState('courses registered')
1045        self.browser.open(self.payments_path)
1046        self.browser.getLink("Add balance payment ticket").click()
1047
1048        # Previous session payment form is provided
1049        self.assertEqual(self.student.current_session, 2004)
1050        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1051        self.browser.getControl(name="form.balance_session").value = ['2004']
1052        self.browser.getControl(name="form.balance_level").value = ['300']
1053        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1054        self.browser.getControl("Create ticket").click()
1055        self.assertMatches('...Amount must be greater than 0...',
1056                           self.browser.contents)
1057        self.browser.getControl(name="form.balance_amount").value = '0'
1058        self.browser.getControl("Create ticket").click()
1059        self.assertMatches('...Amount must be greater than 0...',
1060                           self.browser.contents)
1061        self.browser.getControl(name="form.balance_amount").value = '567.8'
1062        self.browser.getControl("Create ticket").click()
1063        self.assertMatches('...ticket created...',
1064                           self.browser.contents)
1065        ctrl = self.browser.getControl(name='val_id')
1066        value = ctrl.options[0]
1067        self.browser.getLink(value).click()
1068        self.assertMatches('...Amount Authorized...',
1069                           self.browser.contents)
1070        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1071        # Payment attributes are properly set
1072        self.assertEqual(self.student['payments'][value].p_session, 2004)
1073        self.assertEqual(self.student['payments'][value].p_level, 300)
1074        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1075        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1076
1077    def test_manage_accommodation(self):
1078        logfile = os.path.join(
1079            self.app['datacenter'].storage, 'logs', 'students.log')
1080        # Managers can add online booking fee payment tickets and open the
1081        # callback view (see test_manage_payments)
1082        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1083        self.browser.open(self.payments_path)
1084        self.browser.getLink("Add current session payment ticket").click()
1085        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1086        # If student is not in accommodation session, payment cannot be processed
1087        self.app['hostels'].accommodation_session = 2011
1088        self.browser.getControl("Create ticket").click()
1089        self.assertMatches('...Your current session does not match...',
1090                           self.browser.contents)
1091        self.app['hostels'].accommodation_session = 2004
1092        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1093        self.browser.getControl("Create ticket").click()
1094        ctrl = self.browser.getControl(name='val_id')
1095        value = ctrl.options[0]
1096        self.browser.getLink(value).click()
1097        self.browser.open(self.browser.url + '/approve')
1098        # The new HOS-0 pin has been created
1099        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1100        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1101        ac = self.app['accesscodes']['HOS-0'][pin]
1102        self.assertEqual(ac.owner, self.student_id)
1103        parts = pin.split('-')[1:]
1104        sfeseries, sfenumber = parts
1105        # Managers can use HOS code and book a bed space with it
1106        self.browser.open(self.acco_path)
1107        self.browser.getLink("Book accommodation").click()
1108        self.assertMatches('...You are in the wrong...',
1109                           self.browser.contents)
1110        IWorkflowInfo(self.student).fireTransition('admit')
1111        # An existing HOS code can only be used if students
1112        # are in accommodation session
1113        self.student['studycourse'].current_session = 2003
1114        self.browser.getLink("Book accommodation").click()
1115        self.assertMatches('...Your current session does not match...',
1116                           self.browser.contents)
1117        self.student['studycourse'].current_session = 2004
1118        # All requirements are met and ticket can be created
1119        self.browser.getLink("Book accommodation").click()
1120        self.assertMatches('...Activation Code:...',
1121                           self.browser.contents)
1122        self.browser.getControl(name="ac_series").value = sfeseries
1123        self.browser.getControl(name="ac_number").value = sfenumber
1124        self.browser.getControl("Create bed ticket").click()
1125        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1126                           self.browser.contents)
1127        # Bed has been allocated
1128        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1129        self.assertTrue(bed1.owner == self.student_id)
1130        # BedTicketAddPage is now blocked
1131        self.browser.getLink("Book accommodation").click()
1132        self.assertMatches('...You already booked a bed space...',
1133            self.browser.contents)
1134        # The bed ticket displays the data correctly
1135        self.browser.open(self.acco_path + '/2004')
1136        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1137                           self.browser.contents)
1138        self.assertMatches('...2004/2005...', self.browser.contents)
1139        self.assertMatches('...regular_male_fr...', self.browser.contents)
1140        self.assertMatches('...%s...' % pin, self.browser.contents)
1141        # Booking is properly logged
1142        logcontent = open(logfile).read()
1143        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1144            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1145        # Managers can relocate students if the student's bed_type has changed
1146        self.browser.getLink("Relocate student").click()
1147        self.assertMatches(
1148            "...Student can't be relocated...", self.browser.contents)
1149        self.student.sex = u'f'
1150        self.browser.getLink("Relocate student").click()
1151        self.assertMatches(
1152            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1153        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1154        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1155        self.assertTrue(bed2.owner == self.student_id)
1156        self.assertTrue(self.student['accommodation'][
1157            '2004'].bed_type == u'regular_female_fr')
1158        # Relocation is properly logged
1159        logcontent = open(logfile).read()
1160        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationPage '
1161            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1162        # The payment object still shows the original payment item
1163        payment_id = self.student['payments'].keys()[0]
1164        payment = self.student['payments'][payment_id]
1165        self.assertTrue(payment.p_item == u'regular_male_fr')
1166        # Managers can relocate students if the bed's bed_type has changed
1167        bed1.bed_type = u'regular_female_fr'
1168        bed2.bed_type = u'regular_male_fr'
1169        notify(grok.ObjectModifiedEvent(bed1))
1170        notify(grok.ObjectModifiedEvent(bed2))
1171        self.browser.getLink("Relocate student").click()
1172        self.assertMatches(
1173            "...Student relocated...", self.browser.contents)
1174        self.assertMatches(
1175            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1176        self.assertMatches(bed1.owner, self.student_id)
1177        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1178        # Managers can't relocate students if bed is reserved
1179        self.student.sex = u'm'
1180        bed1.bed_type = u'regular_female_reserved'
1181        notify(grok.ObjectModifiedEvent(bed1))
1182        self.browser.getLink("Relocate student").click()
1183        self.assertMatches(
1184            "...Students in reserved beds can't be relocated...",
1185            self.browser.contents)
1186        # Managers can relocate students if booking has been cancelled but
1187        # other bed space has been manually allocated after cancellation
1188        old_owner = bed1.releaseBed()
1189        self.assertMatches(old_owner, self.student_id)
1190        bed2.owner = self.student_id
1191        self.browser.open(self.acco_path + '/2004')
1192        self.assertMatches(
1193            "...booking cancelled...", self.browser.contents)
1194        self.browser.getLink("Relocate student").click()
1195        # We didn't informed the catalog therefore the new owner is not found
1196        self.assertMatches(
1197            "...There is no free bed in your category regular_male_fr...",
1198            self.browser.contents)
1199        # Now we fire the event properly
1200        notify(grok.ObjectModifiedEvent(bed2))
1201        self.browser.getLink("Relocate student").click()
1202        self.assertMatches(
1203            "...Student relocated...", self.browser.contents)
1204        self.assertMatches(
1205            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1206          # Managers can delete bed tickets
1207        self.browser.open(self.acco_path)
1208        ctrl = self.browser.getControl(name='val_id')
1209        value = ctrl.options[0]
1210        ctrl.getControl(value=value).selected = True
1211        self.browser.getControl("Remove selected", index=0).click()
1212        self.assertMatches('...Successfully removed...', self.browser.contents)
1213        # The bed has been properly released by the event handler
1214        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1215        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1216        return
1217
1218    def test_manage_workflow(self):
1219        # Managers can pass through the whole workflow
1220        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1221        student = self.app['students'][self.student_id]
1222        self.browser.open(self.trigtrans_path)
1223        self.assertTrue(student.clearance_locked)
1224        self.browser.getControl(name="transition").value = ['admit']
1225        self.browser.getControl("Save").click()
1226        self.assertTrue(student.clearance_locked)
1227        self.browser.getControl(name="transition").value = ['start_clearance']
1228        self.browser.getControl("Save").click()
1229        self.assertFalse(student.clearance_locked)
1230        self.browser.getControl(name="transition").value = ['request_clearance']
1231        self.browser.getControl("Save").click()
1232        self.assertTrue(student.clearance_locked)
1233        self.browser.getControl(name="transition").value = ['clear']
1234        self.browser.getControl("Save").click()
1235        # Managers approve payment, they do not pay
1236        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1237        self.browser.getControl(
1238            name="transition").value = ['approve_first_school_fee']
1239        self.browser.getControl("Save").click()
1240        self.browser.getControl(name="transition").value = ['reset6']
1241        self.browser.getControl("Save").click()
1242        # In state returning the pay_school_fee transition triggers some
1243        # changes of attributes
1244        self.browser.getControl(name="transition").value = ['approve_school_fee']
1245        self.browser.getControl("Save").click()
1246        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1247        self.assertEqual(student['studycourse'].current_level, 200) # +100
1248        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1249        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1250        self.browser.getControl(name="transition").value = ['register_courses']
1251        self.browser.getControl("Save").click()
1252        self.browser.getControl(name="transition").value = ['validate_courses']
1253        self.browser.getControl("Save").click()
1254        self.browser.getControl(name="transition").value = ['return']
1255        self.browser.getControl("Save").click()
1256        return
1257
1258    def test_manage_pg_workflow(self):
1259        # Managers can pass through the whole workflow
1260        IWorkflowState(self.student).setState('school fee paid')
1261        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1262        student = self.app['students'][self.student_id]
1263        self.browser.open(self.trigtrans_path)
1264        self.assertTrue('<option value="reset6">' in self.browser.contents)
1265        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1266        self.assertTrue('<option value="reset5">' in self.browser.contents)
1267        self.certificate.study_mode = 'pg_ft'
1268        self.browser.open(self.trigtrans_path)
1269        self.assertFalse('<option value="reset6">' in self.browser.contents)
1270        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1271        self.assertTrue('<option value="reset5">' in self.browser.contents)
1272        return
1273
1274    def test_manage_import(self):
1275        # Managers can import student data files
1276        datacenter_path = 'http://localhost/app/datacenter'
1277        # Prepare a csv file for students
1278        open('students.csv', 'wb').write(
1279"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1280Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1281Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1282Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1283""")
1284        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1285        self.browser.open(datacenter_path)
1286        self.browser.getLink('Upload data').click()
1287        filecontents = StringIO(open('students.csv', 'rb').read())
1288        filewidget = self.browser.getControl(name='uploadfile:file')
1289        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1290        self.browser.getControl(name='SUBMIT').click()
1291        self.browser.getLink('Process data').click()
1292        button = lookup_submit_value(
1293            'select', 'students_zope.mgr.csv', self.browser)
1294        button.click()
1295        importerselect = self.browser.getControl(name='importer')
1296        modeselect = self.browser.getControl(name='mode')
1297        importerselect.getControl('Student Processor').selected = True
1298        modeselect.getControl(value='create').selected = True
1299        self.browser.getControl('Proceed to step 3').click()
1300        self.assertTrue('Header fields OK' in self.browser.contents)
1301        self.browser.getControl('Perform import').click()
1302        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1303        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1304        self.assertTrue('Batch processing finished' in self.browser.contents)
1305        open('studycourses.csv', 'wb').write(
1306"""reg_number,matric_number,certificate,current_session,current_level
13071,,CERT1,2008,100
1308,100001,CERT1,2008,100
1309,100002,CERT1,2008,100
1310""")
1311        self.browser.open(datacenter_path)
1312        self.browser.getLink('Upload data').click()
1313        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1314        filewidget = self.browser.getControl(name='uploadfile:file')
1315        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1316        self.browser.getControl(name='SUBMIT').click()
1317        self.browser.getLink('Process data').click()
1318        button = lookup_submit_value(
1319            'select', 'studycourses_zope.mgr.csv', self.browser)
1320        button.click()
1321        importerselect = self.browser.getControl(name='importer')
1322        modeselect = self.browser.getControl(name='mode')
1323        importerselect.getControl(
1324            'StudentStudyCourse Processor (update only)').selected = True
1325        modeselect.getControl(value='create').selected = True
1326        self.browser.getControl('Proceed to step 3').click()
1327        self.assertTrue('Update mode only' in self.browser.contents)
1328        self.browser.getControl('Proceed to step 3').click()
1329        self.assertTrue('Header fields OK' in self.browser.contents)
1330        self.browser.getControl('Perform import').click()
1331        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1332        self.assertTrue('Successfully processed 2 rows'
1333                        in self.browser.contents)
1334        # The students are properly indexed and we can
1335        # thus find a student in  the department
1336        self.browser.open(self.manage_container_path)
1337        self.browser.getControl(name="searchtype").value = ['depcode']
1338        self.browser.getControl(name="searchterm").value = 'dep1'
1339        self.browser.getControl("Find student(s)").click()
1340        self.assertTrue('Aaren Pieri' in self.browser.contents)
1341        # We can search for a new student by name ...
1342        self.browser.getControl(name="searchtype").value = ['fullname']
1343        self.browser.getControl(name="searchterm").value = 'Claus'
1344        self.browser.getControl("Find student(s)").click()
1345        self.assertTrue('Claus Finau' in self.browser.contents)
1346        # ... and check if the imported password has been properly set
1347        ctrl = self.browser.getControl(name='entries')
1348        value = ctrl.options[0]
1349        claus = self.app['students'][value]
1350        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1351        return
1352
1353    def test_handle_clearance_by_co(self):
1354        # Create clearance officer
1355        self.app['users'].addUser('mrclear', 'mrclearsecret')
1356        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1357        self.app['users']['mrclear'].title = 'Carlo Pitter'
1358        # Clearance officers need not necessarily to get
1359        # the StudentsOfficer site role
1360        #prmglobal = IPrincipalRoleManager(self.app)
1361        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1362        # Assign local ClearanceOfficer role
1363        department = self.app['faculties']['fac1']['dep1']
1364        prmlocal = IPrincipalRoleManager(department)
1365        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1366        IWorkflowState(self.student).setState('clearance started')
1367        # Login as clearance officer
1368        self.browser.open(self.login_path)
1369        self.browser.getControl(name="form.login").value = 'mrclear'
1370        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1371        self.browser.getControl("Login").click()
1372        self.assertMatches('...You logged in...', self.browser.contents)
1373        # CO can see his roles
1374        self.browser.getLink("My Roles").click()
1375        self.assertMatches(
1376            '...<div>Academics Officer (view only)</div>...',
1377            self.browser.contents)
1378        #self.assertMatches(
1379        #    '...<div>Students Officer (view only)</div>...',
1380        #    self.browser.contents)
1381        # But not his local role ...
1382        self.assertFalse('Clearance Officer' in self.browser.contents)
1383        # ... because we forgot to notify the department that the local role
1384        # has changed
1385        notify(LocalRoleSetEvent(
1386            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
1387        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1388        self.assertTrue('Clearance Officer' in self.browser.contents)
1389        self.assertMatches(
1390            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1391            self.browser.contents)
1392        # CO can view the student ...
1393        self.browser.open(self.clearance_path)
1394        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1395        self.assertEqual(self.browser.url, self.clearance_path)
1396        # ... but not other students
1397        other_student = Student()
1398        other_student.firstname = u'Dep2'
1399        other_student.lastname = u'Student'
1400        self.app['students'].addStudent(other_student)
1401        other_student_path = (
1402            'http://localhost/app/students/%s' % other_student.student_id)
1403        self.assertRaises(
1404            Unauthorized, self.browser.open, other_student_path)
1405        # Clearance is disabled for this session
1406        self.browser.open(self.clearance_path)
1407        self.assertFalse('Clear student' in self.browser.contents)
1408        self.browser.open(self.student_path + '/clear')
1409        self.assertTrue('Clearance is disabled for this session'
1410            in self.browser.contents)
1411        self.app['configuration']['2004'].clearance_enabled = True
1412        # Only in state clearance requested the CO does see the 'Clear' button
1413        self.browser.open(self.clearance_path)
1414        self.assertFalse('Clear student' in self.browser.contents)
1415        IWorkflowInfo(self.student).fireTransition('request_clearance')
1416        self.browser.open(self.clearance_path)
1417        self.assertTrue('Clear student' in self.browser.contents)
1418        self.browser.getLink("Clear student").click()
1419        self.assertTrue('Student has been cleared' in self.browser.contents)
1420        self.assertTrue('cleared' in self.browser.contents)
1421        self.browser.open(self.history_path)
1422        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1423        # Hide real name.
1424        self.app['users']['mrclear'].public_name = 'My Public Name'
1425        self.browser.open(self.clearance_path)
1426        self.browser.getLink("Reject clearance").click()
1427        self.assertEqual(
1428            self.browser.url, self.student_path + '/reject_clearance')
1429        # Type comment why
1430        self.browser.getControl(name="form.officer_comment").value = """Dear Student,
1431You did not fill properly.
1432"""
1433        self.browser.getControl("Save comment").click()
1434        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1435        url = ('http://localhost/app/students/K1000000/'
1436              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1437              '%0A&subject=Clearance+has+been+annulled.')
1438        # CO does now see the prefilled contact form and can send a message
1439        self.assertEqual(self.browser.url, url)
1440        self.assertTrue('clearance started' in self.browser.contents)
1441        self.assertTrue('name="form.subject" size="20" type="text" '
1442            'value="Clearance has been annulled."'
1443            in self.browser.contents)
1444        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1445            in self.browser.contents)
1446        self.browser.getControl("Send message now").click()
1447        self.assertTrue('Your message has been sent' in self.browser.contents)
1448        # The comment has been stored ...
1449        self.assertEqual(self.student.officer_comment,
1450            u'Dear Student,\nYou did not fill properly.\n')
1451        # ... and logged
1452        logfile = os.path.join(
1453            self.app['datacenter'].storage, 'logs', 'students.log')
1454        logcontent = open(logfile).read()
1455        self.assertTrue(
1456            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1457            'K1000000 - comment: Dear Student,<br>You did not fill '
1458            'properly.<br>\n' in logcontent)
1459        self.browser.open(self.history_path)
1460        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1461            self.browser.contents)
1462        IWorkflowInfo(self.student).fireTransition('request_clearance')
1463        self.browser.open(self.clearance_path)
1464        self.browser.getLink("Reject clearance").click()
1465        self.browser.getControl("Save comment").click()
1466        self.assertTrue('Clearance request has been rejected'
1467            in self.browser.contents)
1468        self.assertTrue('clearance started' in self.browser.contents)
1469        # The CO can't clear students if not in state
1470        # clearance requested
1471        self.browser.open(self.student_path + '/clear')
1472        self.assertTrue('Student is in wrong state'
1473            in self.browser.contents)
1474        # The CO can go to his department throug the my_roles page ...
1475        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1476        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1477        # ... and view the list of students
1478        self.browser.getLink("Show students").click()
1479        self.browser.getControl(name="session").value = ['2004']
1480        self.browser.getControl(name="level").value = ['200']
1481        self.browser.getControl("Show").click()
1482        self.assertFalse(self.student_id in self.browser.contents)
1483        self.browser.getControl(name="session").value = ['2004']
1484        self.browser.getControl(name="level").value = ['100']
1485        self.browser.getControl("Show").click()
1486        self.assertTrue(self.student_id in self.browser.contents)
1487        # The comment is indicated by 'yes'
1488        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1489        # Check if the enquiries form is not pre-filled with officer_comment
1490        # (regression test)
1491        self.browser.getLink("Logout").click()
1492        self.browser.open('http://localhost/app/enquiries')
1493        self.assertFalse(
1494            'You did not fill properly'
1495            in self.browser.contents)
1496        # When a student is cleared the comment is automatically deleted
1497        IWorkflowInfo(self.student).fireTransition('request_clearance')
1498        IWorkflowInfo(self.student).fireTransition('clear')
1499        self.assertEqual(self.student.officer_comment, None)
1500
1501    def test_handle_courses_by_ca(self):
1502        # Create course adviser
1503        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1504        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1505        self.app['users']['mrsadvise'].title = u'Helen Procter'
1506        # Assign local CourseAdviser100 role for a certificate
1507        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1508        prmlocal = IPrincipalRoleManager(cert)
1509        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1510        IWorkflowState(self.student).setState('school fee paid')
1511        # Login as course adviser
1512        self.browser.open(self.login_path)
1513        self.browser.getControl(name="form.login").value = 'mrsadvise'
1514        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1515        self.browser.getControl("Login").click()
1516        self.assertMatches('...You logged in...', self.browser.contents)
1517        # CO can see his roles
1518        self.browser.getLink("My Roles").click()
1519        self.assertMatches(
1520            '...<div>Academics Officer (view only)</div>...',
1521            self.browser.contents)
1522        # But not his local role ...
1523        self.assertFalse('Course Adviser' in self.browser.contents)
1524        # ... because we forgot to notify the certificate that the local role
1525        # has changed
1526        notify(LocalRoleSetEvent(
1527            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1528        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1529        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1530        self.assertMatches(
1531            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1532            self.browser.contents)
1533        # CA can view the student ...
1534        self.browser.open(self.student_path)
1535        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1536        self.assertEqual(self.browser.url, self.student_path)
1537        # ... but not other students
1538        other_student = Student()
1539        other_student.firstname = u'Dep2'
1540        other_student.lastname = u'Student'
1541        self.app['students'].addStudent(other_student)
1542        other_student_path = (
1543            'http://localhost/app/students/%s' % other_student.student_id)
1544        self.assertRaises(
1545            Unauthorized, self.browser.open, other_student_path)
1546        # We add study level 110 to the student's studycourse
1547        studylevel = StudentStudyLevel()
1548        studylevel.level = 110
1549        self.student['studycourse'].addStudentStudyLevel(
1550            cert,studylevel)
1551        L110_student_path = self.studycourse_path + '/110'
1552        # The CA can neither see the Validate nor the Edit button
1553        self.browser.open(L110_student_path)
1554        self.assertFalse('Validate courses' in self.browser.contents)
1555        self.assertFalse('Edit' in self.browser.contents)
1556        IWorkflowInfo(self.student).fireTransition('register_courses')
1557        self.browser.open(L110_student_path)
1558        self.assertFalse('Validate courses' in self.browser.contents)
1559        self.assertFalse('Edit' in self.browser.contents)
1560        # Only in state courses registered and only if the current level
1561        # corresponds with the name of the study level object
1562        # the 100L CA does see the 'Validate' button but not
1563        # the edit button
1564        self.student['studycourse'].current_level = 110
1565        self.browser.open(L110_student_path)
1566        self.assertFalse('Edit' in self.browser.contents)
1567        self.assertTrue('Validate courses' in self.browser.contents)
1568        # But a 100L CA does not see the button at other levels
1569        studylevel2 = StudentStudyLevel()
1570        studylevel2.level = 200
1571        self.student['studycourse'].addStudentStudyLevel(
1572            cert,studylevel2)
1573        L200_student_path = self.studycourse_path + '/200'
1574        self.browser.open(L200_student_path)
1575        self.assertFalse('Edit' in self.browser.contents)
1576        self.assertFalse('Validate courses' in self.browser.contents)
1577        self.browser.open(L110_student_path)
1578        self.browser.getLink("Validate courses").click()
1579        self.assertTrue('Course list has been validated' in self.browser.contents)
1580        self.assertTrue('courses validated' in self.browser.contents)
1581        self.assertEqual(self.student['studycourse']['110'].validated_by,
1582            'Helen Procter')
1583        self.assertMatches(
1584            '<YYYY-MM-DD hh:mm:ss>',
1585            self.student['studycourse']['110'].validation_date.strftime(
1586                "%Y-%m-%d %H:%M:%S"))
1587        self.browser.getLink("Reject courses").click()
1588        self.assertTrue('Course list request has been annulled.'
1589            in self.browser.contents)
1590        urlmessage = 'Course+list+request+has+been+annulled.'
1591        self.assertEqual(self.browser.url, self.student_path +
1592            '/contactstudent?subject=%s' % urlmessage)
1593        self.assertTrue('school fee paid' in self.browser.contents)
1594        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1595        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1596        IWorkflowInfo(self.student).fireTransition('register_courses')
1597        self.browser.open(L110_student_path)
1598        self.browser.getLink("Reject courses").click()
1599        self.assertTrue('Course list request has been rejected'
1600            in self.browser.contents)
1601        self.assertTrue('school fee paid' in self.browser.contents)
1602        # CA does now see the contact form and can send a message
1603        self.browser.getControl(name="form.subject").value = 'Important subject'
1604        self.browser.getControl(name="form.body").value = 'Course list rejected'
1605        self.browser.getControl("Send message now").click()
1606        self.assertTrue('Your message has been sent' in self.browser.contents)
1607        # The CA does now see the Edit button and can edit
1608        # current study level
1609        self.browser.open(L110_student_path)
1610        self.browser.getLink("Edit").click()
1611        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1612            in self.browser.contents)
1613        # The CA can't validate courses if not in state
1614        # courses registered
1615        self.browser.open(L110_student_path + '/validate_courses')
1616        self.assertTrue('Student is in the wrong state'
1617            in self.browser.contents)
1618        # The CA can go to his certificate through the my_roles page
1619        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1620        self.browser.getLink(
1621            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1622        # and view the list of students
1623        self.browser.getLink("Show students").click()
1624        self.browser.getControl(name="session").value = ['2004']
1625        self.browser.getControl(name="level").value = ['100']
1626        self.browser.getControl("Show").click()
1627        self.assertTrue(self.student_id in self.browser.contents)
1628
1629    def test_handle_courses_by_lecturer(self):
1630        # Create course lecturer
1631        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1632        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1633        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1634        # Assign local Lecturer role for a certificate
1635        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1636        prmlocal = IPrincipalRoleManager(course)
1637        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1638        # Login as lecturer
1639        self.browser.open(self.login_path)
1640        self.browser.getControl(name="form.login").value = 'mrslecturer'
1641        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1642        self.browser.getControl("Login").click()
1643        self.assertMatches('...You logged in...', self.browser.contents)
1644        # CO can see her roles
1645        self.browser.getLink("My Roles").click()
1646        self.assertMatches(
1647            '...<div>Academics Officer (view only)</div>...',
1648            self.browser.contents)
1649        # But not her local role ...
1650        self.assertFalse('Lecturer' in self.browser.contents)
1651        # ... because we forgot to notify the course that the local role
1652        # has changed
1653        notify(LocalRoleSetEvent(
1654            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1655        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1656        self.assertTrue('Lecturer' in self.browser.contents)
1657        self.assertMatches(
1658            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1659            self.browser.contents)
1660        # The lecturer can go to her course
1661        self.browser.getLink(
1662            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1663        # and view the list of students
1664        self.browser.getLink("Show students").click()
1665        self.browser.getControl(name="session").value = ['2004']
1666        self.browser.getControl(name="level").value = ['100']
1667        self.browser.getControl("Show").click()
1668        self.assertTrue('No student found.' in self.browser.contents)
1669        # No student in course so far
1670        self.assertFalse(self.student_id in self.browser.contents)
1671        studylevel = createObject(u'waeup.StudentStudyLevel')
1672        studylevel.level = 100
1673        studylevel.level_session = 2004
1674        self.student['studycourse'].addStudentStudyLevel(
1675            self.certificate, studylevel)
1676        # Now the student has registered the course and can
1677        # be seen by the lecturer.
1678        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1679        self.browser.getControl(name="session").value = ['2004']
1680        self.browser.getControl(name="level").value = ['100']
1681        self.browser.getControl("Show").click()
1682        self.assertTrue(self.student_id in self.browser.contents)
1683        # The course ticket can be linked with the course.
1684        self.assertEqual(
1685            self.student['studycourse']['100']['COURSE1'].course,
1686            self.course)
1687        # Lecturer can neither access the student ...
1688        self.assertRaises(
1689            Unauthorized, self.browser.open, self.student_path)
1690        # ... nor the respective course ticket since
1691        # editing course tickets by lecturers is not feasible.
1692        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1693        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1694        self.assertRaises(
1695            Unauthorized, self.browser.open, course_ticket_path)
1696        # Course results can be batch edited via the edit_courses view
1697        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1698        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1699        self.browser.getLink("Update scores").click()
1700        self.assertTrue('Score editing disabled' in self.browser.contents)
1701        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1702        self.browser.getLink("Update scores").click()
1703        self.assertTrue('Current academic session not set' in self.browser.contents)
1704        self.app['configuration'].current_academic_session = 2004
1705        self.browser.getLink("Update scores").click()
1706        self.assertFalse(
1707            '<input type="text" name="scores" class="span1" />'
1708            in self.browser.contents)
1709        IWorkflowState(self.student).setState('courses validated')
1710        # Student must be in state 'courses validated'
1711        self.browser.open(
1712            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1713        self.assertTrue(
1714            '<input type="text" name="scores" class="span1" />'
1715            in self.browser.contents)
1716        self.browser.getControl(name="scores", index=0).value = '55'
1717        self.browser.getControl("Update scores").click()
1718        # New score has been set
1719        self.assertEqual(
1720            self.student['studycourse']['100']['COURSE1'].score, 55)
1721        # Score editing has been logged
1722        logfile = os.path.join(
1723            self.app['datacenter'].storage, 'logs', 'students.log')
1724        logcontent = open(logfile).read()
1725        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1726                        'K1000000 100/COURSE1 score updated (55)' in logcontent)
1727        # Non-integer scores won't be accepted
1728        self.browser.open(
1729            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1730        self.assertTrue('value="55" />' in self.browser.contents)
1731        self.browser.getControl(name="scores", index=0).value = 'abc'
1732        self.browser.getControl("Update scores").click()
1733        self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
1734            in self.browser.contents)
1735        # Scores can be removed
1736        self.browser.open(
1737            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1738        self.browser.getControl(name="scores", index=0).value = ''
1739        self.browser.getControl("Update scores").click()
1740        self.assertEqual(
1741            self.student['studycourse']['100']['COURSE1'].score, None)
1742        logcontent = open(logfile).read()
1743        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1744                        'K1000000 100/COURSE1 score updated (None)' in logcontent)
1745
1746    def test_change_current_mode(self):
1747        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1748        self.browser.open(self.clearance_path)
1749        self.assertFalse('Employer' in self.browser.contents)
1750        self.browser.open(self.manage_clearance_path)
1751        self.assertFalse('Employer' in self.browser.contents)
1752        self.student.clearance_locked = False
1753        self.browser.open(self.edit_clearance_path)
1754        self.assertFalse('Employer' in self.browser.contents)
1755        # Now we change the study mode of the certificate and a different
1756        # interface is used by clearance views.
1757        self.certificate.study_mode = 'pg_ft'
1758        # Invariants are not being checked here?!
1759        self.certificate.end_level = 100
1760        self.browser.open(self.clearance_path)
1761        self.assertTrue('Employer' in self.browser.contents)
1762        self.browser.open(self.manage_clearance_path)
1763        self.assertTrue('Employer' in self.browser.contents)
1764        self.browser.open(self.edit_clearance_path)
1765        self.assertTrue('Employer' in self.browser.contents)
1766
1767    def test_find_students_in_faculties(self):
1768        # Create local students manager in faculty
1769        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
1770        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1771        self.app['users']['mrmanager'].title = u'Volk Wagen'
1772        # Assign LocalStudentsManager role for faculty
1773        fac = self.app['faculties']['fac1']
1774        prmlocal = IPrincipalRoleManager(fac)
1775        prmlocal.assignRoleToPrincipal(
1776            'waeup.local.LocalStudentsManager', 'mrmanager')
1777        notify(LocalRoleSetEvent(
1778            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1779            granted=True))
1780        # Login as manager
1781        self.browser.open(self.login_path)
1782        self.browser.getControl(name="form.login").value = 'mrmanager'
1783        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
1784        self.browser.getControl("Login").click()
1785        self.assertMatches('...You logged in...', self.browser.contents)
1786        # Manager can see his roles
1787        self.browser.getLink("My Roles").click()
1788        self.assertMatches(
1789            '...<span>Students Manager</span>...',
1790            self.browser.contents)
1791        # The manager can go to his faculty
1792        self.browser.getLink(
1793            "http://localhost/app/faculties/fac1").click()
1794        # and find students
1795        self.browser.getLink("Find students").click()
1796        self.browser.getControl("Find student").click()
1797        self.assertTrue('Empty search string' in self.browser.contents)
1798        self.browser.getControl(name="searchtype").value = ['student_id']
1799        self.browser.getControl(name="searchterm").value = self.student_id
1800        self.browser.getControl("Find student").click()
1801        self.assertTrue('Anna Tester' in self.browser.contents)
1802
1803    def test_activate_deactivate_buttons(self):
1804        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1805        self.browser.open(self.student_path)
1806        self.browser.getLink("Deactivate").click()
1807        self.assertTrue(
1808            'Student account has been deactivated.' in self.browser.contents)
1809        self.assertTrue(
1810            'Base Data (account deactivated)' in self.browser.contents)
1811        self.assertTrue(self.student.suspended)
1812        self.browser.getLink("Activate").click()
1813        self.assertTrue(
1814            'Student account has been activated.' in self.browser.contents)
1815        self.assertFalse(
1816            'Base Data (account deactivated)' in self.browser.contents)
1817        self.assertFalse(self.student.suspended)
1818        # History messages have been added ...
1819        self.browser.getLink("History").click()
1820        self.assertTrue(
1821            'Student account deactivated by Manager<br />' in self.browser.contents)
1822        self.assertTrue(
1823            'Student account activated by Manager<br />' in self.browser.contents)
1824        # ... and actions have been logged.
1825        logfile = os.path.join(
1826            self.app['datacenter'].storage, 'logs', 'students.log')
1827        logcontent = open(logfile).read()
1828        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
1829                        'K1000000 - account deactivated' in logcontent)
1830        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
1831                        'K1000000 - account activated' in logcontent)
1832
1833    def test_manage_student_transfer(self):
1834        # Add second certificate
1835        self.certificate2 = createObject('waeup.Certificate')
1836        self.certificate2.code = u'CERT2'
1837        self.certificate2.study_mode = 'ug_ft'
1838        self.certificate2.start_level = 999
1839        self.certificate2.end_level = 999
1840        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1841            self.certificate2)
1842
1843        # Add study level to old study course
1844        studylevel = createObject(u'waeup.StudentStudyLevel')
1845        studylevel.level = 200
1846        self.student['studycourse'].addStudentStudyLevel(
1847            self.certificate, studylevel)
1848        studylevel = createObject(u'waeup.StudentStudyLevel')
1849        studylevel.level = 999
1850        self.student['studycourse'].addStudentStudyLevel(
1851            self.certificate, studylevel)
1852
1853        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1854        self.browser.open(self.student_path)
1855        self.browser.getLink("Transfer").click()
1856        self.browser.getControl(name="form.certificate").value = ['CERT2']
1857        self.browser.getControl(name="form.current_session").value = ['2011']
1858        self.browser.getControl(name="form.current_level").value = ['200']
1859        self.browser.getControl("Transfer").click()
1860        self.assertTrue(
1861            'Current level does not match certificate levels'
1862            in self.browser.contents)
1863        self.browser.getControl(name="form.current_level").value = ['999']
1864        self.browser.getControl("Transfer").click()
1865        self.assertTrue('Successfully transferred' in self.browser.contents)
1866        # The catalog has been updated
1867        cat = queryUtility(ICatalog, name='students_catalog')
1868        results = list(
1869            cat.searchResults(
1870            certcode=('CERT2', 'CERT2')))
1871        self.assertTrue(results[0] is self.student)
1872        results = list(
1873            cat.searchResults(
1874            current_session=(2011, 2011)))
1875        self.assertTrue(results[0] is self.student)
1876        # Add study level to new study course
1877        studylevel = createObject(u'waeup.StudentStudyLevel')
1878        studylevel.level = 999
1879        self.student['studycourse'].addStudentStudyLevel(
1880            self.certificate, studylevel)
1881
1882        # Edit and add pages are locked for old study courses
1883        self.browser.open(self.student_path + '/studycourse/manage')
1884        self.assertFalse('The requested form is locked' in self.browser.contents)
1885        self.browser.open(self.student_path + '/studycourse_1/manage')
1886        self.assertTrue('The requested form is locked' in self.browser.contents)
1887
1888        self.browser.open(self.student_path + '/studycourse/start_session')
1889        self.assertFalse('The requested form is locked' in self.browser.contents)
1890        self.browser.open(self.student_path + '/studycourse_1/start_session')
1891        self.assertTrue('The requested form is locked' in self.browser.contents)
1892
1893        IWorkflowState(self.student).setState('school fee paid')
1894        self.browser.open(self.student_path + '/studycourse/add')
1895        self.assertFalse('The requested form is locked' in self.browser.contents)
1896        self.browser.open(self.student_path + '/studycourse_1/add')
1897        self.assertTrue('The requested form is locked' in self.browser.contents)
1898
1899        self.browser.open(self.student_path + '/studycourse/999/manage')
1900        self.assertFalse('The requested form is locked' in self.browser.contents)
1901        self.browser.open(self.student_path + '/studycourse_1/999/manage')
1902        self.assertTrue('The requested form is locked' in self.browser.contents)
1903
1904        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
1905        self.assertFalse('The requested form is locked' in self.browser.contents)
1906        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
1907        self.assertTrue('The requested form is locked' in self.browser.contents)
1908
1909        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
1910        self.assertFalse('The requested form is locked' in self.browser.contents)
1911        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
1912        self.assertTrue('The requested form is locked' in self.browser.contents)
1913
1914        self.browser.open(self.student_path + '/studycourse/999/add')
1915        self.assertFalse('The requested form is locked' in self.browser.contents)
1916        self.browser.open(self.student_path + '/studycourse_1/999/add')
1917        self.assertTrue('The requested form is locked' in self.browser.contents)
1918
1919        self.browser.open(self.student_path + '/studycourse/999/edit')
1920        self.assertFalse('The requested form is locked' in self.browser.contents)
1921        self.browser.open(self.student_path + '/studycourse_1/999/edit')
1922        self.assertTrue('The requested form is locked' in self.browser.contents)
1923
1924        # Revert transfer
1925        self.browser.open(self.student_path + '/studycourse_1')
1926        self.browser.getLink("Reactivate").click()
1927        self.browser.getControl("Revert now").click()
1928        self.assertTrue('Previous transfer reverted' in self.browser.contents)
1929        results = list(
1930            cat.searchResults(
1931            certcode=('CERT1', 'CERT1')))
1932        self.assertTrue(results[0] is self.student)
1933        self.assertEqual([i for i in self.student.keys()],
1934            [u'accommodation', u'payments', u'studycourse'])
1935
1936    def test_login_as_student(self):
1937        # StudentImpersonators can login as student
1938        # Create clearance officer
1939        self.app['users'].addUser('mrofficer', 'mrofficersecret')
1940        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
1941        self.app['users']['mrofficer'].title = 'Harry Actor'
1942        prmglobal = IPrincipalRoleManager(self.app)
1943        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
1944        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
1945        # Login as student impersonator
1946        self.browser.open(self.login_path)
1947        self.browser.getControl(name="form.login").value = 'mrofficer'
1948        self.browser.getControl(name="form.password").value = 'mrofficersecret'
1949        self.browser.getControl("Login").click()
1950        self.assertMatches('...You logged in...', self.browser.contents)
1951        self.browser.open(self.student_path)
1952        self.browser.getLink("Login as").click()
1953        self.browser.getControl("Set password now").click()
1954        temp_password = self.browser.getControl(name='form.password').value
1955        self.browser.getControl("Login now").click()
1956        self.assertMatches(
1957            '...You successfully logged in as...', self.browser.contents)
1958        # We are logged in as student and can see the 'My Data' tab
1959        self.assertMatches(
1960            '...<a href="#" class="dropdown-toggle">My Data</a>...',
1961            self.browser.contents)
1962        self.browser.getLink("Logout").click()
1963        # The student can't login with the original password ...
1964        self.browser.open(self.login_path)
1965        self.browser.getControl(name="form.login").value = self.student_id
1966        self.browser.getControl(name="form.password").value = 'spwd'
1967        self.browser.getControl("Login").click()
1968        self.assertMatches(
1969            '...Your account has been temporarily deactivated...',
1970            self.browser.contents)
1971        # ... but with the temporary password
1972        self.browser.open(self.login_path)
1973        self.browser.getControl(name="form.login").value = self.student_id
1974        self.browser.getControl(name="form.password").value = temp_password
1975        self.browser.getControl("Login").click()
1976        self.assertMatches('...You logged in...', self.browser.contents)
1977        # Creation of temp_password is properly logged
1978        logfile = os.path.join(
1979            self.app['datacenter'].storage, 'logs', 'students.log')
1980        logcontent = open(logfile).read()
1981        self.assertTrue(
1982            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
1983            'temp_password generated: %s' % temp_password in logcontent)
1984
1985    def test_transcripts(self):
1986        studylevel = createObject(u'waeup.StudentStudyLevel')
1987        studylevel.level = 100
1988        studylevel.level_session = 2005
1989        self.student['studycourse'].entry_mode = 'ug_ft'
1990        self.student['studycourse'].addStudentStudyLevel(
1991            self.certificate, studylevel)
1992        studylevel2 = createObject(u'waeup.StudentStudyLevel')
1993        studylevel2.level = 110
1994        studylevel2.level_session = 2006
1995        self.student['studycourse'].addStudentStudyLevel(
1996            self.certificate, studylevel2)
1997        # Add second course (COURSE has been added automatically)
1998        courseticket = createObject('waeup.CourseTicket')
1999        courseticket.code = 'ANYCODE'
2000        courseticket.title = u'Any TITLE'
2001        courseticket.credits = 13
2002        courseticket.score = 66
2003        courseticket.semester = 1
2004        courseticket.dcode = u'ANYDCODE'
2005        courseticket.fcode = u'ANYFCODE'
2006        self.student['studycourse']['110']['COURSE2'] = courseticket
2007        self.student['studycourse']['100']['COURSE1'].score = 55
2008        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2009        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2010        # Get transcript data
2011        td = self.student['studycourse'].getTranscriptData()
2012        self.assertEqual(td[0][0]['level_key'], '100')
2013        self.assertEqual(td[0][0]['sgpa'], 3.0)
2014        self.assertEqual(td[0][0]['level'].level, 100)
2015        self.assertEqual(td[0][0]['level'].level_session, 2005)
2016        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2017        self.assertEqual(td[0][1]['level_key'], '110')
2018        self.assertEqual(td[0][1]['sgpa'], 4.0)
2019        self.assertEqual(td[0][1]['level'].level, 110)
2020        self.assertEqual(td[0][1]['level'].level_session, 2006)
2021        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2022        self.assertEqual(td[1], 3.57)
2023        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2024        self.browser.open(self.student_path + '/studycourse/transcript')
2025        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2026        self.assertTrue('Transcript' in self.browser.contents)
2027        # Officers can open the pdf transcript
2028        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2029        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2030        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2031        path = os.path.join(samples_dir(), 'transcript.pdf')
2032        open(path, 'wb').write(self.browser.contents)
2033        print "Sample PDF transcript.pdf written to %s" % path
2034
2035    def test_process_transcript_request(self):
2036        IWorkflowState(self.student).setState('transcript requested')
2037        notify(grok.ObjectModifiedEvent(self.student))
2038        self.student.transcript_comment = (
2039            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2040            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2041            'Address line2\n\n')
2042        # Create transcript officer
2043        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2044        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2045        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2046        prmglobal = IPrincipalRoleManager(self.app)
2047        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2048        # Login as transcript officer
2049        self.browser.open(self.login_path)
2050        self.browser.getControl(name="form.login").value = 'mrtranscript'
2051        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2052        self.browser.getControl("Login").click()
2053        self.assertMatches('...You logged in...', self.browser.contents)
2054        # Officer can see his roles
2055        self.browser.getLink("My Roles").click()
2056        self.assertMatches(
2057            '...<div>Transcript Officer</div>...',
2058            self.browser.contents)
2059        # Officer can search for students in state 'transcripr requested'
2060        self.browser.open(self.container_path)
2061        self.browser.getControl(name="searchtype").value = ['transcript']
2062        self.browser.getControl("Find student(s)").click()
2063        self.assertTrue('Anna Tester' in self.browser.contents)
2064        self.browser.getLink("K1000000").click()
2065        self.browser.getLink("Manage transcript request").click()
2066        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2067        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2068        'Address line2<br><br></p>' in self.browser.contents)
2069        self.browser.getControl(name="comment").value = (
2070            'Hello,\nYour transcript has been sent to the address provided.')
2071        self.browser.getControl("Save comment and mark as processed").click()
2072        self.assertTrue(
2073            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2074            'been sent to the address provided.\n\n'
2075            in self.student.transcript_comment)
2076        # The comment has been logged
2077        logfile = os.path.join(
2078            self.app['datacenter'].storage, 'logs', 'students.log')
2079        logcontent = open(logfile).read()
2080        self.assertTrue(
2081            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2082            'K1000000 - comment: Hello,<br>'
2083            'Your transcript has been sent to the address provided'
2084            in logcontent)
2085
2086class StudentUITests(StudentsFullSetup):
2087    # Tests for Student class views and pages
2088
2089    def test_student_change_password(self):
2090        # Students can change the password
2091        self.student.personal_updated = datetime.utcnow()
2092        self.browser.open(self.login_path)
2093        self.browser.getControl(name="form.login").value = self.student_id
2094        self.browser.getControl(name="form.password").value = 'spwd'
2095        self.browser.getControl("Login").click()
2096        self.assertEqual(self.browser.url, self.student_path)
2097        self.assertTrue('You logged in' in self.browser.contents)
2098        # Change password
2099        self.browser.getLink("Change password").click()
2100        self.browser.getControl(name="change_password").value = 'pw'
2101        self.browser.getControl(
2102            name="change_password_repeat").value = 'pw'
2103        self.browser.getControl("Save").click()
2104        self.assertTrue('Password must have at least' in self.browser.contents)
2105        self.browser.getControl(name="change_password").value = 'new_password'
2106        self.browser.getControl(
2107            name="change_password_repeat").value = 'new_passssword'
2108        self.browser.getControl("Save").click()
2109        self.assertTrue('Passwords do not match' in self.browser.contents)
2110        self.browser.getControl(name="change_password").value = 'new_password'
2111        self.browser.getControl(
2112            name="change_password_repeat").value = 'new_password'
2113        self.browser.getControl("Save").click()
2114        self.assertTrue('Password changed' in self.browser.contents)
2115        # We are still logged in. Changing the password hasn't thrown us out.
2116        self.browser.getLink("Base Data").click()
2117        self.assertEqual(self.browser.url, self.student_path)
2118        # We can logout
2119        self.browser.getLink("Logout").click()
2120        self.assertTrue('You have been logged out' in self.browser.contents)
2121        self.assertEqual(self.browser.url, 'http://localhost/app')
2122        # We can login again with the new password
2123        self.browser.getLink("Login").click()
2124        self.browser.open(self.login_path)
2125        self.browser.getControl(name="form.login").value = self.student_id
2126        self.browser.getControl(name="form.password").value = 'new_password'
2127        self.browser.getControl("Login").click()
2128        self.assertEqual(self.browser.url, self.student_path)
2129        self.assertTrue('You logged in' in self.browser.contents)
2130        return
2131
2132    def test_setpassword(self):
2133        # Set password for first-time access
2134        student = Student()
2135        student.reg_number = u'123456'
2136        student.firstname = u'Klaus'
2137        student.lastname = u'Tester'
2138        self.app['students'].addStudent(student)
2139        setpassword_path = 'http://localhost/app/setpassword'
2140        student_path = 'http://localhost/app/students/%s' % student.student_id
2141        self.browser.open(setpassword_path)
2142        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2143        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2144        self.browser.getControl(name="reg_number").value = '223456'
2145        self.browser.getControl("Set").click()
2146        self.assertMatches('...No student found...',
2147                           self.browser.contents)
2148        self.browser.getControl(name="reg_number").value = '123456'
2149        self.browser.getControl(name="ac_number").value = '999999'
2150        self.browser.getControl("Set").click()
2151        self.assertMatches('...Access code is invalid...',
2152                           self.browser.contents)
2153        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2154        self.browser.getControl("Set").click()
2155        self.assertMatches('...Password has been set. Your Student Id is...',
2156                           self.browser.contents)
2157        self.browser.getControl("Set").click()
2158        self.assertMatches(
2159            '...Password has already been set. Your Student Id is...',
2160            self.browser.contents)
2161        existing_pwdpin = self.pwdpins[1]
2162        parts = existing_pwdpin.split('-')[1:]
2163        existing_pwdseries, existing_pwdnumber = parts
2164        self.browser.getControl(name="ac_series").value = existing_pwdseries
2165        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2166        self.browser.getControl(name="reg_number").value = '123456'
2167        self.browser.getControl("Set").click()
2168        self.assertMatches(
2169            '...You are using the wrong Access Code...',
2170            self.browser.contents)
2171        # The student can login with the new credentials
2172        self.browser.open(self.login_path)
2173        self.browser.getControl(name="form.login").value = student.student_id
2174        self.browser.getControl(
2175            name="form.password").value = self.existing_pwdnumber
2176        self.browser.getControl("Login").click()
2177        self.assertEqual(self.browser.url, student_path)
2178        self.assertTrue('You logged in' in self.browser.contents)
2179        return
2180
2181    def test_student_login(self):
2182        # Student cant login if their password is not set
2183        self.student.password = None
2184        self.browser.open(self.login_path)
2185        self.browser.getControl(name="form.login").value = self.student_id
2186        self.browser.getControl(name="form.password").value = 'spwd'
2187        self.browser.getControl("Login").click()
2188        self.assertTrue(
2189            'You entered invalid credentials.' in self.browser.contents)
2190        # We set the password again
2191        IUserAccount(
2192            self.app['students'][self.student_id]).setPassword('spwd')
2193        # Students can't login if their account is suspended/deactivated
2194        self.student.suspended = True
2195        self.browser.open(self.login_path)
2196        self.browser.getControl(name="form.login").value = self.student_id
2197        self.browser.getControl(name="form.password").value = 'spwd'
2198        self.browser.getControl("Login").click()
2199        self.assertMatches(
2200            '...<div class="alert-message warning">'
2201            'Your account has been deactivated.</div>...', self.browser.contents)
2202        # If suspended_comment is set this message will be flashed instead
2203        self.student.suspended_comment = u'Aetsch baetsch!'
2204        self.browser.getControl(name="form.login").value = self.student_id
2205        self.browser.getControl(name="form.password").value = 'spwd'
2206        self.browser.getControl("Login").click()
2207        self.assertMatches(
2208            '...<div class="alert-message warning">Aetsch baetsch!</div>...',
2209            self.browser.contents)
2210        self.student.suspended = False
2211        # Students can't login if a temporary password has been set and
2212        # is not expired
2213        self.app['students'][self.student_id].setTempPassword(
2214            'anybody', 'temp_spwd')
2215        self.browser.open(self.login_path)
2216        self.browser.getControl(name="form.login").value = self.student_id
2217        self.browser.getControl(name="form.password").value = 'spwd'
2218        self.browser.getControl("Login").click()
2219        self.assertMatches(
2220            '...Your account has been temporarily deactivated...',
2221            self.browser.contents)
2222        # The student can login with the temporary password
2223        self.browser.open(self.login_path)
2224        self.browser.getControl(name="form.login").value = self.student_id
2225        self.browser.getControl(name="form.password").value = 'temp_spwd'
2226        self.browser.getControl("Login").click()
2227        self.assertMatches(
2228            '...You logged in...', self.browser.contents)
2229        # Student can view the base data
2230        self.browser.open(self.student_path)
2231        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2232        self.assertEqual(self.browser.url, self.student_path)
2233        # When the password expires ...
2234        delta = timedelta(minutes=11)
2235        self.app['students'][self.student_id].temp_password[
2236            'timestamp'] = datetime.utcnow() - delta
2237        self.app['students'][self.student_id]._p_changed = True
2238        # ... the student will be automatically logged out
2239        self.assertRaises(
2240            Unauthorized, self.browser.open, self.student_path)
2241        # Then the student can login with the original password
2242        self.browser.open(self.login_path)
2243        self.browser.getControl(name="form.login").value = self.student_id
2244        self.browser.getControl(name="form.password").value = 'spwd'
2245        self.browser.getControl("Login").click()
2246        self.assertMatches(
2247            '...You logged in...', self.browser.contents)
2248
2249    def test_student_clearance(self):
2250        # Student cant login if their password is not set
2251        IWorkflowInfo(self.student).fireTransition('admit')
2252        self.browser.open(self.login_path)
2253        self.browser.getControl(name="form.login").value = self.student_id
2254        self.browser.getControl(name="form.password").value = 'spwd'
2255        self.browser.getControl("Login").click()
2256        self.assertMatches(
2257            '...You logged in...', self.browser.contents)
2258        # Admitted student can upload a passport picture
2259        self.browser.open(self.student_path + '/change_portrait')
2260        ctrl = self.browser.getControl(name='passportuploadedit')
2261        file_obj = open(SAMPLE_IMAGE, 'rb')
2262        file_ctrl = ctrl.mech_control
2263        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2264        self.browser.getControl(
2265            name='upload_passportuploadedit').click()
2266        self.assertTrue(
2267            '<img align="middle" height="125px" src="passport.jpg" />'
2268            in self.browser.contents)
2269        # Students can open admission letter
2270        self.browser.getLink("Base Data").click()
2271        self.browser.getLink("Download admission letter").click()
2272        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2273        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2274        # Student can view the clearance data
2275        self.browser.open(self.student_path)
2276        self.browser.getLink("Clearance Data").click()
2277        # Student can't open clearance edit form before starting clearance
2278        self.browser.open(self.student_path + '/cedit')
2279        self.assertMatches('...The requested form is locked...',
2280                           self.browser.contents)
2281        self.browser.getLink("Clearance Data").click()
2282        self.browser.getLink("Start clearance").click()
2283        self.student.email = None
2284        # Uups, we forgot to fill the email fields
2285        self.browser.getControl("Start clearance").click()
2286        self.assertMatches('...Not all required fields filled...',
2287                           self.browser.contents)
2288        self.browser.open(self.student_path + '/edit_base')
2289        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2290        self.browser.getControl("Save").click()
2291        self.browser.open(self.student_path + '/start_clearance')
2292        self.browser.getControl(name="ac_series").value = '3'
2293        self.browser.getControl(name="ac_number").value = '4444444'
2294        self.browser.getControl("Start clearance now").click()
2295        self.assertMatches('...Activation code is invalid...',
2296                           self.browser.contents)
2297        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2298        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2299        # Owner is Hans Wurst, AC can't be invalidated
2300        self.browser.getControl("Start clearance now").click()
2301        self.assertMatches('...You are not the owner of this access code...',
2302                           self.browser.contents)
2303        # Set the correct owner
2304        self.existing_clrac.owner = self.student_id
2305        # clr_code might be set (and thus returns None) due importing
2306        # an empty clr_code column.
2307        self.student.clr_code = None
2308        self.browser.getControl("Start clearance now").click()
2309        self.assertMatches('...Clearance process has been started...',
2310                           self.browser.contents)
2311        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2312        self.browser.getControl("Save", index=0).click()
2313        # Student can view the clearance data
2314        self.browser.getLink("Clearance Data").click()
2315        # and go back to the edit form
2316        self.browser.getLink("Edit").click()
2317        # Students can upload documents
2318        ctrl = self.browser.getControl(name='birthcertificateupload')
2319        file_obj = open(SAMPLE_IMAGE, 'rb')
2320        file_ctrl = ctrl.mech_control
2321        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2322        self.browser.getControl(
2323            name='upload_birthcertificateupload').click()
2324        self.assertTrue(
2325            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2326            in self.browser.contents)
2327        # Students can open clearance slip
2328        self.browser.getLink("View").click()
2329        self.browser.getLink("Download clearance slip").click()
2330        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2331        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2332        # Students can request clearance
2333        self.browser.open(self.edit_clearance_path)
2334        self.browser.getControl("Save and request clearance").click()
2335        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2336        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2337        self.browser.getControl("Request clearance now").click()
2338        self.assertMatches('...Clearance has been requested...',
2339                           self.browser.contents)
2340        # Student can't reopen clearance form after requesting clearance
2341        self.browser.open(self.student_path + '/cedit')
2342        self.assertMatches('...The requested form is locked...',
2343                           self.browser.contents)
2344
2345    def test_student_course_registration(self):
2346        # Student cant login if their password is not set
2347        IWorkflowInfo(self.student).fireTransition('admit')
2348        self.browser.open(self.login_path)
2349        self.browser.getControl(name="form.login").value = self.student_id
2350        self.browser.getControl(name="form.password").value = 'spwd'
2351        self.browser.getControl("Login").click()
2352        # Student can't add study level if not in state 'school fee paid'
2353        self.browser.open(self.student_path + '/studycourse/add')
2354        self.assertMatches('...The requested form is locked...',
2355                           self.browser.contents)
2356        # ... and must be transferred first
2357        IWorkflowState(self.student).setState('school fee paid')
2358        # Now students can add the current study level
2359        self.browser.getLink("Study Course").click()
2360        self.browser.getLink("Add course list").click()
2361        self.assertMatches('...Add current level 100 (Year 1)...',
2362                           self.browser.contents)
2363        self.browser.getControl("Create course list now").click()
2364        # A level with one course ticket was created
2365        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2366        self.browser.getLink("100").click()
2367        self.browser.getLink("Edit course list").click()
2368        self.browser.getLink("here").click()
2369        self.browser.getControl(name="form.course").value = ['COURSE1']
2370        self.browser.getControl("Add course ticket").click()
2371        self.assertMatches('...The ticket exists...',
2372                           self.browser.contents)
2373        self.student['studycourse'].current_level = 200
2374        self.browser.getLink("Study Course").click()
2375        self.browser.getLink("Add course list").click()
2376        self.assertMatches('...Add current level 200 (Year 2)...',
2377                           self.browser.contents)
2378        self.browser.getControl("Create course list now").click()
2379        self.browser.getLink("200").click()
2380        self.browser.getLink("Edit course list").click()
2381        self.browser.getLink("here").click()
2382        self.browser.getControl(name="form.course").value = ['COURSE1']
2383        self.course.credits = 100
2384        self.browser.getControl("Add course ticket").click()
2385        self.assertMatches(
2386            '...Total credits exceed 50...', self.browser.contents)
2387        self.course.credits = 10
2388        self.browser.getControl("Add course ticket").click()
2389        self.assertMatches('...The ticket exists...',
2390                           self.browser.contents)
2391        # Indeed the ticket exists as carry-over course from level 100
2392        # since its score was 0
2393        self.assertTrue(
2394            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2395        # Students can open the pdf course registration slip
2396        self.browser.open(self.student_path + '/studycourse/200')
2397        self.browser.getLink("Download course registration slip").click()
2398        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2399        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2400        # Students can remove course tickets
2401        self.browser.open(self.student_path + '/studycourse/200/edit')
2402        self.browser.getControl("Remove selected", index=0).click()
2403        self.assertTrue('No ticket selected' in self.browser.contents)
2404        # No ticket can be selected since the carry-over course is a core course
2405        self.assertRaises(
2406            LookupError, self.browser.getControl, name='val_id')
2407        self.student['studycourse']['200']['COURSE1'].mandatory = False
2408        self.browser.open(self.student_path + '/studycourse/200/edit')
2409        # Course list can't be registered if total_credits exceeds max_credits
2410        self.student['studycourse']['200']['COURSE1'].credits = 60
2411        self.browser.getControl("Register course list").click()
2412        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2413        # Student can now remove the ticket
2414        ctrl = self.browser.getControl(name='val_id')
2415        ctrl.getControl(value='COURSE1').selected = True
2416        self.browser.getControl("Remove selected", index=0).click()
2417        self.assertTrue('Successfully removed' in self.browser.contents)
2418        # Removing course tickets is properly logged
2419        logfile = os.path.join(
2420            self.app['datacenter'].storage, 'logs', 'students.log')
2421        logcontent = open(logfile).read()
2422        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2423        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2424        # They can add the same ticket using the edit page directly.
2425        # We can do the same by adding the course on the manage page directly
2426        self.browser.getControl(name="course").value = 'COURSE1'
2427        self.browser.getControl("Add course ticket").click()
2428        # Adding course tickets is logged
2429        logfile = os.path.join(
2430            self.app['datacenter'].storage, 'logs', 'students.log')
2431        logcontent = open(logfile).read()
2432        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2433            'K1000000 - added: COURSE1|200|2004' in logcontent)
2434        # Course list can be registered
2435        self.browser.getControl("Register course list").click()
2436        self.assertTrue('Course list has been registered' in self.browser.contents)
2437        self.assertEqual(self.student.state, 'courses registered')
2438        # Students can view the transcript
2439        #self.browser.open(self.studycourse_path)
2440        #self.browser.getLink("Transcript").click()
2441        #self.browser.getLink("Academic Transcript").click()
2442        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2443        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2444        return
2445
2446    def test_postgraduate_student_access(self):
2447        self.certificate.study_mode = 'pg_ft'
2448        self.certificate.start_level = 999
2449        self.certificate.end_level = 999
2450        self.student['studycourse'].current_level = 999
2451        IWorkflowState(self.student).setState('school fee paid')
2452        self.browser.open(self.login_path)
2453        self.browser.getControl(name="form.login").value = self.student_id
2454        self.browser.getControl(name="form.password").value = 'spwd'
2455        self.browser.getControl("Login").click()
2456        self.assertTrue(
2457            'You logged in.' in self.browser.contents)
2458        # Now students can add the current study level
2459        self.browser.getLink("Study Course").click()
2460        self.browser.getLink("Add course list").click()
2461        self.assertMatches('...Add current level Postgraduate Level...',
2462                           self.browser.contents)
2463        self.browser.getControl("Create course list now").click()
2464        # A level with one course ticket was created
2465        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2466        self.browser.getLink("999").click()
2467        self.browser.getLink("Edit course list").click()
2468        self.browser.getLink("here").click()
2469        self.browser.getControl(name="form.course").value = ['COURSE1']
2470        self.browser.getControl("Add course ticket").click()
2471        self.assertMatches('...Successfully added COURSE1...',
2472                           self.browser.contents)
2473        # Postgraduate students can't register course lists
2474        self.browser.getControl("Register course list").click()
2475        self.assertTrue("your course list can't bee registered"
2476            in self.browser.contents)
2477        self.assertEqual(self.student.state, 'school fee paid')
2478        return
2479
2480    def test_student_clearance_wo_clrcode(self):
2481        IWorkflowState(self.student).setState('clearance started')
2482        self.browser.open(self.login_path)
2483        self.browser.getControl(name="form.login").value = self.student_id
2484        self.browser.getControl(name="form.password").value = 'spwd'
2485        self.browser.getControl("Login").click()
2486        self.student.clearance_locked = False
2487        self.browser.open(self.edit_clearance_path)
2488        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2489        self.browser.getControl("Save and request clearance").click()
2490        self.assertMatches('...Clearance has been requested...',
2491                           self.browser.contents)
2492
2493    def test_student_clearance_payment(self):
2494        # Login
2495        self.browser.open(self.login_path)
2496        self.browser.getControl(name="form.login").value = self.student_id
2497        self.browser.getControl(name="form.password").value = 'spwd'
2498        self.browser.getControl("Login").click()
2499
2500        # Students can add online clearance payment tickets
2501        self.browser.open(self.payments_path + '/addop')
2502        self.browser.getControl(name="form.p_category").value = ['clearance']
2503        self.browser.getControl("Create ticket").click()
2504        self.assertMatches('...ticket created...',
2505                           self.browser.contents)
2506
2507        # Students can't approve the payment
2508        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2509        ctrl = self.browser.getControl(name='val_id')
2510        value = ctrl.options[0]
2511        self.browser.getLink(value).click()
2512        payment_url = self.browser.url
2513        self.assertRaises(
2514            Unauthorized, self.browser.open, payment_url + '/approve')
2515        # In the base package they can 'use' a fake approval view.
2516        # XXX: I tried to use
2517        # self.student['payments'][value].approveStudentPayment() instead.
2518        # But this function fails in
2519        # w.k.accesscodes.accesscode.create_accesscode.
2520        # grok.getSite returns None in tests.
2521        self.browser.open(payment_url + '/fake_approve')
2522        self.assertMatches('...Payment approved...',
2523                          self.browser.contents)
2524        expected = '''...
2525        <td>
2526          <span>Paid</span>
2527        </td>...'''
2528        expected = '''...
2529        <td>
2530          <span>Paid</span>
2531        </td>...'''
2532        self.assertMatches(expected,self.browser.contents)
2533        payment_id = self.student['payments'].keys()[0]
2534        payment = self.student['payments'][payment_id]
2535        self.assertEqual(payment.p_state, 'paid')
2536        self.assertEqual(payment.r_amount_approved, 3456.0)
2537        self.assertEqual(payment.r_code, 'AP')
2538        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2539        # The new CLR-0 pin has been created
2540        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2541        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2542        ac = self.app['accesscodes']['CLR-0'][pin]
2543        self.assertEqual(ac.owner, self.student_id)
2544        self.assertEqual(ac.cost, 3456.0)
2545
2546        # Students can open the pdf payment slip
2547        self.browser.open(payment_url + '/payment_slip.pdf')
2548        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2549        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2550
2551        # The new CLR-0 pin can be used for starting clearance
2552        # but they have to upload a passport picture first
2553        # which is only possible in state admitted
2554        self.browser.open(self.student_path + '/change_portrait')
2555        self.assertMatches('...form is locked...',
2556                          self.browser.contents)
2557        IWorkflowInfo(self.student).fireTransition('admit')
2558        self.browser.open(self.student_path + '/change_portrait')
2559        image = open(SAMPLE_IMAGE, 'rb')
2560        ctrl = self.browser.getControl(name='passportuploadedit')
2561        file_ctrl = ctrl.mech_control
2562        file_ctrl.add_file(image, filename='my_photo.jpg')
2563        self.browser.getControl(
2564            name='upload_passportuploadedit').click()
2565        self.browser.open(self.student_path + '/start_clearance')
2566        parts = pin.split('-')[1:]
2567        clrseries, clrnumber = parts
2568        self.browser.getControl(name="ac_series").value = clrseries
2569        self.browser.getControl(name="ac_number").value = clrnumber
2570        self.browser.getControl("Start clearance now").click()
2571        self.assertMatches('...Clearance process has been started...',
2572                           self.browser.contents)
2573
2574    def test_student_schoolfee_payment(self):
2575        configuration = createObject('waeup.SessionConfiguration')
2576        configuration.academic_session = 2005
2577        self.app['configuration'].addSessionConfiguration(configuration)
2578        # Login
2579        self.browser.open(self.login_path)
2580        self.browser.getControl(name="form.login").value = self.student_id
2581        self.browser.getControl(name="form.password").value = 'spwd'
2582        self.browser.getControl("Login").click()
2583
2584        # Students can add online school fee payment tickets.
2585        IWorkflowState(self.student).setState('returning')
2586        self.browser.open(self.payments_path)
2587        self.assertRaises(
2588            LookupError, self.browser.getControl, name='val_id')
2589        self.browser.getLink("Add current session payment ticket").click()
2590        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2591        self.browser.getControl("Create ticket").click()
2592        self.assertMatches('...ticket created...',
2593                           self.browser.contents)
2594        ctrl = self.browser.getControl(name='val_id')
2595        value = ctrl.options[0]
2596        self.browser.getLink(value).click()
2597        self.assertMatches('...Amount Authorized...',
2598                           self.browser.contents)
2599        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2600        # Payment session and will be calculated as defined
2601        # in w.k.students.utils because we set changed the state
2602        # to returning
2603        self.assertEqual(self.student['payments'][value].p_session, 2005)
2604        self.assertEqual(self.student['payments'][value].p_level, 200)
2605
2606        # Student is the payer of the payment ticket.
2607        payer = IPayer(self.student['payments'][value])
2608        self.assertEqual(payer.display_fullname, 'Anna Tester')
2609        self.assertEqual(payer.id, self.student_id)
2610        self.assertEqual(payer.faculty, 'fac1')
2611        self.assertEqual(payer.department, 'dep1')
2612
2613        # We simulate the approval
2614        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2615        self.browser.open(self.browser.url + '/fake_approve')
2616        self.assertMatches('...Payment approved...',
2617                          self.browser.contents)
2618
2619        # The new SFE-0 pin can be used for starting new session
2620        self.browser.open(self.studycourse_path)
2621        self.browser.getLink('Start new session').click()
2622        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2623        parts = pin.split('-')[1:]
2624        sfeseries, sfenumber = parts
2625        self.browser.getControl(name="ac_series").value = sfeseries
2626        self.browser.getControl(name="ac_number").value = sfenumber
2627        self.browser.getControl("Start now").click()
2628        self.assertMatches('...Session started...',
2629                           self.browser.contents)
2630        self.assertTrue(self.student.state == 'school fee paid')
2631        return
2632
2633    def test_student_bedallocation_payment(self):
2634        # Login
2635        self.browser.open(self.login_path)
2636        self.browser.getControl(name="form.login").value = self.student_id
2637        self.browser.getControl(name="form.password").value = 'spwd'
2638        self.browser.getControl("Login").click()
2639        self.browser.open(self.payments_path)
2640        self.browser.open(self.payments_path + '/addop')
2641        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2642        self.browser.getControl("Create ticket").click()
2643        self.assertMatches('...ticket created...',
2644                           self.browser.contents)
2645        # Students can remove only online payment tickets which have
2646        # not received a valid callback
2647        self.browser.open(self.payments_path)
2648        ctrl = self.browser.getControl(name='val_id')
2649        value = ctrl.options[0]
2650        ctrl.getControl(value=value).selected = True
2651        self.browser.getControl("Remove selected", index=0).click()
2652        self.assertTrue('Successfully removed' in self.browser.contents)
2653
2654    def test_student_maintenance_payment(self):
2655        # Login
2656        self.browser.open(self.login_path)
2657        self.browser.getControl(name="form.login").value = self.student_id
2658        self.browser.getControl(name="form.password").value = 'spwd'
2659        self.browser.getControl("Login").click()
2660        self.browser.open(self.payments_path)
2661        self.browser.open(self.payments_path + '/addop')
2662        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2663        self.browser.getControl("Create ticket").click()
2664        self.assertMatches('...You have not yet booked accommodation...',
2665                           self.browser.contents)
2666        # We continue this test in test_student_accommodation
2667
2668    def test_student_previous_payments(self):
2669        configuration = createObject('waeup.SessionConfiguration')
2670        configuration.academic_session = 2000
2671        configuration.clearance_fee = 3456.0
2672        configuration.booking_fee = 123.4
2673        self.app['configuration'].addSessionConfiguration(configuration)
2674        configuration2 = createObject('waeup.SessionConfiguration')
2675        configuration2.academic_session = 2003
2676        configuration2.clearance_fee = 3456.0
2677        configuration2.booking_fee = 123.4
2678        self.app['configuration'].addSessionConfiguration(configuration2)
2679        configuration3 = createObject('waeup.SessionConfiguration')
2680        configuration3.academic_session = 2005
2681        configuration3.clearance_fee = 3456.0
2682        configuration3.booking_fee = 123.4
2683        self.app['configuration'].addSessionConfiguration(configuration3)
2684        self.student['studycourse'].entry_session = 2002
2685
2686        # Login
2687        self.browser.open(self.login_path)
2688        self.browser.getControl(name="form.login").value = self.student_id
2689        self.browser.getControl(name="form.password").value = 'spwd'
2690        self.browser.getControl("Login").click()
2691
2692        # Students can add previous school fee payment tickets in any state.
2693        IWorkflowState(self.student).setState('courses registered')
2694        self.browser.open(self.payments_path)
2695        self.browser.getLink("Add previous session payment ticket").click()
2696
2697        # Previous session payment form is provided
2698        self.assertEqual(self.student.current_session, 2004)
2699        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2700        self.browser.getControl(name="form.p_session").value = ['2000']
2701        self.browser.getControl(name="form.p_level").value = ['300']
2702        self.browser.getControl("Create ticket").click()
2703        self.assertMatches('...The previous session must not fall below...',
2704                           self.browser.contents)
2705        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2706        self.browser.getControl(name="form.p_session").value = ['2005']
2707        self.browser.getControl(name="form.p_level").value = ['300']
2708        self.browser.getControl("Create ticket").click()
2709        self.assertMatches('...This is not a previous session...',
2710                           self.browser.contents)
2711        # Students can pay current session school fee.
2712        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2713        self.browser.getControl(name="form.p_session").value = ['2004']
2714        self.browser.getControl(name="form.p_level").value = ['300']
2715        self.browser.getControl("Create ticket").click()
2716        self.assertMatches('...ticket created...',
2717                           self.browser.contents)
2718        ctrl = self.browser.getControl(name='val_id')
2719        value = ctrl.options[0]
2720        self.browser.getLink(value).click()
2721        self.assertMatches('...Amount Authorized...',
2722                           self.browser.contents)
2723        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2724
2725        # Payment session is properly set
2726        self.assertEqual(self.student['payments'][value].p_session, 2004)
2727        self.assertEqual(self.student['payments'][value].p_level, 300)
2728
2729        # We simulate the approval
2730        self.browser.open(self.browser.url + '/fake_approve')
2731        self.assertMatches('...Payment approved...',
2732                          self.browser.contents)
2733
2734        # No AC has been created
2735        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2736        self.assertTrue(self.student['payments'][value].ac is None)
2737
2738        # Current payment flag is set False
2739        self.assertFalse(self.student['payments'][value].p_current)
2740
2741        # Button and form are not available for students who are in
2742        # states up to cleared
2743        self.student['studycourse'].entry_session = 2004
2744        IWorkflowState(self.student).setState('cleared')
2745        self.browser.open(self.payments_path)
2746        self.assertFalse(
2747            "Add previous session payment ticket" in self.browser.contents)
2748        self.browser.open(self.payments_path + '/addpp')
2749        self.assertTrue(
2750            "No previous payment to be made" in self.browser.contents)
2751        return
2752
2753    def test_postgraduate_student_payments(self):
2754        configuration = createObject('waeup.SessionConfiguration')
2755        configuration.academic_session = 2005
2756        self.app['configuration'].addSessionConfiguration(configuration)
2757        self.certificate.study_mode = 'pg_ft'
2758        self.certificate.start_level = 999
2759        self.certificate.end_level = 999
2760        self.student['studycourse'].current_level = 999
2761        # Login
2762        self.browser.open(self.login_path)
2763        self.browser.getControl(name="form.login").value = self.student_id
2764        self.browser.getControl(name="form.password").value = 'spwd'
2765        self.browser.getControl("Login").click()
2766        # Students can add online school fee payment tickets.
2767        IWorkflowState(self.student).setState('cleared')
2768        self.browser.open(self.payments_path)
2769        self.browser.getLink("Add current session payment ticket").click()
2770        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2771        self.browser.getControl("Create ticket").click()
2772        self.assertMatches('...ticket created...',
2773                           self.browser.contents)
2774        ctrl = self.browser.getControl(name='val_id')
2775        value = ctrl.options[0]
2776        self.browser.getLink(value).click()
2777        self.assertMatches('...Amount Authorized...',
2778                           self.browser.contents)
2779        # Payment session and level are current ones.
2780        # Postgrads have to pay school_fee_1.
2781        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2782        self.assertEqual(self.student['payments'][value].p_session, 2004)
2783        self.assertEqual(self.student['payments'][value].p_level, 999)
2784
2785        # We simulate the approval
2786        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2787        self.browser.open(self.browser.url + '/fake_approve')
2788        self.assertMatches('...Payment approved...',
2789                          self.browser.contents)
2790
2791        # The new SFE-0 pin can be used for starting session
2792        self.browser.open(self.studycourse_path)
2793        self.browser.getLink('Start new session').click()
2794        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2795        parts = pin.split('-')[1:]
2796        sfeseries, sfenumber = parts
2797        self.browser.getControl(name="ac_series").value = sfeseries
2798        self.browser.getControl(name="ac_number").value = sfenumber
2799        self.browser.getControl("Start now").click()
2800        self.assertMatches('...Session started...',
2801                           self.browser.contents)
2802        self.assertTrue(self.student.state == 'school fee paid')
2803
2804        # Postgrad students do not need to register courses the
2805        # can just pay for the next session.
2806        self.browser.open(self.payments_path)
2807        # Remove first payment to be sure that we access the right ticket
2808        del self.student['payments'][value]
2809        self.browser.getLink("Add current session payment ticket").click()
2810        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2811        self.browser.getControl("Create ticket").click()
2812        ctrl = self.browser.getControl(name='val_id')
2813        value = ctrl.options[0]
2814        self.browser.getLink(value).click()
2815        # Payment session has increased by one, payment level remains the same.
2816        # Returning Postgraduates have to pay school_fee_2.
2817        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2818        self.assertEqual(self.student['payments'][value].p_session, 2005)
2819        self.assertEqual(self.student['payments'][value].p_level, 999)
2820
2821        # Student is still in old session
2822        self.assertEqual(self.student.current_session, 2004)
2823
2824        # We do not need to pay the ticket if any other
2825        # SFE pin is provided
2826        pin_container = self.app['accesscodes']
2827        pin_container.createBatch(
2828            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2829        pin = pin_container['SFE-1'].values()[0].representation
2830        sfeseries, sfenumber = pin.split('-')[1:]
2831        # The new SFE-1 pin can be used for starting new session
2832        self.browser.open(self.studycourse_path)
2833        self.browser.getLink('Start new session').click()
2834        self.browser.getControl(name="ac_series").value = sfeseries
2835        self.browser.getControl(name="ac_number").value = sfenumber
2836        self.browser.getControl("Start now").click()
2837        self.assertMatches('...Session started...',
2838                           self.browser.contents)
2839        self.assertTrue(self.student.state == 'school fee paid')
2840        # Student is in new session
2841        self.assertEqual(self.student.current_session, 2005)
2842        self.assertEqual(self.student['studycourse'].current_level, 999)
2843        return
2844
2845    def test_student_accommodation(self):
2846        # Login
2847        self.browser.open(self.login_path)
2848        self.browser.getControl(name="form.login").value = self.student_id
2849        self.browser.getControl(name="form.password").value = 'spwd'
2850        self.browser.getControl("Login").click()
2851
2852        # Students can add online booking fee payment tickets and open the
2853        # callback view (see test_manage_payments)
2854        self.browser.getLink("Payments").click()
2855        self.browser.getLink("Add current session payment ticket").click()
2856        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2857        self.browser.getControl("Create ticket").click()
2858        ctrl = self.browser.getControl(name='val_id')
2859        value = ctrl.options[0]
2860        self.browser.getLink(value).click()
2861        self.browser.open(self.browser.url + '/fake_approve')
2862        # The new HOS-0 pin has been created
2863        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2864        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2865        ac = self.app['accesscodes']['HOS-0'][pin]
2866        parts = pin.split('-')[1:]
2867        sfeseries, sfenumber = parts
2868
2869        # Students can use HOS code and book a bed space with it ...
2870        self.browser.open(self.acco_path)
2871        # ... but not if booking period has expired ...
2872        self.app['hostels'].enddate = datetime.now(pytz.utc)
2873        self.browser.getLink("Book accommodation").click()
2874        self.assertMatches('...Outside booking period: ...',
2875                           self.browser.contents)
2876        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2877        # ... or student is not the an allowed state ...
2878        self.browser.getLink("Book accommodation").click()
2879        self.assertMatches('...You are in the wrong...',
2880                           self.browser.contents)
2881        IWorkflowInfo(self.student).fireTransition('admit')
2882        self.browser.getLink("Book accommodation").click()
2883        self.assertMatches('...Activation Code:...',
2884                           self.browser.contents)
2885        # Student can't used faked ACs ...
2886        self.browser.getControl(name="ac_series").value = u'nonsense'
2887        self.browser.getControl(name="ac_number").value = sfenumber
2888        self.browser.getControl("Create bed ticket").click()
2889        self.assertMatches('...Activation code is invalid...',
2890                           self.browser.contents)
2891        # ... or ACs owned by somebody else.
2892        ac.owner = u'Anybody'
2893        self.browser.getControl(name="ac_series").value = sfeseries
2894        self.browser.getControl(name="ac_number").value = sfenumber
2895        self.browser.getControl("Create bed ticket").click()
2896        self.assertMatches('...You are not the owner of this access code...',
2897                           self.browser.contents)
2898        ac.owner = self.student_id
2899        self.browser.getControl(name="ac_series").value = sfeseries
2900        self.browser.getControl(name="ac_number").value = sfenumber
2901        self.browser.getControl("Create bed ticket").click()
2902        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2903                           self.browser.contents)
2904
2905        # Bed has been allocated
2906        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2907        self.assertTrue(bed.owner == self.student_id)
2908
2909        # BedTicketAddPage is now blocked
2910        self.browser.getLink("Book accommodation").click()
2911        self.assertMatches('...You already booked a bed space...',
2912            self.browser.contents)
2913
2914        # The bed ticket displays the data correctly
2915        self.browser.open(self.acco_path + '/2004')
2916        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2917                           self.browser.contents)
2918        self.assertMatches('...2004/2005...', self.browser.contents)
2919        self.assertMatches('...regular_male_fr...', self.browser.contents)
2920        self.assertMatches('...%s...' % pin, self.browser.contents)
2921
2922        # Students can open the pdf slip
2923        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2924        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2925        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2926
2927        # Students can't relocate themselves
2928        self.assertFalse('Relocate' in self.browser.contents)
2929        relocate_path = self.acco_path + '/2004/relocate'
2930        self.assertRaises(
2931            Unauthorized, self.browser.open, relocate_path)
2932
2933        # Students can't the Remove button and check boxes
2934        self.browser.open(self.acco_path)
2935        self.assertFalse('Remove' in self.browser.contents)
2936        self.assertFalse('val_id' in self.browser.contents)
2937
2938        # Students can pay maintenance fee now
2939        self.browser.open(self.payments_path)
2940        self.browser.open(self.payments_path + '/addop')
2941        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2942        self.browser.getControl("Create ticket").click()
2943        self.assertMatches('...Payment ticket created...',
2944                           self.browser.contents)
2945
2946        ctrl = self.browser.getControl(name='val_id')
2947        value = ctrl.options[0]
2948        # Maintennace fee is taken from the hostel object
2949        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
2950        # If the hostel's maintenance fee isn't set, the fee is
2951        # taken from the session configuration object.
2952        self.app['hostels']['hall-1'].maint_fee = 0.0
2953        self.browser.open(self.payments_path + '/addop')
2954        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2955        self.browser.getControl("Create ticket").click()
2956        ctrl = self.browser.getControl(name='val_id')
2957        value = ctrl.options[1]
2958        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
2959        return
2960
2961    def test_change_password_request(self):
2962        self.browser.open('http://localhost/app/changepw')
2963        self.browser.getControl(name="form.identifier").value = '123'
2964        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2965        self.browser.getControl("Send login credentials").click()
2966        self.assertTrue('An email with' in self.browser.contents)
2967
2968    def test_student_expired_personal_data(self):
2969        # Login
2970        IWorkflowState(self.student).setState('school fee paid')
2971        delta = timedelta(days=180)
2972        self.student.personal_updated = datetime.utcnow() - delta
2973        self.browser.open(self.login_path)
2974        self.browser.getControl(name="form.login").value = self.student_id
2975        self.browser.getControl(name="form.password").value = 'spwd'
2976        self.browser.getControl("Login").click()
2977        self.assertEqual(self.browser.url, self.student_path)
2978        self.assertTrue(
2979            'You logged in' in self.browser.contents)
2980        # Students don't see personal_updated field in edit form
2981        self.browser.open(self.edit_personal_path)
2982        self.assertFalse('Updated' in self.browser.contents)
2983        self.browser.open(self.personal_path)
2984        self.assertTrue('Updated' in self.browser.contents)
2985        self.browser.getLink("Logout").click()
2986        delta = timedelta(days=181)
2987        self.student.personal_updated = datetime.utcnow() - delta
2988        self.browser.open(self.login_path)
2989        self.browser.getControl(name="form.login").value = self.student_id
2990        self.browser.getControl(name="form.password").value = 'spwd'
2991        self.browser.getControl("Login").click()
2992        self.assertEqual(self.browser.url, self.edit_personal_path)
2993        self.assertTrue(
2994            'Your personal data record is outdated.' in self.browser.contents)
2995
2996    def test_setReturningData(self):
2997        utils = getUtility(IStudentsUtils)
2998        self.student['studycourse'].current_level = 600
2999        utils.setReturningData(self.student)
3000        # The new level exceeds the certificates end_level.
3001        # In this case current_level remains unchanged and no error is raised.
3002        self.assertEqual(self.student['studycourse'].current_level, 600)
3003
3004    def test_request_transcript(self):
3005        IWorkflowState(self.student).setState('graduated')
3006        self.browser.open(self.login_path)
3007        self.browser.getControl(name="form.login").value = self.student_id
3008        self.browser.getControl(name="form.password").value = 'spwd'
3009        self.browser.getControl("Login").click()
3010        self.assertMatches(
3011            '...You logged in...', self.browser.contents)
3012        # Create payment ticket
3013        self.browser.open(self.payments_path)
3014        self.browser.open(self.payments_path + '/addop')
3015        self.browser.getControl(name="form.p_category").value = ['transcript']
3016        self.browser.getControl("Create ticket").click()
3017        ctrl = self.browser.getControl(name='val_id')
3018        value = ctrl.options[0]
3019        self.browser.getLink(value).click()
3020        self.assertMatches('...Amount Authorized...',
3021                           self.browser.contents)
3022        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3023        # Student is the payer of the payment ticket.
3024        payer = IPayer(self.student['payments'][value])
3025        self.assertEqual(payer.display_fullname, 'Anna Tester')
3026        self.assertEqual(payer.id, self.student_id)
3027        self.assertEqual(payer.faculty, 'fac1')
3028        self.assertEqual(payer.department, 'dep1')
3029        # We simulate the approval and fetch the pin
3030        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3031        self.browser.open(self.browser.url + '/fake_approve')
3032        self.assertMatches('...Payment approved...',
3033                          self.browser.contents)
3034        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3035        parts = pin.split('-')[1:]
3036        tscseries, tscnumber = parts
3037        # Student can use the pin to send the transcript request
3038        self.browser.open(self.student_path)
3039        self.browser.getLink("Request transcript").click()
3040        self.browser.getControl(name="ac_series").value = tscseries
3041        self.browser.getControl(name="ac_number").value = tscnumber
3042        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3043        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3044        self.browser.getControl("Submit").click()
3045        self.assertMatches('...Transcript processing has been started...',
3046                          self.browser.contents)
3047        self.assertEqual(self.student.state, 'transcript requested')
3048        self.assertMatches(
3049            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3050            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3051            'Address line2\n\n', self.student.transcript_comment)
3052        # The comment has been logged
3053        logfile = os.path.join(
3054            self.app['datacenter'].storage, 'logs', 'students.log')
3055        logcontent = open(logfile).read()
3056        self.assertTrue(
3057            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3058            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3059            in logcontent)
3060
3061class StudentRequestPWTests(StudentsFullSetup):
3062    # Tests for student registration
3063
3064    layer = FunctionalLayer
3065
3066    def test_request_pw(self):
3067        # Student with wrong number can't be found.
3068        self.browser.open('http://localhost/app/requestpw')
3069        self.browser.getControl(name="form.firstname").value = 'Anna'
3070        self.browser.getControl(name="form.number").value = 'anynumber'
3071        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3072        self.browser.getControl("Send login credentials").click()
3073        self.assertTrue('No student record found.'
3074            in self.browser.contents)
3075        # Anonymous is not informed that firstname verification failed.
3076        # It seems that the record doesn't exist.
3077        self.browser.open('http://localhost/app/requestpw')
3078        self.browser.getControl(name="form.firstname").value = 'Johnny'
3079        self.browser.getControl(name="form.number").value = '123'
3080        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3081        self.browser.getControl("Send login credentials").click()
3082        self.assertTrue('No student record found.'
3083            in self.browser.contents)
3084        # Even with the correct firstname we can't register if a
3085        # password has been set and used.
3086        self.browser.getControl(name="form.firstname").value = 'Anna'
3087        self.browser.getControl(name="form.number").value = '123'
3088        self.browser.getControl("Send login credentials").click()
3089        self.assertTrue('Your password has already been set and used.'
3090            in self.browser.contents)
3091        self.browser.open('http://localhost/app/requestpw')
3092        self.app['students'][self.student_id].password = None
3093        # The firstname field, used for verification, is not case-sensitive.
3094        self.browser.getControl(name="form.firstname").value = 'aNNa'
3095        self.browser.getControl(name="form.number").value = '123'
3096        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3097        self.browser.getControl("Send login credentials").click()
3098        # Yeah, we succeded ...
3099        self.assertTrue('Your password request was successful.'
3100            in self.browser.contents)
3101        # We can also use the matric_number instead.
3102        self.browser.open('http://localhost/app/requestpw')
3103        self.browser.getControl(name="form.firstname").value = 'aNNa'
3104        self.browser.getControl(name="form.number").value = '234'
3105        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3106        self.browser.getControl("Send login credentials").click()
3107        self.assertTrue('Your password request was successful.'
3108            in self.browser.contents)
3109        # ... and  student can be found in the catalog via the email address
3110        cat = queryUtility(ICatalog, name='students_catalog')
3111        results = list(
3112            cat.searchResults(
3113            email=('new@yy.zz', 'new@yy.zz')))
3114        self.assertEqual(self.student,results[0])
3115        logfile = os.path.join(
3116            self.app['datacenter'].storage, 'logs', 'main.log')
3117        logcontent = open(logfile).read()
3118        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3119                        '234 (K1000000) - new@yy.zz' in logcontent)
3120        return
3121
3122    def test_student_locked_level_forms(self):
3123
3124        # Add two study levels, one current and one previous
3125        studylevel = createObject(u'waeup.StudentStudyLevel')
3126        studylevel.level = 100
3127        self.student['studycourse'].addStudentStudyLevel(
3128            self.certificate, studylevel)
3129        studylevel = createObject(u'waeup.StudentStudyLevel')
3130        studylevel.level = 200
3131        self.student['studycourse'].addStudentStudyLevel(
3132            self.certificate, studylevel)
3133        IWorkflowState(self.student).setState('school fee paid')
3134        self.student['studycourse'].current_level = 200
3135
3136        self.browser.open(self.login_path)
3137        self.browser.getControl(name="form.login").value = self.student_id
3138        self.browser.getControl(name="form.password").value = 'spwd'
3139        self.browser.getControl("Login").click()
3140
3141        self.browser.open(self.student_path + '/studycourse/200/edit')
3142        self.assertFalse('The requested form is locked' in self.browser.contents)
3143        self.browser.open(self.student_path + '/studycourse/100/edit')
3144        self.assertTrue('The requested form is locked' in self.browser.contents)
3145
3146        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3147        self.assertFalse('The requested form is locked' in self.browser.contents)
3148        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3149        self.assertTrue('The requested form is locked' in self.browser.contents)
3150
3151        IWorkflowState(self.student).setState('courses registered')
3152        self.browser.open(self.student_path + '/studycourse/200/edit')
3153        self.assertTrue('The requested form is locked' in self.browser.contents)
3154        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3155        self.assertTrue('The requested form is locked' in self.browser.contents)
3156
3157
3158class PublicPagesTests(StudentsFullSetup):
3159    # Tests for simple webservices
3160
3161    layer = FunctionalLayer
3162
3163    def test_paymentrequest(self):
3164        payment = createObject('waeup.StudentOnlinePayment')
3165        payment.p_category = u'schoolfee'
3166        payment.p_session = self.student.current_session
3167        payment.p_item = u'My Certificate'
3168        payment.p_id = u'anyid'
3169        self.student['payments']['anykey'] = payment
3170        # Request information about unpaid payment ticket
3171        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3172        self.assertEqual(self.browser.contents, '-1')
3173        # Request information about paid payment ticket
3174        payment.p_state = u'paid'
3175        notify(grok.ObjectModifiedEvent(payment))
3176        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3177        self.assertEqual(self.browser.contents,
3178            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3179            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3180            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3181            '&FEE_AMOUNT=0.0')
3182        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3183        self.assertEqual(self.browser.contents, '-1')
3184        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3185        self.assertEqual(self.browser.contents, '-1')
3186
3187class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3188    # Tests for StudentsContainer class views and pages
3189
3190    layer = FunctionalLayer
3191
3192    def wait_for_export_job_completed(self):
3193        # helper function waiting until the current export job is completed
3194        manager = getUtility(IJobManager)
3195        job_id = self.app['datacenter'].running_exports[0][0]
3196        job = manager.get(job_id)
3197        wait_for_result(job)
3198        return job_id
3199
3200    def test_faculties_export(self):
3201        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3202        facs_path = 'http://localhost/app/faculties'
3203        self.browser.open(facs_path)
3204        self.browser.getLink("Export student data").click()
3205        self.browser.getControl("Configure new export").click()
3206        self.browser.getControl(name="exporter").value = ['bursary']
3207        self.browser.getControl(name="session").value = ['2004']
3208        self.browser.getControl(name="level").value = ['100']
3209        self.browser.getControl(name="mode").value = ['ug_ft']
3210        self.browser.getControl("Create CSV file").click()
3211
3212        # When the job is finished and we reload the page...
3213        job_id = self.wait_for_export_job_completed()
3214        self.browser.open(facs_path + '/exports')
3215        # ... the csv file can be downloaded ...
3216        self.browser.getLink("Download").click()
3217        self.assertEqual(self.browser.headers['content-type'],
3218            'text/csv; charset=UTF-8')
3219        self.assertTrue(
3220            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3221            self.browser.headers['content-disposition'])
3222        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3223        job_id = self.app['datacenter'].running_exports[0][0]
3224        # ... and discarded
3225        self.browser.open(facs_path + '/exports')
3226        self.browser.getControl("Discard").click()
3227        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3228        # Creation, downloading and discarding is logged
3229        logfile = os.path.join(
3230            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3231        logcontent = open(logfile).read()
3232        self.assertTrue(
3233            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3234            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
3235            % job_id in logcontent
3236            )
3237        self.assertTrue(
3238            'zope.mgr - students.browser.ExportJobContainerDownload '
3239            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3240            % (job_id, job_id) in logcontent
3241            )
3242        self.assertTrue(
3243            'zope.mgr - students.browser.ExportJobContainerOverview '
3244            '- discarded: job_id=%s' % job_id in logcontent
3245            )
3246
3247    def test_department_export(self):
3248        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3249        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3250        self.browser.open(dep1_path)
3251        self.browser.getLink("Export student data").click()
3252        self.browser.getControl("Configure new export").click()
3253        self.browser.getControl(name="exporter").value = ['students']
3254        self.browser.getControl(name="session").value = ['2004']
3255        self.browser.getControl(name="level").value = ['100']
3256        self.browser.getControl(name="mode").value = ['ug_ft']
3257        self.browser.getControl("Create CSV file").click()
3258
3259        # When the job is finished and we reload the page...
3260        job_id = self.wait_for_export_job_completed()
3261        self.browser.open(dep1_path + '/exports')
3262        # ... the csv file can be downloaded ...
3263        self.browser.getLink("Download").click()
3264        self.assertEqual(self.browser.headers['content-type'],
3265            'text/csv; charset=UTF-8')
3266        self.assertTrue(
3267            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3268            self.browser.headers['content-disposition'])
3269        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3270        job_id = self.app['datacenter'].running_exports[0][0]
3271        # ... and discarded
3272        self.browser.open(dep1_path + '/exports')
3273        self.browser.getControl("Discard").click()
3274        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3275        # Creation, downloading and discarding is logged
3276        logfile = os.path.join(
3277            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3278        logcontent = open(logfile).read()
3279        self.assertTrue(
3280            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3281            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
3282            % job_id in logcontent
3283            )
3284        self.assertTrue(
3285            'zope.mgr - students.browser.ExportJobContainerDownload '
3286            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3287            % (job_id, job_id) in logcontent
3288            )
3289        self.assertTrue(
3290            'zope.mgr - students.browser.ExportJobContainerOverview '
3291            '- discarded: job_id=%s' % job_id in logcontent
3292            )
3293
3294    def test_certificate_export(self):
3295        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3296        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3297        self.browser.open(cert1_path)
3298        self.browser.getLink("Export student data").click()
3299        self.browser.getControl("Configure new export").click()
3300        self.browser.getControl(name="exporter").value = ['students']
3301        self.browser.getControl(name="session").value = ['2004']
3302        self.browser.getControl(name="level").value = ['100']
3303        self.browser.getControl("Create CSV file").click()
3304
3305        # When the job is finished and we reload the page...
3306        job_id = self.wait_for_export_job_completed()
3307        self.browser.open(cert1_path + '/exports')
3308        # ... the csv file can be downloaded ...
3309        self.browser.getLink("Download").click()
3310        self.assertEqual(self.browser.headers['content-type'],
3311            'text/csv; charset=UTF-8')
3312        self.assertTrue(
3313            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3314            self.browser.headers['content-disposition'])
3315        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3316        job_id = self.app['datacenter'].running_exports[0][0]
3317        # ... and discarded
3318        self.browser.open(cert1_path + '/exports')
3319        self.browser.getControl("Discard").click()
3320        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3321        # Creation, downloading and discarding is logged
3322        logfile = os.path.join(
3323            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3324        logcontent = open(logfile).read()
3325        self.assertTrue(
3326            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3327            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
3328            % job_id in logcontent
3329            )
3330        self.assertTrue(
3331            'zope.mgr - students.browser.ExportJobContainerDownload '
3332            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3333            % (job_id, job_id) in logcontent
3334            )
3335        self.assertTrue(
3336            'zope.mgr - students.browser.ExportJobContainerOverview '
3337            '- discarded: job_id=%s' % job_id in logcontent
3338            )
3339
3340    def test_course_export_students(self):
3341        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3342        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3343        self.browser.open(course1_path)
3344        self.browser.getLink("Export student data").click()
3345        self.browser.getControl("Configure new export").click()
3346        self.browser.getControl(name="exporter").value = ['students']
3347        self.browser.getControl(name="session").value = ['2004']
3348        self.browser.getControl(name="level").value = ['100']
3349        self.browser.getControl("Create CSV file").click()
3350
3351        # When the job is finished and we reload the page...
3352        job_id = self.wait_for_export_job_completed()
3353        self.browser.open(course1_path + '/exports')
3354        # ... the csv file can be downloaded ...
3355        self.browser.getLink("Download").click()
3356        self.assertEqual(self.browser.headers['content-type'],
3357            'text/csv; charset=UTF-8')
3358        self.assertTrue(
3359            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3360            self.browser.headers['content-disposition'])
3361        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3362        job_id = self.app['datacenter'].running_exports[0][0]
3363        # ... and discarded
3364        self.browser.open(course1_path + '/exports')
3365        self.browser.getControl("Discard").click()
3366        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3367        # Creation, downloading and discarding is logged
3368        logfile = os.path.join(
3369            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3370        logcontent = open(logfile).read()
3371        self.assertTrue(
3372            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3373            '- exported: students (2004, 100, COURSE1), job_id=%s'
3374            % job_id in logcontent
3375            )
3376        self.assertTrue(
3377            'zope.mgr - students.browser.ExportJobContainerDownload '
3378            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3379            % (job_id, job_id) in logcontent
3380            )
3381        self.assertTrue(
3382            'zope.mgr - students.browser.ExportJobContainerOverview '
3383            '- discarded: job_id=%s' % job_id in logcontent
3384            )
3385
3386    def test_course_export_coursetickets(self):
3387        # We add study level 100 to the student's studycourse
3388        studylevel = StudentStudyLevel()
3389        studylevel.level = 100
3390        studylevel.level_session = 2004
3391        self.student['studycourse'].addStudentStudyLevel(
3392            self.certificate,studylevel)
3393        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3394        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3395        self.browser.open(course1_path)
3396        self.browser.getLink("Export student data").click()
3397        self.browser.getControl("Configure new export").click()
3398        self.browser.getControl(name="exporter").value = ['coursetickets']
3399        self.browser.getControl(name="session").value = ['2004']
3400        self.browser.getControl(name="level").value = ['100']
3401        self.browser.getControl("Create CSV file").click()
3402        # When the job is finished and we reload the page...
3403        job_id = self.wait_for_export_job_completed()
3404        self.browser.open(course1_path + '/exports')
3405        # ... the csv file can be downloaded ...
3406        self.browser.getLink("Download").click()
3407        self.assertEqual(self.browser.headers['content-type'],
3408            'text/csv; charset=UTF-8')
3409        self.assertTrue(
3410            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3411            self.browser.headers['content-disposition'])
3412        # ... and contains the course ticket COURSE1
3413        self.assertEqual(self.browser.contents,
3414            'automatic,carry_over,code,credits,dcode,fcode,level,'
3415            'level_session,mandatory,passmark,score,semester,title,'
3416            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3417            'Unnamed Course,K1000000,CERT1\r\n')
3418
3419        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3420        job_id = self.app['datacenter'].running_exports[0][0]
3421        # Thew job can be discarded
3422        self.browser.open(course1_path + '/exports')
3423        self.browser.getControl("Discard").click()
3424        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3425        # Creation, downloading and discarding is logged
3426        logfile = os.path.join(
3427            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3428        logcontent = open(logfile).read()
3429        self.assertTrue(
3430            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3431            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3432            % job_id in logcontent
3433            )
3434        self.assertTrue(
3435            'zope.mgr - students.browser.ExportJobContainerDownload '
3436            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3437            % (job_id, job_id) in logcontent
3438            )
3439        self.assertTrue(
3440            'zope.mgr - students.browser.ExportJobContainerOverview '
3441            '- discarded: job_id=%s' % job_id in logcontent
3442            )
3443
3444    def test_export_departmet_officers(self):
3445        # Create department officer
3446        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3447        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3448        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3449        # Assign local role
3450        department = self.app['faculties']['fac1']['dep1']
3451        prmlocal = IPrincipalRoleManager(department)
3452        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3453        # Login as department officer
3454        self.browser.open(self.login_path)
3455        self.browser.getControl(name="form.login").value = 'mrdepartment'
3456        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3457        self.browser.getControl("Login").click()
3458        self.assertMatches('...You logged in...', self.browser.contents)
3459        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3460        self.browser.getLink("Export student data").click()
3461        self.browser.getControl("Configure new export").click()
3462        # Only the paymentsoverview exporter is available for department officers
3463        self.assertFalse('<option value="students">' in self.browser.contents)
3464        self.assertTrue(
3465            '<option value="paymentsoverview">' in self.browser.contents)
3466        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3467        self.browser.getControl(name="session").value = ['2004']
3468        self.browser.getControl(name="level").value = ['100']
3469        self.browser.getControl("Create CSV file").click()
3470        self.assertTrue('Export started' in self.browser.contents)
3471        # Thew job can be discarded
3472        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3473        #job_id = self.app['datacenter'].running_exports[0][0]
3474        job_id = self.wait_for_export_job_completed()
3475        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3476        self.browser.getControl("Discard").click()
3477        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3478
3479    def test_export_bursary_officers(self):
3480        # Create bursary officer
3481        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3482        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3483        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3484        prmglobal = IPrincipalRoleManager(self.app)
3485        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3486        # Login as bursary officer
3487        self.browser.open(self.login_path)
3488        self.browser.getControl(name="form.login").value = 'mrbursary'
3489        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3490        self.browser.getControl("Login").click()
3491        self.assertMatches('...You logged in...', self.browser.contents)
3492        self.browser.getLink("Academics").click()
3493        self.browser.getLink("Export student data").click()
3494        self.browser.getControl("Configure new export").click()
3495        # Only the bursary exporter is available for bursary officers
3496        # not only at facultiescontainer level ...
3497        self.assertFalse('<option value="students">' in self.browser.contents)
3498        self.assertTrue('<option value="bursary">' in self.browser.contents)
3499        self.browser.getControl(name="exporter").value = ['bursary']
3500        self.browser.getControl(name="session").value = ['2004']
3501        self.browser.getControl(name="level").value = ['100']
3502        self.browser.getControl("Create CSV file").click()
3503        self.assertTrue('Export started' in self.browser.contents)
3504        # ... but also at other levels
3505        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3506        self.browser.getLink("Export student data").click()
3507        self.browser.getControl("Configure new export").click()
3508        self.assertFalse('<option value="students">' in self.browser.contents)
3509        self.assertTrue('<option value="bursary">' in self.browser.contents)
3510        # Thew job can be discarded
3511        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3512        #job_id = self.app['datacenter'].running_exports[0][0]
3513        job_id = self.wait_for_export_job_completed()
3514        self.browser.open('http://localhost/app/faculties/exports')
3515        self.browser.getControl("Discard").click()
3516        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.