source: main/waeup.kofa/branches/uli-diazo-themed/src/waeup/kofa/students/tests/test_browser.py @ 11026

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

Make 'My Data' dropdown menu work.

  • Property svn:keywords set to Id
File size: 179.6 KB
Line 
1## $Id: test_browser.py 11023 2014-01-31 18:19:16Z 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" data-toggle="dropdown">...',
1961            self.browser.contents)
1962        self.assertMatches(
1963            '...My Data...',
1964            self.browser.contents)
1965        self.browser.getLink("Logout").click()
1966        # The student can't login with the original password ...
1967        self.browser.open(self.login_path)
1968        self.browser.getControl(name="form.login").value = self.student_id
1969        self.browser.getControl(name="form.password").value = 'spwd'
1970        self.browser.getControl("Login").click()
1971        self.assertMatches(
1972            '...Your account has been temporarily deactivated...',
1973            self.browser.contents)
1974        # ... but with the temporary password
1975        self.browser.open(self.login_path)
1976        self.browser.getControl(name="form.login").value = self.student_id
1977        self.browser.getControl(name="form.password").value = temp_password
1978        self.browser.getControl("Login").click()
1979        self.assertMatches('...You logged in...', self.browser.contents)
1980        # Creation of temp_password is properly logged
1981        logfile = os.path.join(
1982            self.app['datacenter'].storage, 'logs', 'students.log')
1983        logcontent = open(logfile).read()
1984        self.assertTrue(
1985            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
1986            'temp_password generated: %s' % temp_password in logcontent)
1987
1988    def test_transcripts(self):
1989        studylevel = createObject(u'waeup.StudentStudyLevel')
1990        studylevel.level = 100
1991        studylevel.level_session = 2005
1992        self.student['studycourse'].entry_mode = 'ug_ft'
1993        self.student['studycourse'].addStudentStudyLevel(
1994            self.certificate, studylevel)
1995        studylevel2 = createObject(u'waeup.StudentStudyLevel')
1996        studylevel2.level = 110
1997        studylevel2.level_session = 2006
1998        self.student['studycourse'].addStudentStudyLevel(
1999            self.certificate, studylevel2)
2000        # Add second course (COURSE has been added automatically)
2001        courseticket = createObject('waeup.CourseTicket')
2002        courseticket.code = 'ANYCODE'
2003        courseticket.title = u'Any TITLE'
2004        courseticket.credits = 13
2005        courseticket.score = 66
2006        courseticket.semester = 1
2007        courseticket.dcode = u'ANYDCODE'
2008        courseticket.fcode = u'ANYFCODE'
2009        self.student['studycourse']['110']['COURSE2'] = courseticket
2010        self.student['studycourse']['100']['COURSE1'].score = 55
2011        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2012        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2013        # Get transcript data
2014        td = self.student['studycourse'].getTranscriptData()
2015        self.assertEqual(td[0][0]['level_key'], '100')
2016        self.assertEqual(td[0][0]['sgpa'], 3.0)
2017        self.assertEqual(td[0][0]['level'].level, 100)
2018        self.assertEqual(td[0][0]['level'].level_session, 2005)
2019        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2020        self.assertEqual(td[0][1]['level_key'], '110')
2021        self.assertEqual(td[0][1]['sgpa'], 4.0)
2022        self.assertEqual(td[0][1]['level'].level, 110)
2023        self.assertEqual(td[0][1]['level'].level_session, 2006)
2024        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2025        self.assertEqual(td[1], 3.57)
2026        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2027        self.browser.open(self.student_path + '/studycourse/transcript')
2028        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2029        self.assertTrue('Transcript' in self.browser.contents)
2030        # Officers can open the pdf transcript
2031        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2032        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2033        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2034        path = os.path.join(samples_dir(), 'transcript.pdf')
2035        open(path, 'wb').write(self.browser.contents)
2036        print "Sample PDF transcript.pdf written to %s" % path
2037
2038    def test_process_transcript_request(self):
2039        IWorkflowState(self.student).setState('transcript requested')
2040        notify(grok.ObjectModifiedEvent(self.student))
2041        self.student.transcript_comment = (
2042            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2043            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2044            'Address line2\n\n')
2045        # Create transcript officer
2046        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2047        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2048        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2049        prmglobal = IPrincipalRoleManager(self.app)
2050        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2051        # Login as transcript officer
2052        self.browser.open(self.login_path)
2053        self.browser.getControl(name="form.login").value = 'mrtranscript'
2054        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2055        self.browser.getControl("Login").click()
2056        self.assertMatches('...You logged in...', self.browser.contents)
2057        # Officer can see his roles
2058        self.browser.getLink("My Roles").click()
2059        self.assertMatches(
2060            '...<div>Transcript Officer</div>...',
2061            self.browser.contents)
2062        # Officer can search for students in state 'transcripr requested'
2063        self.browser.open(self.container_path)
2064        self.browser.getControl(name="searchtype").value = ['transcript']
2065        self.browser.getControl("Find student(s)").click()
2066        self.assertTrue('Anna Tester' in self.browser.contents)
2067        self.browser.getLink("K1000000").click()
2068        self.browser.getLink("Manage transcript request").click()
2069        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2070        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2071        'Address line2<br><br></p>' in self.browser.contents)
2072        self.browser.getControl(name="comment").value = (
2073            'Hello,\nYour transcript has been sent to the address provided.')
2074        self.browser.getControl("Save comment and mark as processed").click()
2075        self.assertTrue(
2076            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2077            'been sent to the address provided.\n\n'
2078            in self.student.transcript_comment)
2079        # The comment has been logged
2080        logfile = os.path.join(
2081            self.app['datacenter'].storage, 'logs', 'students.log')
2082        logcontent = open(logfile).read()
2083        self.assertTrue(
2084            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2085            'K1000000 - comment: Hello,<br>'
2086            'Your transcript has been sent to the address provided'
2087            in logcontent)
2088
2089class StudentUITests(StudentsFullSetup):
2090    # Tests for Student class views and pages
2091
2092    def test_student_change_password(self):
2093        # Students can change the password
2094        self.student.personal_updated = datetime.utcnow()
2095        self.browser.open(self.login_path)
2096        self.browser.getControl(name="form.login").value = self.student_id
2097        self.browser.getControl(name="form.password").value = 'spwd'
2098        self.browser.getControl("Login").click()
2099        self.assertEqual(self.browser.url, self.student_path)
2100        self.assertTrue('You logged in' in self.browser.contents)
2101        # Change password
2102        self.browser.getLink("Change password").click()
2103        self.browser.getControl(name="change_password").value = 'pw'
2104        self.browser.getControl(
2105            name="change_password_repeat").value = 'pw'
2106        self.browser.getControl("Save").click()
2107        self.assertTrue('Password must have at least' in self.browser.contents)
2108        self.browser.getControl(name="change_password").value = 'new_password'
2109        self.browser.getControl(
2110            name="change_password_repeat").value = 'new_passssword'
2111        self.browser.getControl("Save").click()
2112        self.assertTrue('Passwords do not match' in self.browser.contents)
2113        self.browser.getControl(name="change_password").value = 'new_password'
2114        self.browser.getControl(
2115            name="change_password_repeat").value = 'new_password'
2116        self.browser.getControl("Save").click()
2117        self.assertTrue('Password changed' in self.browser.contents)
2118        # We are still logged in. Changing the password hasn't thrown us out.
2119        self.browser.getLink("Base Data").click()
2120        self.assertEqual(self.browser.url, self.student_path)
2121        # We can logout
2122        self.browser.getLink("Logout").click()
2123        self.assertTrue('You have been logged out' in self.browser.contents)
2124        self.assertEqual(self.browser.url, 'http://localhost/app')
2125        # We can login again with the new password
2126        self.browser.getLink("Login").click()
2127        self.browser.open(self.login_path)
2128        self.browser.getControl(name="form.login").value = self.student_id
2129        self.browser.getControl(name="form.password").value = 'new_password'
2130        self.browser.getControl("Login").click()
2131        self.assertEqual(self.browser.url, self.student_path)
2132        self.assertTrue('You logged in' in self.browser.contents)
2133        return
2134
2135    def test_setpassword(self):
2136        # Set password for first-time access
2137        student = Student()
2138        student.reg_number = u'123456'
2139        student.firstname = u'Klaus'
2140        student.lastname = u'Tester'
2141        self.app['students'].addStudent(student)
2142        setpassword_path = 'http://localhost/app/setpassword'
2143        student_path = 'http://localhost/app/students/%s' % student.student_id
2144        self.browser.open(setpassword_path)
2145        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2146        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2147        self.browser.getControl(name="reg_number").value = '223456'
2148        self.browser.getControl("Set").click()
2149        self.assertMatches('...No student found...',
2150                           self.browser.contents)
2151        self.browser.getControl(name="reg_number").value = '123456'
2152        self.browser.getControl(name="ac_number").value = '999999'
2153        self.browser.getControl("Set").click()
2154        self.assertMatches('...Access code is invalid...',
2155                           self.browser.contents)
2156        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2157        self.browser.getControl("Set").click()
2158        self.assertMatches('...Password has been set. Your Student Id is...',
2159                           self.browser.contents)
2160        self.browser.getControl("Set").click()
2161        self.assertMatches(
2162            '...Password has already been set. Your Student Id is...',
2163            self.browser.contents)
2164        existing_pwdpin = self.pwdpins[1]
2165        parts = existing_pwdpin.split('-')[1:]
2166        existing_pwdseries, existing_pwdnumber = parts
2167        self.browser.getControl(name="ac_series").value = existing_pwdseries
2168        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2169        self.browser.getControl(name="reg_number").value = '123456'
2170        self.browser.getControl("Set").click()
2171        self.assertMatches(
2172            '...You are using the wrong Access Code...',
2173            self.browser.contents)
2174        # The student can login with the new credentials
2175        self.browser.open(self.login_path)
2176        self.browser.getControl(name="form.login").value = student.student_id
2177        self.browser.getControl(
2178            name="form.password").value = self.existing_pwdnumber
2179        self.browser.getControl("Login").click()
2180        self.assertEqual(self.browser.url, student_path)
2181        self.assertTrue('You logged in' in self.browser.contents)
2182        return
2183
2184    def test_student_login(self):
2185        # Student cant login if their password is not set
2186        self.student.password = None
2187        self.browser.open(self.login_path)
2188        self.browser.getControl(name="form.login").value = self.student_id
2189        self.browser.getControl(name="form.password").value = 'spwd'
2190        self.browser.getControl("Login").click()
2191        self.assertTrue(
2192            'You entered invalid credentials.' in self.browser.contents)
2193        # We set the password again
2194        IUserAccount(
2195            self.app['students'][self.student_id]).setPassword('spwd')
2196        # Students can't login if their account is suspended/deactivated
2197        self.student.suspended = True
2198        self.browser.open(self.login_path)
2199        self.browser.getControl(name="form.login").value = self.student_id
2200        self.browser.getControl(name="form.password").value = 'spwd'
2201        self.browser.getControl("Login").click()
2202        self.assertMatches(
2203            '...<div class="alert alert-warning">'
2204            'Your account has been deactivated.</div>...', self.browser.contents)
2205        # If suspended_comment is set this message will be flashed instead
2206        self.student.suspended_comment = u'Aetsch baetsch!'
2207        self.browser.getControl(name="form.login").value = self.student_id
2208        self.browser.getControl(name="form.password").value = 'spwd'
2209        self.browser.getControl("Login").click()
2210        self.assertMatches(
2211            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2212            self.browser.contents)
2213        self.student.suspended = False
2214        # Students can't login if a temporary password has been set and
2215        # is not expired
2216        self.app['students'][self.student_id].setTempPassword(
2217            'anybody', 'temp_spwd')
2218        self.browser.open(self.login_path)
2219        self.browser.getControl(name="form.login").value = self.student_id
2220        self.browser.getControl(name="form.password").value = 'spwd'
2221        self.browser.getControl("Login").click()
2222        self.assertMatches(
2223            '...Your account has been temporarily deactivated...',
2224            self.browser.contents)
2225        # The student can login with the temporary password
2226        self.browser.open(self.login_path)
2227        self.browser.getControl(name="form.login").value = self.student_id
2228        self.browser.getControl(name="form.password").value = 'temp_spwd'
2229        self.browser.getControl("Login").click()
2230        self.assertMatches(
2231            '...You logged in...', self.browser.contents)
2232        # Student can view the base data
2233        self.browser.open(self.student_path)
2234        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2235        self.assertEqual(self.browser.url, self.student_path)
2236        # When the password expires ...
2237        delta = timedelta(minutes=11)
2238        self.app['students'][self.student_id].temp_password[
2239            'timestamp'] = datetime.utcnow() - delta
2240        self.app['students'][self.student_id]._p_changed = True
2241        # ... the student will be automatically logged out
2242        self.assertRaises(
2243            Unauthorized, self.browser.open, self.student_path)
2244        # Then the student can login with the original password
2245        self.browser.open(self.login_path)
2246        self.browser.getControl(name="form.login").value = self.student_id
2247        self.browser.getControl(name="form.password").value = 'spwd'
2248        self.browser.getControl("Login").click()
2249        self.assertMatches(
2250            '...You logged in...', self.browser.contents)
2251
2252    def test_student_clearance(self):
2253        # Student cant login if their password is not set
2254        IWorkflowInfo(self.student).fireTransition('admit')
2255        self.browser.open(self.login_path)
2256        self.browser.getControl(name="form.login").value = self.student_id
2257        self.browser.getControl(name="form.password").value = 'spwd'
2258        self.browser.getControl("Login").click()
2259        self.assertMatches(
2260            '...You logged in...', self.browser.contents)
2261        # Admitted student can upload a passport picture
2262        self.browser.open(self.student_path + '/change_portrait')
2263        ctrl = self.browser.getControl(name='passportuploadedit')
2264        file_obj = open(SAMPLE_IMAGE, 'rb')
2265        file_ctrl = ctrl.mech_control
2266        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2267        self.browser.getControl(
2268            name='upload_passportuploadedit').click()
2269        self.assertTrue(
2270            '<img align="middle" height="125px" src="passport.jpg" />'
2271            in self.browser.contents)
2272        # Students can open admission letter
2273        self.browser.getLink("Base Data").click()
2274        self.browser.getLink("Download admission letter").click()
2275        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2276        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2277        # Student can view the clearance data
2278        self.browser.open(self.student_path)
2279        self.browser.getLink("Clearance Data").click()
2280        # Student can't open clearance edit form before starting clearance
2281        self.browser.open(self.student_path + '/cedit')
2282        self.assertMatches('...The requested form is locked...',
2283                           self.browser.contents)
2284        self.browser.getLink("Clearance Data").click()
2285        self.browser.getLink("Start clearance").click()
2286        self.student.email = None
2287        # Uups, we forgot to fill the email fields
2288        self.browser.getControl("Start clearance").click()
2289        self.assertMatches('...Not all required fields filled...',
2290                           self.browser.contents)
2291        self.browser.open(self.student_path + '/edit_base')
2292        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2293        self.browser.getControl("Save").click()
2294        self.browser.open(self.student_path + '/start_clearance')
2295        self.browser.getControl(name="ac_series").value = '3'
2296        self.browser.getControl(name="ac_number").value = '4444444'
2297        self.browser.getControl("Start clearance now").click()
2298        self.assertMatches('...Activation code is invalid...',
2299                           self.browser.contents)
2300        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2301        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2302        # Owner is Hans Wurst, AC can't be invalidated
2303        self.browser.getControl("Start clearance now").click()
2304        self.assertMatches('...You are not the owner of this access code...',
2305                           self.browser.contents)
2306        # Set the correct owner
2307        self.existing_clrac.owner = self.student_id
2308        # clr_code might be set (and thus returns None) due importing
2309        # an empty clr_code column.
2310        self.student.clr_code = None
2311        self.browser.getControl("Start clearance now").click()
2312        self.assertMatches('...Clearance process has been started...',
2313                           self.browser.contents)
2314        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2315        self.browser.getControl("Save", index=0).click()
2316        # Student can view the clearance data
2317        self.browser.getLink("Clearance Data").click()
2318        # and go back to the edit form
2319        self.browser.getLink("Edit").click()
2320        # Students can upload documents
2321        ctrl = self.browser.getControl(name='birthcertificateupload')
2322        file_obj = open(SAMPLE_IMAGE, 'rb')
2323        file_ctrl = ctrl.mech_control
2324        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2325        self.browser.getControl(
2326            name='upload_birthcertificateupload').click()
2327        self.assertTrue(
2328            '<a target="image" href="birth_certificate">Birth Certificate Scan</a>'
2329            in self.browser.contents)
2330        # Students can open clearance slip
2331        self.browser.getLink("View").click()
2332        self.browser.getLink("Download clearance slip").click()
2333        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2334        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2335        # Students can request clearance
2336        self.browser.open(self.edit_clearance_path)
2337        self.browser.getControl("Save and request clearance").click()
2338        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2339        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2340        self.browser.getControl("Request clearance now").click()
2341        self.assertMatches('...Clearance has been requested...',
2342                           self.browser.contents)
2343        # Student can't reopen clearance form after requesting clearance
2344        self.browser.open(self.student_path + '/cedit')
2345        self.assertMatches('...The requested form is locked...',
2346                           self.browser.contents)
2347
2348    def test_student_course_registration(self):
2349        # Student cant login if their password is not set
2350        IWorkflowInfo(self.student).fireTransition('admit')
2351        self.browser.open(self.login_path)
2352        self.browser.getControl(name="form.login").value = self.student_id
2353        self.browser.getControl(name="form.password").value = 'spwd'
2354        self.browser.getControl("Login").click()
2355        # Student can't add study level if not in state 'school fee paid'
2356        self.browser.open(self.student_path + '/studycourse/add')
2357        self.assertMatches('...The requested form is locked...',
2358                           self.browser.contents)
2359        # ... and must be transferred first
2360        IWorkflowState(self.student).setState('school fee paid')
2361        # Now students can add the current study level
2362        self.browser.getLink("Study Course").click()
2363        self.browser.getLink("Add course list").click()
2364        self.assertMatches('...Add current level 100 (Year 1)...',
2365                           self.browser.contents)
2366        self.browser.getControl("Create course list now").click()
2367        # A level with one course ticket was created
2368        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2369        self.browser.getLink("100").click()
2370        self.browser.getLink("Edit course list").click()
2371        self.browser.getLink("here").click()
2372        self.browser.getControl(name="form.course").value = ['COURSE1']
2373        self.browser.getControl("Add course ticket").click()
2374        self.assertMatches('...The ticket exists...',
2375                           self.browser.contents)
2376        self.student['studycourse'].current_level = 200
2377        self.browser.getLink("Study Course").click()
2378        self.browser.getLink("Add course list").click()
2379        self.assertMatches('...Add current level 200 (Year 2)...',
2380                           self.browser.contents)
2381        self.browser.getControl("Create course list now").click()
2382        self.browser.getLink("200").click()
2383        self.browser.getLink("Edit course list").click()
2384        self.browser.getLink("here").click()
2385        self.browser.getControl(name="form.course").value = ['COURSE1']
2386        self.course.credits = 100
2387        self.browser.getControl("Add course ticket").click()
2388        self.assertMatches(
2389            '...Total credits exceed 50...', self.browser.contents)
2390        self.course.credits = 10
2391        self.browser.getControl("Add course ticket").click()
2392        self.assertMatches('...The ticket exists...',
2393                           self.browser.contents)
2394        # Indeed the ticket exists as carry-over course from level 100
2395        # since its score was 0
2396        self.assertTrue(
2397            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2398        # Students can open the pdf course registration slip
2399        self.browser.open(self.student_path + '/studycourse/200')
2400        self.browser.getLink("Download course registration slip").click()
2401        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2402        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2403        # Students can remove course tickets
2404        self.browser.open(self.student_path + '/studycourse/200/edit')
2405        self.browser.getControl("Remove selected", index=0).click()
2406        self.assertTrue('No ticket selected' in self.browser.contents)
2407        # No ticket can be selected since the carry-over course is a core course
2408        self.assertRaises(
2409            LookupError, self.browser.getControl, name='val_id')
2410        self.student['studycourse']['200']['COURSE1'].mandatory = False
2411        self.browser.open(self.student_path + '/studycourse/200/edit')
2412        # Course list can't be registered if total_credits exceeds max_credits
2413        self.student['studycourse']['200']['COURSE1'].credits = 60
2414        self.browser.getControl("Register course list").click()
2415        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2416        # Student can now remove the ticket
2417        ctrl = self.browser.getControl(name='val_id')
2418        ctrl.getControl(value='COURSE1').selected = True
2419        self.browser.getControl("Remove selected", index=0).click()
2420        self.assertTrue('Successfully removed' in self.browser.contents)
2421        # Removing course tickets is properly logged
2422        logfile = os.path.join(
2423            self.app['datacenter'].storage, 'logs', 'students.log')
2424        logcontent = open(logfile).read()
2425        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2426        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2427        # They can add the same ticket using the edit page directly.
2428        # We can do the same by adding the course on the manage page directly
2429        self.browser.getControl(name="course").value = 'COURSE1'
2430        self.browser.getControl("Add course ticket").click()
2431        # Adding course tickets is logged
2432        logfile = os.path.join(
2433            self.app['datacenter'].storage, 'logs', 'students.log')
2434        logcontent = open(logfile).read()
2435        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2436            'K1000000 - added: COURSE1|200|2004' in logcontent)
2437        # Course list can be registered
2438        self.browser.getControl("Register course list").click()
2439        self.assertTrue('Course list has been registered' in self.browser.contents)
2440        self.assertEqual(self.student.state, 'courses registered')
2441        # Students can view the transcript
2442        #self.browser.open(self.studycourse_path)
2443        #self.browser.getLink("Transcript").click()
2444        #self.browser.getLink("Academic Transcript").click()
2445        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2446        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2447        return
2448
2449    def test_postgraduate_student_access(self):
2450        self.certificate.study_mode = 'pg_ft'
2451        self.certificate.start_level = 999
2452        self.certificate.end_level = 999
2453        self.student['studycourse'].current_level = 999
2454        IWorkflowState(self.student).setState('school fee paid')
2455        self.browser.open(self.login_path)
2456        self.browser.getControl(name="form.login").value = self.student_id
2457        self.browser.getControl(name="form.password").value = 'spwd'
2458        self.browser.getControl("Login").click()
2459        self.assertTrue(
2460            'You logged in.' in self.browser.contents)
2461        # Now students can add the current study level
2462        self.browser.getLink("Study Course").click()
2463        self.browser.getLink("Add course list").click()
2464        self.assertMatches('...Add current level Postgraduate Level...',
2465                           self.browser.contents)
2466        self.browser.getControl("Create course list now").click()
2467        # A level with one course ticket was created
2468        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2469        self.browser.getLink("999").click()
2470        self.browser.getLink("Edit course list").click()
2471        self.browser.getLink("here").click()
2472        self.browser.getControl(name="form.course").value = ['COURSE1']
2473        self.browser.getControl("Add course ticket").click()
2474        self.assertMatches('...Successfully added COURSE1...',
2475                           self.browser.contents)
2476        # Postgraduate students can't register course lists
2477        self.browser.getControl("Register course list").click()
2478        self.assertTrue("your course list can't bee registered"
2479            in self.browser.contents)
2480        self.assertEqual(self.student.state, 'school fee paid')
2481        return
2482
2483    def test_student_clearance_wo_clrcode(self):
2484        IWorkflowState(self.student).setState('clearance started')
2485        self.browser.open(self.login_path)
2486        self.browser.getControl(name="form.login").value = self.student_id
2487        self.browser.getControl(name="form.password").value = 'spwd'
2488        self.browser.getControl("Login").click()
2489        self.student.clearance_locked = False
2490        self.browser.open(self.edit_clearance_path)
2491        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2492        self.browser.getControl("Save and request clearance").click()
2493        self.assertMatches('...Clearance has been requested...',
2494                           self.browser.contents)
2495
2496    def test_student_clearance_payment(self):
2497        # Login
2498        self.browser.open(self.login_path)
2499        self.browser.getControl(name="form.login").value = self.student_id
2500        self.browser.getControl(name="form.password").value = 'spwd'
2501        self.browser.getControl("Login").click()
2502
2503        # Students can add online clearance payment tickets
2504        self.browser.open(self.payments_path + '/addop')
2505        self.browser.getControl(name="form.p_category").value = ['clearance']
2506        self.browser.getControl("Create ticket").click()
2507        self.assertMatches('...ticket created...',
2508                           self.browser.contents)
2509
2510        # Students can't approve the payment
2511        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2512        ctrl = self.browser.getControl(name='val_id')
2513        value = ctrl.options[0]
2514        self.browser.getLink(value).click()
2515        payment_url = self.browser.url
2516        self.assertRaises(
2517            Unauthorized, self.browser.open, payment_url + '/approve')
2518        # In the base package they can 'use' a fake approval view.
2519        # XXX: I tried to use
2520        # self.student['payments'][value].approveStudentPayment() instead.
2521        # But this function fails in
2522        # w.k.accesscodes.accesscode.create_accesscode.
2523        # grok.getSite returns None in tests.
2524        self.browser.open(payment_url + '/fake_approve')
2525        self.assertMatches('...Payment approved...',
2526                          self.browser.contents)
2527        expected = '''...
2528        <td>
2529          <span>Paid</span>
2530        </td>...'''
2531        expected = '''...
2532        <td>
2533          <span>Paid</span>
2534        </td>...'''
2535        self.assertMatches(expected,self.browser.contents)
2536        payment_id = self.student['payments'].keys()[0]
2537        payment = self.student['payments'][payment_id]
2538        self.assertEqual(payment.p_state, 'paid')
2539        self.assertEqual(payment.r_amount_approved, 3456.0)
2540        self.assertEqual(payment.r_code, 'AP')
2541        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2542        # The new CLR-0 pin has been created
2543        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2544        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2545        ac = self.app['accesscodes']['CLR-0'][pin]
2546        self.assertEqual(ac.owner, self.student_id)
2547        self.assertEqual(ac.cost, 3456.0)
2548
2549        # Students can open the pdf payment slip
2550        self.browser.open(payment_url + '/payment_slip.pdf')
2551        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2552        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2553
2554        # The new CLR-0 pin can be used for starting clearance
2555        # but they have to upload a passport picture first
2556        # which is only possible in state admitted
2557        self.browser.open(self.student_path + '/change_portrait')
2558        self.assertMatches('...form is locked...',
2559                          self.browser.contents)
2560        IWorkflowInfo(self.student).fireTransition('admit')
2561        self.browser.open(self.student_path + '/change_portrait')
2562        image = open(SAMPLE_IMAGE, 'rb')
2563        ctrl = self.browser.getControl(name='passportuploadedit')
2564        file_ctrl = ctrl.mech_control
2565        file_ctrl.add_file(image, filename='my_photo.jpg')
2566        self.browser.getControl(
2567            name='upload_passportuploadedit').click()
2568        self.browser.open(self.student_path + '/start_clearance')
2569        parts = pin.split('-')[1:]
2570        clrseries, clrnumber = parts
2571        self.browser.getControl(name="ac_series").value = clrseries
2572        self.browser.getControl(name="ac_number").value = clrnumber
2573        self.browser.getControl("Start clearance now").click()
2574        self.assertMatches('...Clearance process has been started...',
2575                           self.browser.contents)
2576
2577    def test_student_schoolfee_payment(self):
2578        configuration = createObject('waeup.SessionConfiguration')
2579        configuration.academic_session = 2005
2580        self.app['configuration'].addSessionConfiguration(configuration)
2581        # Login
2582        self.browser.open(self.login_path)
2583        self.browser.getControl(name="form.login").value = self.student_id
2584        self.browser.getControl(name="form.password").value = 'spwd'
2585        self.browser.getControl("Login").click()
2586
2587        # Students can add online school fee payment tickets.
2588        IWorkflowState(self.student).setState('returning')
2589        self.browser.open(self.payments_path)
2590        self.assertRaises(
2591            LookupError, self.browser.getControl, name='val_id')
2592        self.browser.getLink("Add current session payment ticket").click()
2593        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2594        self.browser.getControl("Create ticket").click()
2595        self.assertMatches('...ticket created...',
2596                           self.browser.contents)
2597        ctrl = self.browser.getControl(name='val_id')
2598        value = ctrl.options[0]
2599        self.browser.getLink(value).click()
2600        self.assertMatches('...Amount Authorized...',
2601                           self.browser.contents)
2602        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2603        # Payment session and will be calculated as defined
2604        # in w.k.students.utils because we set changed the state
2605        # to returning
2606        self.assertEqual(self.student['payments'][value].p_session, 2005)
2607        self.assertEqual(self.student['payments'][value].p_level, 200)
2608
2609        # Student is the payer of the payment ticket.
2610        payer = IPayer(self.student['payments'][value])
2611        self.assertEqual(payer.display_fullname, 'Anna Tester')
2612        self.assertEqual(payer.id, self.student_id)
2613        self.assertEqual(payer.faculty, 'fac1')
2614        self.assertEqual(payer.department, 'dep1')
2615
2616        # We simulate the approval
2617        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2618        self.browser.open(self.browser.url + '/fake_approve')
2619        self.assertMatches('...Payment approved...',
2620                          self.browser.contents)
2621
2622        # The new SFE-0 pin can be used for starting new session
2623        self.browser.open(self.studycourse_path)
2624        self.browser.getLink('Start new session').click()
2625        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2626        parts = pin.split('-')[1:]
2627        sfeseries, sfenumber = parts
2628        self.browser.getControl(name="ac_series").value = sfeseries
2629        self.browser.getControl(name="ac_number").value = sfenumber
2630        self.browser.getControl("Start now").click()
2631        self.assertMatches('...Session started...',
2632                           self.browser.contents)
2633        self.assertTrue(self.student.state == 'school fee paid')
2634        return
2635
2636    def test_student_bedallocation_payment(self):
2637        # Login
2638        self.browser.open(self.login_path)
2639        self.browser.getControl(name="form.login").value = self.student_id
2640        self.browser.getControl(name="form.password").value = 'spwd'
2641        self.browser.getControl("Login").click()
2642        self.browser.open(self.payments_path)
2643        self.browser.open(self.payments_path + '/addop')
2644        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2645        self.browser.getControl("Create ticket").click()
2646        self.assertMatches('...ticket created...',
2647                           self.browser.contents)
2648        # Students can remove only online payment tickets which have
2649        # not received a valid callback
2650        self.browser.open(self.payments_path)
2651        ctrl = self.browser.getControl(name='val_id')
2652        value = ctrl.options[0]
2653        ctrl.getControl(value=value).selected = True
2654        self.browser.getControl("Remove selected", index=0).click()
2655        self.assertTrue('Successfully removed' in self.browser.contents)
2656
2657    def test_student_maintenance_payment(self):
2658        # Login
2659        self.browser.open(self.login_path)
2660        self.browser.getControl(name="form.login").value = self.student_id
2661        self.browser.getControl(name="form.password").value = 'spwd'
2662        self.browser.getControl("Login").click()
2663        self.browser.open(self.payments_path)
2664        self.browser.open(self.payments_path + '/addop')
2665        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2666        self.browser.getControl("Create ticket").click()
2667        self.assertMatches('...You have not yet booked accommodation...',
2668                           self.browser.contents)
2669        # We continue this test in test_student_accommodation
2670
2671    def test_student_previous_payments(self):
2672        configuration = createObject('waeup.SessionConfiguration')
2673        configuration.academic_session = 2000
2674        configuration.clearance_fee = 3456.0
2675        configuration.booking_fee = 123.4
2676        self.app['configuration'].addSessionConfiguration(configuration)
2677        configuration2 = createObject('waeup.SessionConfiguration')
2678        configuration2.academic_session = 2003
2679        configuration2.clearance_fee = 3456.0
2680        configuration2.booking_fee = 123.4
2681        self.app['configuration'].addSessionConfiguration(configuration2)
2682        configuration3 = createObject('waeup.SessionConfiguration')
2683        configuration3.academic_session = 2005
2684        configuration3.clearance_fee = 3456.0
2685        configuration3.booking_fee = 123.4
2686        self.app['configuration'].addSessionConfiguration(configuration3)
2687        self.student['studycourse'].entry_session = 2002
2688
2689        # Login
2690        self.browser.open(self.login_path)
2691        self.browser.getControl(name="form.login").value = self.student_id
2692        self.browser.getControl(name="form.password").value = 'spwd'
2693        self.browser.getControl("Login").click()
2694
2695        # Students can add previous school fee payment tickets in any state.
2696        IWorkflowState(self.student).setState('courses registered')
2697        self.browser.open(self.payments_path)
2698        self.browser.getLink("Add previous session payment ticket").click()
2699
2700        # Previous session payment form is provided
2701        self.assertEqual(self.student.current_session, 2004)
2702        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2703        self.browser.getControl(name="form.p_session").value = ['2000']
2704        self.browser.getControl(name="form.p_level").value = ['300']
2705        self.browser.getControl("Create ticket").click()
2706        self.assertMatches('...The previous session must not fall below...',
2707                           self.browser.contents)
2708        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2709        self.browser.getControl(name="form.p_session").value = ['2005']
2710        self.browser.getControl(name="form.p_level").value = ['300']
2711        self.browser.getControl("Create ticket").click()
2712        self.assertMatches('...This is not a previous session...',
2713                           self.browser.contents)
2714        # Students can pay current session school fee.
2715        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2716        self.browser.getControl(name="form.p_session").value = ['2004']
2717        self.browser.getControl(name="form.p_level").value = ['300']
2718        self.browser.getControl("Create ticket").click()
2719        self.assertMatches('...ticket created...',
2720                           self.browser.contents)
2721        ctrl = self.browser.getControl(name='val_id')
2722        value = ctrl.options[0]
2723        self.browser.getLink(value).click()
2724        self.assertMatches('...Amount Authorized...',
2725                           self.browser.contents)
2726        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2727
2728        # Payment session is properly set
2729        self.assertEqual(self.student['payments'][value].p_session, 2004)
2730        self.assertEqual(self.student['payments'][value].p_level, 300)
2731
2732        # We simulate the approval
2733        self.browser.open(self.browser.url + '/fake_approve')
2734        self.assertMatches('...Payment approved...',
2735                          self.browser.contents)
2736
2737        # No AC has been created
2738        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2739        self.assertTrue(self.student['payments'][value].ac is None)
2740
2741        # Current payment flag is set False
2742        self.assertFalse(self.student['payments'][value].p_current)
2743
2744        # Button and form are not available for students who are in
2745        # states up to cleared
2746        self.student['studycourse'].entry_session = 2004
2747        IWorkflowState(self.student).setState('cleared')
2748        self.browser.open(self.payments_path)
2749        self.assertFalse(
2750            "Add previous session payment ticket" in self.browser.contents)
2751        self.browser.open(self.payments_path + '/addpp')
2752        self.assertTrue(
2753            "No previous payment to be made" in self.browser.contents)
2754        return
2755
2756    def test_postgraduate_student_payments(self):
2757        configuration = createObject('waeup.SessionConfiguration')
2758        configuration.academic_session = 2005
2759        self.app['configuration'].addSessionConfiguration(configuration)
2760        self.certificate.study_mode = 'pg_ft'
2761        self.certificate.start_level = 999
2762        self.certificate.end_level = 999
2763        self.student['studycourse'].current_level = 999
2764        # Login
2765        self.browser.open(self.login_path)
2766        self.browser.getControl(name="form.login").value = self.student_id
2767        self.browser.getControl(name="form.password").value = 'spwd'
2768        self.browser.getControl("Login").click()
2769        # Students can add online school fee payment tickets.
2770        IWorkflowState(self.student).setState('cleared')
2771        self.browser.open(self.payments_path)
2772        self.browser.getLink("Add current session payment ticket").click()
2773        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2774        self.browser.getControl("Create ticket").click()
2775        self.assertMatches('...ticket created...',
2776                           self.browser.contents)
2777        ctrl = self.browser.getControl(name='val_id')
2778        value = ctrl.options[0]
2779        self.browser.getLink(value).click()
2780        self.assertMatches('...Amount Authorized...',
2781                           self.browser.contents)
2782        # Payment session and level are current ones.
2783        # Postgrads have to pay school_fee_1.
2784        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2785        self.assertEqual(self.student['payments'][value].p_session, 2004)
2786        self.assertEqual(self.student['payments'][value].p_level, 999)
2787
2788        # We simulate the approval
2789        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2790        self.browser.open(self.browser.url + '/fake_approve')
2791        self.assertMatches('...Payment approved...',
2792                          self.browser.contents)
2793
2794        # The new SFE-0 pin can be used for starting session
2795        self.browser.open(self.studycourse_path)
2796        self.browser.getLink('Start new session').click()
2797        pin = self.app['accesscodes']['SFE-0'].keys()[0]
2798        parts = pin.split('-')[1:]
2799        sfeseries, sfenumber = parts
2800        self.browser.getControl(name="ac_series").value = sfeseries
2801        self.browser.getControl(name="ac_number").value = sfenumber
2802        self.browser.getControl("Start now").click()
2803        self.assertMatches('...Session started...',
2804                           self.browser.contents)
2805        self.assertTrue(self.student.state == 'school fee paid')
2806
2807        # Postgrad students do not need to register courses the
2808        # can just pay for the next session.
2809        self.browser.open(self.payments_path)
2810        # Remove first payment to be sure that we access the right ticket
2811        del self.student['payments'][value]
2812        self.browser.getLink("Add current session payment ticket").click()
2813        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2814        self.browser.getControl("Create ticket").click()
2815        ctrl = self.browser.getControl(name='val_id')
2816        value = ctrl.options[0]
2817        self.browser.getLink(value).click()
2818        # Payment session has increased by one, payment level remains the same.
2819        # Returning Postgraduates have to pay school_fee_2.
2820        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2821        self.assertEqual(self.student['payments'][value].p_session, 2005)
2822        self.assertEqual(self.student['payments'][value].p_level, 999)
2823
2824        # Student is still in old session
2825        self.assertEqual(self.student.current_session, 2004)
2826
2827        # We do not need to pay the ticket if any other
2828        # SFE pin is provided
2829        pin_container = self.app['accesscodes']
2830        pin_container.createBatch(
2831            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2832        pin = pin_container['SFE-1'].values()[0].representation
2833        sfeseries, sfenumber = pin.split('-')[1:]
2834        # The new SFE-1 pin can be used for starting new session
2835        self.browser.open(self.studycourse_path)
2836        self.browser.getLink('Start new session').click()
2837        self.browser.getControl(name="ac_series").value = sfeseries
2838        self.browser.getControl(name="ac_number").value = sfenumber
2839        self.browser.getControl("Start now").click()
2840        self.assertMatches('...Session started...',
2841                           self.browser.contents)
2842        self.assertTrue(self.student.state == 'school fee paid')
2843        # Student is in new session
2844        self.assertEqual(self.student.current_session, 2005)
2845        self.assertEqual(self.student['studycourse'].current_level, 999)
2846        return
2847
2848    def test_student_accommodation(self):
2849        # Login
2850        self.browser.open(self.login_path)
2851        self.browser.getControl(name="form.login").value = self.student_id
2852        self.browser.getControl(name="form.password").value = 'spwd'
2853        self.browser.getControl("Login").click()
2854
2855        # Students can add online booking fee payment tickets and open the
2856        # callback view (see test_manage_payments)
2857        self.browser.getLink("Payments").click()
2858        self.browser.getLink("Add current session payment ticket").click()
2859        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2860        self.browser.getControl("Create ticket").click()
2861        ctrl = self.browser.getControl(name='val_id')
2862        value = ctrl.options[0]
2863        self.browser.getLink(value).click()
2864        self.browser.open(self.browser.url + '/fake_approve')
2865        # The new HOS-0 pin has been created
2866        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
2867        pin = self.app['accesscodes']['HOS-0'].keys()[0]
2868        ac = self.app['accesscodes']['HOS-0'][pin]
2869        parts = pin.split('-')[1:]
2870        sfeseries, sfenumber = parts
2871
2872        # Students can use HOS code and book a bed space with it ...
2873        self.browser.open(self.acco_path)
2874        # ... but not if booking period has expired ...
2875        self.app['hostels'].enddate = datetime.now(pytz.utc)
2876        self.browser.getLink("Book accommodation").click()
2877        self.assertMatches('...Outside booking period: ...',
2878                           self.browser.contents)
2879        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
2880        # ... or student is not the an allowed state ...
2881        self.browser.getLink("Book accommodation").click()
2882        self.assertMatches('...You are in the wrong...',
2883                           self.browser.contents)
2884        IWorkflowInfo(self.student).fireTransition('admit')
2885        self.browser.getLink("Book accommodation").click()
2886        self.assertMatches('...Activation Code:...',
2887                           self.browser.contents)
2888        # Student can't used faked ACs ...
2889        self.browser.getControl(name="ac_series").value = u'nonsense'
2890        self.browser.getControl(name="ac_number").value = sfenumber
2891        self.browser.getControl("Create bed ticket").click()
2892        self.assertMatches('...Activation code is invalid...',
2893                           self.browser.contents)
2894        # ... or ACs owned by somebody else.
2895        ac.owner = u'Anybody'
2896        self.browser.getControl(name="ac_series").value = sfeseries
2897        self.browser.getControl(name="ac_number").value = sfenumber
2898        self.browser.getControl("Create bed ticket").click()
2899        self.assertMatches('...You are not the owner of this access code...',
2900                           self.browser.contents)
2901        ac.owner = self.student_id
2902        self.browser.getControl(name="ac_series").value = sfeseries
2903        self.browser.getControl(name="ac_number").value = sfenumber
2904        self.browser.getControl("Create bed ticket").click()
2905        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2906                           self.browser.contents)
2907
2908        # Bed has been allocated
2909        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
2910        self.assertTrue(bed.owner == self.student_id)
2911
2912        # BedTicketAddPage is now blocked
2913        self.browser.getLink("Book accommodation").click()
2914        self.assertMatches('...You already booked a bed space...',
2915            self.browser.contents)
2916
2917        # The bed ticket displays the data correctly
2918        self.browser.open(self.acco_path + '/2004')
2919        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
2920                           self.browser.contents)
2921        self.assertMatches('...2004/2005...', self.browser.contents)
2922        self.assertMatches('...regular_male_fr...', self.browser.contents)
2923        self.assertMatches('...%s...' % pin, self.browser.contents)
2924
2925        # Students can open the pdf slip
2926        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
2927        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2928        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2929
2930        # Students can't relocate themselves
2931        self.assertFalse('Relocate' in self.browser.contents)
2932        relocate_path = self.acco_path + '/2004/relocate'
2933        self.assertRaises(
2934            Unauthorized, self.browser.open, relocate_path)
2935
2936        # Students can't the Remove button and check boxes
2937        self.browser.open(self.acco_path)
2938        self.assertFalse('Remove' in self.browser.contents)
2939        self.assertFalse('val_id' in self.browser.contents)
2940
2941        # Students can pay maintenance fee now
2942        self.browser.open(self.payments_path)
2943        self.browser.open(self.payments_path + '/addop')
2944        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2945        self.browser.getControl("Create ticket").click()
2946        self.assertMatches('...Payment ticket created...',
2947                           self.browser.contents)
2948
2949        ctrl = self.browser.getControl(name='val_id')
2950        value = ctrl.options[0]
2951        # Maintennace fee is taken from the hostel object
2952        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
2953        # If the hostel's maintenance fee isn't set, the fee is
2954        # taken from the session configuration object.
2955        self.app['hostels']['hall-1'].maint_fee = 0.0
2956        self.browser.open(self.payments_path + '/addop')
2957        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2958        self.browser.getControl("Create ticket").click()
2959        ctrl = self.browser.getControl(name='val_id')
2960        value = ctrl.options[1]
2961        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
2962        return
2963
2964    def test_change_password_request(self):
2965        self.browser.open('http://localhost/app/changepw')
2966        self.browser.getControl(name="form.identifier").value = '123'
2967        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
2968        self.browser.getControl("Send login credentials").click()
2969        self.assertTrue('An email with' in self.browser.contents)
2970
2971    def test_student_expired_personal_data(self):
2972        # Login
2973        IWorkflowState(self.student).setState('school fee paid')
2974        delta = timedelta(days=180)
2975        self.student.personal_updated = datetime.utcnow() - delta
2976        self.browser.open(self.login_path)
2977        self.browser.getControl(name="form.login").value = self.student_id
2978        self.browser.getControl(name="form.password").value = 'spwd'
2979        self.browser.getControl("Login").click()
2980        self.assertEqual(self.browser.url, self.student_path)
2981        self.assertTrue(
2982            'You logged in' in self.browser.contents)
2983        # Students don't see personal_updated field in edit form
2984        self.browser.open(self.edit_personal_path)
2985        self.assertFalse('Updated' in self.browser.contents)
2986        self.browser.open(self.personal_path)
2987        self.assertTrue('Updated' in self.browser.contents)
2988        self.browser.getLink("Logout").click()
2989        delta = timedelta(days=181)
2990        self.student.personal_updated = datetime.utcnow() - delta
2991        self.browser.open(self.login_path)
2992        self.browser.getControl(name="form.login").value = self.student_id
2993        self.browser.getControl(name="form.password").value = 'spwd'
2994        self.browser.getControl("Login").click()
2995        self.assertEqual(self.browser.url, self.edit_personal_path)
2996        self.assertTrue(
2997            'Your personal data record is outdated.' in self.browser.contents)
2998
2999    def test_setReturningData(self):
3000        utils = getUtility(IStudentsUtils)
3001        self.student['studycourse'].current_level = 600
3002        utils.setReturningData(self.student)
3003        # The new level exceeds the certificates end_level.
3004        # In this case current_level remains unchanged and no error is raised.
3005        self.assertEqual(self.student['studycourse'].current_level, 600)
3006
3007    def test_request_transcript(self):
3008        IWorkflowState(self.student).setState('graduated')
3009        self.browser.open(self.login_path)
3010        self.browser.getControl(name="form.login").value = self.student_id
3011        self.browser.getControl(name="form.password").value = 'spwd'
3012        self.browser.getControl("Login").click()
3013        self.assertMatches(
3014            '...You logged in...', self.browser.contents)
3015        # Create payment ticket
3016        self.browser.open(self.payments_path)
3017        self.browser.open(self.payments_path + '/addop')
3018        self.browser.getControl(name="form.p_category").value = ['transcript']
3019        self.browser.getControl("Create ticket").click()
3020        ctrl = self.browser.getControl(name='val_id')
3021        value = ctrl.options[0]
3022        self.browser.getLink(value).click()
3023        self.assertMatches('...Amount Authorized...',
3024                           self.browser.contents)
3025        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3026        # Student is the payer of the payment ticket.
3027        payer = IPayer(self.student['payments'][value])
3028        self.assertEqual(payer.display_fullname, 'Anna Tester')
3029        self.assertEqual(payer.id, self.student_id)
3030        self.assertEqual(payer.faculty, 'fac1')
3031        self.assertEqual(payer.department, 'dep1')
3032        # We simulate the approval and fetch the pin
3033        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3034        self.browser.open(self.browser.url + '/fake_approve')
3035        self.assertMatches('...Payment approved...',
3036                          self.browser.contents)
3037        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3038        parts = pin.split('-')[1:]
3039        tscseries, tscnumber = parts
3040        # Student can use the pin to send the transcript request
3041        self.browser.open(self.student_path)
3042        self.browser.getLink("Request transcript").click()
3043        self.browser.getControl(name="ac_series").value = tscseries
3044        self.browser.getControl(name="ac_number").value = tscnumber
3045        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3046        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3047        self.browser.getControl("Submit").click()
3048        self.assertMatches('...Transcript processing has been started...',
3049                          self.browser.contents)
3050        self.assertEqual(self.student.state, 'transcript requested')
3051        self.assertMatches(
3052            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3053            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3054            'Address line2\n\n', self.student.transcript_comment)
3055        # The comment has been logged
3056        logfile = os.path.join(
3057            self.app['datacenter'].storage, 'logs', 'students.log')
3058        logcontent = open(logfile).read()
3059        self.assertTrue(
3060            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3061            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3062            in logcontent)
3063
3064class StudentRequestPWTests(StudentsFullSetup):
3065    # Tests for student registration
3066
3067    layer = FunctionalLayer
3068
3069    def test_request_pw(self):
3070        # Student with wrong number can't be found.
3071        self.browser.open('http://localhost/app/requestpw')
3072        self.browser.getControl(name="form.firstname").value = 'Anna'
3073        self.browser.getControl(name="form.number").value = 'anynumber'
3074        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3075        self.browser.getControl("Send login credentials").click()
3076        self.assertTrue('No student record found.'
3077            in self.browser.contents)
3078        # Anonymous is not informed that firstname verification failed.
3079        # It seems that the record doesn't exist.
3080        self.browser.open('http://localhost/app/requestpw')
3081        self.browser.getControl(name="form.firstname").value = 'Johnny'
3082        self.browser.getControl(name="form.number").value = '123'
3083        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3084        self.browser.getControl("Send login credentials").click()
3085        self.assertTrue('No student record found.'
3086            in self.browser.contents)
3087        # Even with the correct firstname we can't register if a
3088        # password has been set and used.
3089        self.browser.getControl(name="form.firstname").value = 'Anna'
3090        self.browser.getControl(name="form.number").value = '123'
3091        self.browser.getControl("Send login credentials").click()
3092        self.assertTrue('Your password has already been set and used.'
3093            in self.browser.contents)
3094        self.browser.open('http://localhost/app/requestpw')
3095        self.app['students'][self.student_id].password = None
3096        # The firstname field, used for verification, is not case-sensitive.
3097        self.browser.getControl(name="form.firstname").value = 'aNNa'
3098        self.browser.getControl(name="form.number").value = '123'
3099        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3100        self.browser.getControl("Send login credentials").click()
3101        # Yeah, we succeded ...
3102        self.assertTrue('Your password request was successful.'
3103            in self.browser.contents)
3104        # We can also use the matric_number instead.
3105        self.browser.open('http://localhost/app/requestpw')
3106        self.browser.getControl(name="form.firstname").value = 'aNNa'
3107        self.browser.getControl(name="form.number").value = '234'
3108        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3109        self.browser.getControl("Send login credentials").click()
3110        self.assertTrue('Your password request was successful.'
3111            in self.browser.contents)
3112        # ... and  student can be found in the catalog via the email address
3113        cat = queryUtility(ICatalog, name='students_catalog')
3114        results = list(
3115            cat.searchResults(
3116            email=('new@yy.zz', 'new@yy.zz')))
3117        self.assertEqual(self.student,results[0])
3118        logfile = os.path.join(
3119            self.app['datacenter'].storage, 'logs', 'main.log')
3120        logcontent = open(logfile).read()
3121        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3122                        '234 (K1000000) - new@yy.zz' in logcontent)
3123        return
3124
3125    def test_student_locked_level_forms(self):
3126
3127        # Add two study levels, one current and one previous
3128        studylevel = createObject(u'waeup.StudentStudyLevel')
3129        studylevel.level = 100
3130        self.student['studycourse'].addStudentStudyLevel(
3131            self.certificate, studylevel)
3132        studylevel = createObject(u'waeup.StudentStudyLevel')
3133        studylevel.level = 200
3134        self.student['studycourse'].addStudentStudyLevel(
3135            self.certificate, studylevel)
3136        IWorkflowState(self.student).setState('school fee paid')
3137        self.student['studycourse'].current_level = 200
3138
3139        self.browser.open(self.login_path)
3140        self.browser.getControl(name="form.login").value = self.student_id
3141        self.browser.getControl(name="form.password").value = 'spwd'
3142        self.browser.getControl("Login").click()
3143
3144        self.browser.open(self.student_path + '/studycourse/200/edit')
3145        self.assertFalse('The requested form is locked' in self.browser.contents)
3146        self.browser.open(self.student_path + '/studycourse/100/edit')
3147        self.assertTrue('The requested form is locked' in self.browser.contents)
3148
3149        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3150        self.assertFalse('The requested form is locked' in self.browser.contents)
3151        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3152        self.assertTrue('The requested form is locked' in self.browser.contents)
3153
3154        IWorkflowState(self.student).setState('courses registered')
3155        self.browser.open(self.student_path + '/studycourse/200/edit')
3156        self.assertTrue('The requested form is locked' in self.browser.contents)
3157        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3158        self.assertTrue('The requested form is locked' in self.browser.contents)
3159
3160
3161class PublicPagesTests(StudentsFullSetup):
3162    # Tests for simple webservices
3163
3164    layer = FunctionalLayer
3165
3166    def test_paymentrequest(self):
3167        payment = createObject('waeup.StudentOnlinePayment')
3168        payment.p_category = u'schoolfee'
3169        payment.p_session = self.student.current_session
3170        payment.p_item = u'My Certificate'
3171        payment.p_id = u'anyid'
3172        self.student['payments']['anykey'] = payment
3173        # Request information about unpaid payment ticket
3174        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3175        self.assertEqual(self.browser.contents, '-1')
3176        # Request information about paid payment ticket
3177        payment.p_state = u'paid'
3178        notify(grok.ObjectModifiedEvent(payment))
3179        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3180        self.assertEqual(self.browser.contents,
3181            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3182            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3183            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3184            '&FEE_AMOUNT=0.0')
3185        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3186        self.assertEqual(self.browser.contents, '-1')
3187        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3188        self.assertEqual(self.browser.contents, '-1')
3189
3190class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3191    # Tests for StudentsContainer class views and pages
3192
3193    layer = FunctionalLayer
3194
3195    def wait_for_export_job_completed(self):
3196        # helper function waiting until the current export job is completed
3197        manager = getUtility(IJobManager)
3198        job_id = self.app['datacenter'].running_exports[0][0]
3199        job = manager.get(job_id)
3200        wait_for_result(job)
3201        return job_id
3202
3203    def test_faculties_export(self):
3204        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3205        facs_path = 'http://localhost/app/faculties'
3206        self.browser.open(facs_path)
3207        self.browser.getLink("Export student data").click()
3208        self.browser.getControl("Configure new export").click()
3209        self.browser.getControl(name="exporter").value = ['bursary']
3210        self.browser.getControl(name="session").value = ['2004']
3211        self.browser.getControl(name="level").value = ['100']
3212        self.browser.getControl(name="mode").value = ['ug_ft']
3213        self.browser.getControl("Create CSV file").click()
3214
3215        # When the job is finished and we reload the page...
3216        job_id = self.wait_for_export_job_completed()
3217        self.browser.open(facs_path + '/exports')
3218        # ... the csv file can be downloaded ...
3219        self.browser.getLink("Download").click()
3220        self.assertEqual(self.browser.headers['content-type'],
3221            'text/csv; charset=UTF-8')
3222        self.assertTrue(
3223            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3224            self.browser.headers['content-disposition'])
3225        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3226        job_id = self.app['datacenter'].running_exports[0][0]
3227        # ... and discarded
3228        self.browser.open(facs_path + '/exports')
3229        self.browser.getControl("Discard").click()
3230        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3231        # Creation, downloading and discarding is logged
3232        logfile = os.path.join(
3233            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3234        logcontent = open(logfile).read()
3235        self.assertTrue(
3236            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3237            '- exported: bursary (2004, 100, ug_ft, None, None), job_id=%s'
3238            % job_id in logcontent
3239            )
3240        self.assertTrue(
3241            'zope.mgr - students.browser.ExportJobContainerDownload '
3242            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3243            % (job_id, job_id) in logcontent
3244            )
3245        self.assertTrue(
3246            'zope.mgr - students.browser.ExportJobContainerOverview '
3247            '- discarded: job_id=%s' % job_id in logcontent
3248            )
3249
3250    def test_department_export(self):
3251        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3252        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3253        self.browser.open(dep1_path)
3254        self.browser.getLink("Export student data").click()
3255        self.browser.getControl("Configure new export").click()
3256        self.browser.getControl(name="exporter").value = ['students']
3257        self.browser.getControl(name="session").value = ['2004']
3258        self.browser.getControl(name="level").value = ['100']
3259        self.browser.getControl(name="mode").value = ['ug_ft']
3260        self.browser.getControl("Create CSV file").click()
3261
3262        # When the job is finished and we reload the page...
3263        job_id = self.wait_for_export_job_completed()
3264        self.browser.open(dep1_path + '/exports')
3265        # ... the csv file can be downloaded ...
3266        self.browser.getLink("Download").click()
3267        self.assertEqual(self.browser.headers['content-type'],
3268            'text/csv; charset=UTF-8')
3269        self.assertTrue(
3270            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3271            self.browser.headers['content-disposition'])
3272        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3273        job_id = self.app['datacenter'].running_exports[0][0]
3274        # ... and discarded
3275        self.browser.open(dep1_path + '/exports')
3276        self.browser.getControl("Discard").click()
3277        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3278        # Creation, downloading and discarding is logged
3279        logfile = os.path.join(
3280            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3281        logcontent = open(logfile).read()
3282        self.assertTrue(
3283            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3284            '- exported: students (2004, 100, ug_ft, dep1, None), job_id=%s'
3285            % job_id in logcontent
3286            )
3287        self.assertTrue(
3288            'zope.mgr - students.browser.ExportJobContainerDownload '
3289            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3290            % (job_id, job_id) in logcontent
3291            )
3292        self.assertTrue(
3293            'zope.mgr - students.browser.ExportJobContainerOverview '
3294            '- discarded: job_id=%s' % job_id in logcontent
3295            )
3296
3297    def test_certificate_export(self):
3298        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3299        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3300        self.browser.open(cert1_path)
3301        self.browser.getLink("Export student data").click()
3302        self.browser.getControl("Configure new export").click()
3303        self.browser.getControl(name="exporter").value = ['students']
3304        self.browser.getControl(name="session").value = ['2004']
3305        self.browser.getControl(name="level").value = ['100']
3306        self.browser.getControl("Create CSV file").click()
3307
3308        # When the job is finished and we reload the page...
3309        job_id = self.wait_for_export_job_completed()
3310        self.browser.open(cert1_path + '/exports')
3311        # ... the csv file can be downloaded ...
3312        self.browser.getLink("Download").click()
3313        self.assertEqual(self.browser.headers['content-type'],
3314            'text/csv; charset=UTF-8')
3315        self.assertTrue(
3316            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3317            self.browser.headers['content-disposition'])
3318        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3319        job_id = self.app['datacenter'].running_exports[0][0]
3320        # ... and discarded
3321        self.browser.open(cert1_path + '/exports')
3322        self.browser.getControl("Discard").click()
3323        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3324        # Creation, downloading and discarding is logged
3325        logfile = os.path.join(
3326            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3327        logcontent = open(logfile).read()
3328        self.assertTrue(
3329            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3330            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
3331            % job_id in logcontent
3332            )
3333        self.assertTrue(
3334            'zope.mgr - students.browser.ExportJobContainerDownload '
3335            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3336            % (job_id, job_id) in logcontent
3337            )
3338        self.assertTrue(
3339            'zope.mgr - students.browser.ExportJobContainerOverview '
3340            '- discarded: job_id=%s' % job_id in logcontent
3341            )
3342
3343    def test_course_export_students(self):
3344        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3345        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3346        self.browser.open(course1_path)
3347        self.browser.getLink("Export student data").click()
3348        self.browser.getControl("Configure new export").click()
3349        self.browser.getControl(name="exporter").value = ['students']
3350        self.browser.getControl(name="session").value = ['2004']
3351        self.browser.getControl(name="level").value = ['100']
3352        self.browser.getControl("Create CSV file").click()
3353
3354        # When the job is finished and we reload the page...
3355        job_id = self.wait_for_export_job_completed()
3356        self.browser.open(course1_path + '/exports')
3357        # ... the csv file can be downloaded ...
3358        self.browser.getLink("Download").click()
3359        self.assertEqual(self.browser.headers['content-type'],
3360            'text/csv; charset=UTF-8')
3361        self.assertTrue(
3362            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3363            self.browser.headers['content-disposition'])
3364        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3365        job_id = self.app['datacenter'].running_exports[0][0]
3366        # ... and discarded
3367        self.browser.open(course1_path + '/exports')
3368        self.browser.getControl("Discard").click()
3369        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3370        # Creation, downloading and discarding is logged
3371        logfile = os.path.join(
3372            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3373        logcontent = open(logfile).read()
3374        self.assertTrue(
3375            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3376            '- exported: students (2004, 100, COURSE1), job_id=%s'
3377            % job_id in logcontent
3378            )
3379        self.assertTrue(
3380            'zope.mgr - students.browser.ExportJobContainerDownload '
3381            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3382            % (job_id, job_id) in logcontent
3383            )
3384        self.assertTrue(
3385            'zope.mgr - students.browser.ExportJobContainerOverview '
3386            '- discarded: job_id=%s' % job_id in logcontent
3387            )
3388
3389    def test_course_export_coursetickets(self):
3390        # We add study level 100 to the student's studycourse
3391        studylevel = StudentStudyLevel()
3392        studylevel.level = 100
3393        studylevel.level_session = 2004
3394        self.student['studycourse'].addStudentStudyLevel(
3395            self.certificate,studylevel)
3396        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3397        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3398        self.browser.open(course1_path)
3399        self.browser.getLink("Export student data").click()
3400        self.browser.getControl("Configure new export").click()
3401        self.browser.getControl(name="exporter").value = ['coursetickets']
3402        self.browser.getControl(name="session").value = ['2004']
3403        self.browser.getControl(name="level").value = ['100']
3404        self.browser.getControl("Create CSV file").click()
3405        # When the job is finished and we reload the page...
3406        job_id = self.wait_for_export_job_completed()
3407        self.browser.open(course1_path + '/exports')
3408        # ... the csv file can be downloaded ...
3409        self.browser.getLink("Download").click()
3410        self.assertEqual(self.browser.headers['content-type'],
3411            'text/csv; charset=UTF-8')
3412        self.assertTrue(
3413            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3414            self.browser.headers['content-disposition'])
3415        # ... and contains the course ticket COURSE1
3416        self.assertEqual(self.browser.contents,
3417            'automatic,carry_over,code,credits,dcode,fcode,level,'
3418            'level_session,mandatory,passmark,score,semester,title,'
3419            'student_id,certcode\r\n1,0,COURSE1,10,dep1,fac1,100,2004,1,40,,1,'
3420            'Unnamed Course,K1000000,CERT1\r\n')
3421
3422        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3423        job_id = self.app['datacenter'].running_exports[0][0]
3424        # Thew job can be discarded
3425        self.browser.open(course1_path + '/exports')
3426        self.browser.getControl("Discard").click()
3427        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3428        # Creation, downloading and discarding is logged
3429        logfile = os.path.join(
3430            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3431        logcontent = open(logfile).read()
3432        self.assertTrue(
3433            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3434            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3435            % job_id in logcontent
3436            )
3437        self.assertTrue(
3438            'zope.mgr - students.browser.ExportJobContainerDownload '
3439            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3440            % (job_id, job_id) in logcontent
3441            )
3442        self.assertTrue(
3443            'zope.mgr - students.browser.ExportJobContainerOverview '
3444            '- discarded: job_id=%s' % job_id in logcontent
3445            )
3446
3447    def test_export_departmet_officers(self):
3448        # Create department officer
3449        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3450        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3451        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3452        # Assign local role
3453        department = self.app['faculties']['fac1']['dep1']
3454        prmlocal = IPrincipalRoleManager(department)
3455        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3456        # Login as department officer
3457        self.browser.open(self.login_path)
3458        self.browser.getControl(name="form.login").value = 'mrdepartment'
3459        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3460        self.browser.getControl("Login").click()
3461        self.assertMatches('...You logged in...', self.browser.contents)
3462        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3463        self.browser.getLink("Export student data").click()
3464        self.browser.getControl("Configure new export").click()
3465        # Only the paymentsoverview exporter is available for department officers
3466        self.assertFalse('<option value="students">' in self.browser.contents)
3467        self.assertTrue(
3468            '<option value="paymentsoverview">' in self.browser.contents)
3469        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3470        self.browser.getControl(name="session").value = ['2004']
3471        self.browser.getControl(name="level").value = ['100']
3472        self.browser.getControl("Create CSV file").click()
3473        self.assertTrue('Export started' in self.browser.contents)
3474        # Thew job can be discarded
3475        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3476        #job_id = self.app['datacenter'].running_exports[0][0]
3477        job_id = self.wait_for_export_job_completed()
3478        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3479        self.browser.getControl("Discard").click()
3480        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3481
3482    def test_export_bursary_officers(self):
3483        # Create bursary officer
3484        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3485        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3486        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3487        prmglobal = IPrincipalRoleManager(self.app)
3488        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3489        # Login as bursary officer
3490        self.browser.open(self.login_path)
3491        self.browser.getControl(name="form.login").value = 'mrbursary'
3492        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3493        self.browser.getControl("Login").click()
3494        self.assertMatches('...You logged in...', self.browser.contents)
3495        self.browser.getLink("Academics").click()
3496        self.browser.getLink("Export student data").click()
3497        self.browser.getControl("Configure new export").click()
3498        # Only the bursary exporter is available for bursary officers
3499        # not only at facultiescontainer level ...
3500        self.assertFalse('<option value="students">' in self.browser.contents)
3501        self.assertTrue('<option value="bursary">' in self.browser.contents)
3502        self.browser.getControl(name="exporter").value = ['bursary']
3503        self.browser.getControl(name="session").value = ['2004']
3504        self.browser.getControl(name="level").value = ['100']
3505        self.browser.getControl("Create CSV file").click()
3506        self.assertTrue('Export started' in self.browser.contents)
3507        # ... but also at other levels
3508        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3509        self.browser.getLink("Export student data").click()
3510        self.browser.getControl("Configure new export").click()
3511        self.assertFalse('<option value="students">' in self.browser.contents)
3512        self.assertTrue('<option value="bursary">' in self.browser.contents)
3513        # Thew job can be discarded
3514        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3515        #job_id = self.app['datacenter'].running_exports[0][0]
3516        job_id = self.wait_for_export_job_completed()
3517        self.browser.open('http://localhost/app/faculties/exports')
3518        self.browser.getControl("Discard").click()
3519        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.