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

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

Implement page to find students in faculties.

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