source: main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py @ 7375

Last change on this file since 7375 was 7372, checked in by Henrik Bettermann, 13 years ago

Fix tests.

  • Property svn:keywords set to Id
File size: 78.7 KB
Line 
1## $Id: test_browser.py 7372 2011-12-18 10:50:21Z 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
23from StringIO import StringIO
24from datetime import datetime
25import os
26import grok
27from zope.event import notify
28from zope.component import createObject
29from zope.component.hooks import setSite, clearSite
30from zope.security.interfaces import Unauthorized
31from zope.securitypolicy.interfaces import IPrincipalRoleManager
32from zope.testbrowser.testing import Browser
33from hurry.workflow.interfaces import IWorkflowInfo
34from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
35from waeup.sirp.app import University
36from waeup.sirp.configuration import SessionConfiguration
37from waeup.sirp.students.student import Student
38from waeup.sirp.students.studylevel import StudentStudyLevel
39from waeup.sirp.university.faculty import Faculty
40from waeup.sirp.university.department import Department
41from waeup.sirp.interfaces import IUserAccount
42from waeup.sirp.authentication import LocalRoleSetEvent
43from waeup.sirp.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
44
45PH_LEN = 2059  # Length of placeholder file
46
47def lookup_submit_value(name, value, browser):
48    """Find a button with a certain value."""
49    for num in range(0, 100):
50        try:
51            button = browser.getControl(name=name, index=num)
52            if button.value.endswith(value):
53                return button
54        except IndexError:
55            break
56    return None
57
58class StudentsFullSetup(FunctionalTestCase):
59    # A test case that only contains a setup and teardown
60    #
61    # Complete setup for students handlings is rather complex and
62    # requires lots of things created before we can start. This is a
63    # setup that does all this, creates a university, creates PINs,
64    # etc.  so that we do not have to bother with that in different
65    # test cases.
66
67    layer = FunctionalLayer
68
69    def setUp(self):
70        super(StudentsFullSetup, self).setUp()
71
72        # Setup a sample site for each test
73        app = University()
74        self.dc_root = tempfile.mkdtemp()
75        app['datacenter'].setStoragePath(self.dc_root)
76
77        # Prepopulate the ZODB...
78        self.getRootFolder()['app'] = app
79        # we add the site immediately after creation to the
80        # ZODB. Catalogs and other local utilities are not setup
81        # before that step.
82        self.app = self.getRootFolder()['app']
83        # Set site here. Some of the following setup code might need
84        # to access grok.getSite() and should get our new app then
85        setSite(app)
86
87        # Add student with subobjects
88        student = Student()
89        student.firstname = u'Anna'
90        student.lastname = u'Tester'
91        student.reg_number = u'123'
92        student.matric_number = u'234'
93        student.sex = u'm'
94        student.email = 'aa@aa.ng'
95        student.phone = u'1234'
96        self.app['students'].addStudent(student)
97        self.student_id = student.student_id
98        self.student = self.app['students'][self.student_id]
99
100        # Set password
101        IUserAccount(
102            self.app['students'][self.student_id]).setPassword('spwd')
103
104        self.login_path = 'http://localhost/app/login'
105        self.container_path = 'http://localhost/app/students'
106        self.manage_container_path = self.container_path + '/@@manage'
107        self.add_student_path = self.container_path + '/addstudent'
108        self.student_path = self.container_path + '/' + self.student_id
109        self.manage_student_path = self.student_path + '/manage_base'
110        self.clearance_student_path = self.student_path + '/view_clearance'
111        self.personal_student_path = self.student_path + '/view_personal'
112        self.edit_clearance_student_path = self.student_path + '/edit_clearance'
113        self.edit_personal_student_path = self.student_path + '/edit_personal'
114        self.studycourse_student_path = self.student_path + '/studycourse'
115        self.payments_student_path = self.student_path + '/payments'
116        self.acco_student_path = self.student_path + '/accommodation'
117        self.history_student_path = self.student_path + '/history'
118
119        # Create 5 access codes with prefix'PWD'
120        pin_container = self.app['accesscodes']
121        pin_container.createBatch(
122            datetime.now(), 'some_userid', 'PWD', 9.99, 5)
123        pins = pin_container['PWD-1'].values()
124        self.pwdpins = [x.representation for x in pins]
125        self.existing_pwdpin = self.pwdpins[0]
126        parts = self.existing_pwdpin.split('-')[1:]
127        self.existing_pwdseries, self.existing_pwdnumber = parts
128        # Create 5 access codes with prefix 'CLR'
129        pin_container.createBatch(
130            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
131        pins = pin_container['CLR-1'].values()
132        pins[0].owner = u'Hans Wurst'
133        self.existing_clrac = pins[0]
134        self.existing_clrpin = pins[0].representation
135        parts = self.existing_clrpin.split('-')[1:]
136        self.existing_clrseries, self.existing_clrnumber = parts
137        # Create 2 access codes with prefix 'HOS'
138        pin_container.createBatch(
139            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
140        pins = pin_container['HOS-1'].values()
141        self.existing_hosac = pins[0]
142        self.existing_hospin = pins[0].representation
143        parts = self.existing_hospin.split('-')[1:]
144        self.existing_hosseries, self.existing_hosnumber = parts
145
146        # Populate university
147        self.certificate = createObject('waeup.Certificate')
148        self.certificate.code = u'CERT1'
149        self.certificate.application_category = 'basic'
150        self.certificate.study_mode = 'ug_ft'
151        self.certificate.start_level = 100
152        self.certificate.end_level = 500
153        self.app['faculties']['fac1'] = Faculty()
154        self.app['faculties']['fac1']['dep1'] = Department(code='dep1')
155        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
156            self.certificate)
157        self.course = createObject('waeup.Course')
158        self.course.code = 'COURSE1'
159        self.course.semester = 1
160        self.course.credits = 10
161        self.course.passmark = 40
162        self.app['faculties']['fac1']['dep1'].courses.addCourse(
163            self.course)
164        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
165            self.course, level=100)
166
167        # Configure university
168        self.app['configuration'].accommodation_states = ['admitted']
169        self.app['configuration'].accommodation_session = 2004
170        configuration = SessionConfiguration()
171        # These attributes must also exist in the customization packages.
172        configuration.academic_session = 2004
173        configuration.fee_1 = 20000
174        configuration.boocking_fee = 500
175        self.app['configuration'].addSessionConfiguration(configuration)
176
177        # Create a hostel with two beds
178        hostel = Hostel()
179        hostel.hostel_id = u'hall-1'
180        hostel.hostel_name = u'Hall 1'
181        self.app['hostels'].addHostel(hostel)
182        bed = Bed()
183        bed.bed_id = u'hall-1_A_101_A'
184        bed.bed_number = 1
185        bed.owner = NOT_OCCUPIED
186        bed.bed_type = u'regular_male_fr'
187        self.app['hostels'][hostel.hostel_id].addBed(bed)
188        bed = Bed()
189        bed.bed_id = u'hall-1_A_101_B'
190        bed.bed_number = 2
191        bed.owner = NOT_OCCUPIED
192        bed.bed_type = u'regular_female_fr'
193        self.app['hostels'][hostel.hostel_id].addBed(bed)
194
195        # Set study course attributes of test student
196        self.student['studycourse'].certificate = self.certificate
197        self.student['studycourse'].current_session = 2004
198        self.student['studycourse'].entry_session = 2004
199        self.student['studycourse'].current_verdict = 'A'
200        self.student['studycourse'].current_level = 100
201        # Update the catalog
202        notify(grok.ObjectModifiedEvent(self.student))
203
204        # Put the prepopulated site into test ZODB and prepare test
205        # browser
206        self.browser = Browser()
207        self.browser.handleErrors = False
208
209    def tearDown(self):
210        super(StudentsFullSetup, self).tearDown()
211        clearSite()
212        shutil.rmtree(self.dc_root)
213
214
215
216class StudentsContainerUITests(StudentsFullSetup):
217    # Tests for StudentsContainer class views and pages
218
219    layer = FunctionalLayer
220
221    def test_anonymous_access(self):
222        # Anonymous users can't access students containers
223        self.assertRaises(
224            Unauthorized, self.browser.open, self.container_path)
225        self.assertRaises(
226            Unauthorized, self.browser.open, self.manage_container_path)
227        return
228
229    def test_manage_access(self):
230        # Managers can access the view page of students
231        # containers and can perform actions
232        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
233        self.browser.open(self.container_path)
234        self.assertEqual(self.browser.headers['Status'], '200 Ok')
235        self.assertEqual(self.browser.url, self.container_path)
236        self.browser.getLink("Manage student section").click()
237        self.assertEqual(self.browser.headers['Status'], '200 Ok')
238        self.assertEqual(self.browser.url, self.manage_container_path)
239        return
240
241    def test_add_search_delete_students(self):
242        # Managers can add search and remove students
243        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
244        self.browser.open(self.manage_container_path)
245        self.browser.getLink("Add student").click()
246        self.assertEqual(self.browser.headers['Status'], '200 Ok')
247        self.assertEqual(self.browser.url, self.add_student_path)
248        self.browser.getControl(name="form.firstname").value = 'Bob'
249        self.browser.getControl(name="form.lastname").value = 'Tester'
250        self.browser.getControl("Create student record").click()
251        self.assertTrue('Student record created' in self.browser.contents)
252
253        # Registration and matric numbers must be unique
254        self.browser.getLink("Manage").click()
255        self.browser.getControl(name="form.reg_number").value = '123'
256        self.browser.getControl("Save").click()
257        self.assertMatches('...Registration number exists...',
258                           self.browser.contents)
259        self.browser.getControl(name="form.reg_number").value = '789'
260        self.browser.getControl(name="form.matric_number").value = '234'
261        self.browser.getControl("Save").click()
262        self.assertMatches('...Matriculation number exists...',
263                           self.browser.contents)
264
265        # We can find a student with a certain student_id
266        self.browser.open(self.container_path)
267        self.browser.getControl("Search").click()
268        self.assertTrue('Empty search string' in self.browser.contents)
269        self.browser.getControl(name="searchtype").value = ['student_id']
270        self.browser.getControl(name="searchterm").value = self.student_id
271        self.browser.getControl("Search").click()
272        self.assertTrue('Anna Tester' in self.browser.contents)
273
274        # We can find a student in a certain department
275        self.browser.open(self.container_path)
276        self.browser.getControl(name="searchtype").value = ['depcode']
277        self.browser.getControl(name="searchterm").value = 'dep1'
278        self.browser.getControl("Search").click()
279        self.assertTrue('Anna Tester' in self.browser.contents)
280
281        # We can find a student by searching for all kind of name parts
282        self.browser.open(self.manage_container_path)
283        self.browser.getControl("Search").click()
284        self.assertTrue('Empty search string' in self.browser.contents)
285        self.browser.getControl(name="searchtype").value = ['fullname']
286        self.browser.getControl(name="searchterm").value = 'Anna Tester'
287        self.browser.getControl("Search").click()
288        self.assertTrue('Anna Tester' in self.browser.contents)
289        self.browser.open(self.manage_container_path)
290        self.browser.getControl(name="searchtype").value = ['fullname']
291        self.browser.getControl(name="searchterm").value = 'Anna'
292        self.browser.getControl("Search").click()
293        self.assertTrue('Anna Tester' in self.browser.contents)
294        self.browser.open(self.manage_container_path)
295        self.browser.getControl(name="searchtype").value = ['fullname']
296        self.browser.getControl(name="searchterm").value = 'Tester'
297        self.browser.getControl("Search").click()
298        self.assertTrue('Anna Tester' in self.browser.contents)
299        self.browser.open(self.manage_container_path)
300        self.browser.getControl(name="searchtype").value = ['fullname']
301        self.browser.getControl(name="searchterm").value = 'An'
302        self.browser.getControl("Search").click()
303        self.assertFalse('Anna Tester' in self.browser.contents)
304        self.browser.open(self.manage_container_path)
305        self.browser.getControl(name="searchtype").value = ['fullname']
306        self.browser.getControl(name="searchterm").value = 'An*'
307        self.browser.getControl("Search").click()
308        self.assertTrue('Anna Tester' in self.browser.contents)
309        self.browser.open(self.manage_container_path)
310        self.browser.getControl(name="searchtype").value = ['fullname']
311        self.browser.getControl(name="searchterm").value = 'tester'
312        self.browser.getControl("Search").click()
313        self.assertTrue('Anna Tester' in self.browser.contents)
314        self.browser.open(self.manage_container_path)
315        self.browser.getControl(name="searchtype").value = ['fullname']
316        self.browser.getControl(name="searchterm").value = 'Tester Ana'
317        self.browser.getControl("Search").click()
318        self.assertFalse('Anna Tester' in self.browser.contents)
319        self.browser.open(self.manage_container_path)
320        self.browser.getControl(name="searchtype").value = ['fullname']
321        self.browser.getControl(name="searchterm").value = 'Tester Anna'
322        self.browser.getControl("Search").click()
323        self.assertTrue('Anna Tester' in self.browser.contents)
324        # The old searchterm will be used again
325        self.browser.getControl("Search").click()
326        self.assertTrue('Anna Tester' in self.browser.contents)
327
328        ctrl = self.browser.getControl(name='entries')
329        ctrl.getControl(value=self.student_id).selected = True
330        self.browser.getControl("Remove selected", index=0).click()
331        self.assertTrue('Successfully removed' in self.browser.contents)
332        self.browser.getControl(name="searchtype").value = ['student_id']
333        self.browser.getControl(name="searchterm").value = self.student_id
334        self.browser.getControl("Search").click()
335        self.assertTrue('No student found' in self.browser.contents)
336
337        self.browser.open(self.container_path)
338        self.browser.getControl(name="searchtype").value = ['student_id']
339        self.browser.getControl(name="searchterm").value = self.student_id
340        self.browser.getControl("Search").click()
341        self.assertTrue('No student found' in self.browser.contents)
342        return
343
344class StudentUITests(StudentsFullSetup):
345    # Tests for Student class views and pages
346
347    layer = FunctionalLayer
348
349    def test_manage_access(self):
350        # Managers can access the pages of students
351        # and can perform actions
352        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
353
354        self.browser.open(self.student_path)
355        self.assertEqual(self.browser.headers['Status'], '200 Ok')
356        self.assertEqual(self.browser.url, self.student_path)
357        self.browser.getLink("Manage").click()
358        self.assertEqual(self.browser.headers['Status'], '200 Ok')
359        self.assertEqual(self.browser.url, self.manage_student_path)
360        # Managers can edit base data and fire transitions
361        self.browser.getControl(name="transition").value = ['admit']
362        self.browser.getControl(name="form.firstname").value = 'John'
363        self.browser.getControl(name="form.lastname").value = 'Tester'
364        self.browser.getControl(name="form.reg_number").value = '345'
365        self.browser.getControl(name="password").value = 'secret'
366        self.browser.getControl(name="control_password").value = 'secret'
367        self.browser.getControl("Save").click()
368        self.assertMatches('...Form has been saved...',
369                           self.browser.contents)
370        self.browser.open(self.student_path)
371        self.browser.getLink("Clearance Data").click()
372        self.assertEqual(self.browser.headers['Status'], '200 Ok')
373        self.assertEqual(self.browser.url, self.clearance_student_path)
374        self.browser.getLink("Manage").click()
375        self.assertEqual(self.browser.headers['Status'], '200 Ok')
376        self.assertEqual(self.browser.url, self.edit_clearance_student_path)
377        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
378        self.browser.getControl("Save").click()
379        self.assertMatches('...Form has been saved...',
380                           self.browser.contents)
381
382        self.browser.open(self.student_path)
383        self.browser.getLink("Personal Data").click()
384        self.assertEqual(self.browser.headers['Status'], '200 Ok')
385        self.assertEqual(self.browser.url, self.personal_student_path)
386        self.browser.getLink("Manage").click()
387        self.assertEqual(self.browser.headers['Status'], '200 Ok')
388        self.assertEqual(self.browser.url, self.edit_personal_student_path)
389        self.browser.getControl("Save").click()
390        self.assertMatches('...Form has been saved...',
391                           self.browser.contents)
392
393        # Managers can browse all subobjects
394        self.browser.open(self.student_path)
395        self.browser.getLink("Payments").click()
396        self.assertEqual(self.browser.headers['Status'], '200 Ok')
397        self.assertEqual(self.browser.url, self.payments_student_path)
398        self.browser.open(self.student_path)
399        self.browser.getLink("Accommodation").click()
400        self.assertEqual(self.browser.headers['Status'], '200 Ok')
401        self.assertEqual(self.browser.url, self.acco_student_path)
402        self.browser.open(self.student_path)
403        self.browser.getLink("History").click()
404        self.assertEqual(self.browser.headers['Status'], '200 Ok')
405        self.assertEqual(self.browser.url, self.history_student_path)
406        self.assertMatches('...Student admitted by Manager...',
407                           self.browser.contents)
408        # Only the Application Slip does not exist
409        self.assertFalse('Application Slip' in self.browser.contents)
410        return
411
412    def test_manage_remove_department(self):
413        # Lazy student is studying CERT1
414        lazystudent = Student()
415        lazystudent.firstname = u'Lazy'
416        lazystudent.lastname = u'Student'
417        self.app['students'].addStudent(lazystudent)
418        student_id = lazystudent.student_id
419        student_path = self.container_path + '/' + student_id
420        lazystudent['studycourse'].certificate = self.certificate
421        notify(grok.ObjectModifiedEvent(lazystudent))
422        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
423        self.browser.open(student_path + '/studycourse')
424        self.assertTrue('CERT1' in self.browser.contents)
425        # After some years the department is removed
426        del self.app['faculties']['fac1']['dep1']
427        # So CERT1 does no longer exist and lazy student's
428        # certificate reference is removed too
429        self.browser.open(student_path + '/studycourse')
430        self.assertEqual(self.browser.headers['Status'], '200 Ok')
431        self.assertEqual(self.browser.url, student_path + '/studycourse')
432        self.assertFalse('CERT1' in self.browser.contents)
433        self.assertMatches('...<div class="widget">Nothing</div>...',
434                           self.browser.contents)
435
436    def test_manage_upload_file(self):
437        # Managers can upload a file via the StudentClearanceManageFormPage
438        # The image is stored even if form has errors
439        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
440        self.browser.open(self.edit_clearance_student_path)
441        # No birth certificate has been uploaded yet
442        # Browsing the link shows a placerholder image
443        self.browser.open('birth_certificate')
444        self.assertEqual(
445            self.browser.headers['content-type'], 'image/jpeg')
446        self.assertEqual(len(self.browser.contents), PH_LEN)
447        # Create a pseudo image file and select it to be uploaded in form
448        # as birth certificate
449        self.browser.open(self.edit_clearance_student_path)
450        pseudo_image = StringIO('I pretend to be a graphics file')
451        ctrl = self.browser.getControl(name='birthcertificateupload')
452        file_ctrl = ctrl.mech_control
453        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
454        # The Save action does not upload files
455        self.browser.getControl("Save").click() # submit form
456        self.assertFalse(
457            '<a target="image" href="birth_certificate">'
458            in self.browser.contents)
459        # ... but the correct upload submit button does
460        pseudo_image = StringIO('I pretend to be a graphics file')
461        ctrl = self.browser.getControl(name='birthcertificateupload')
462        file_ctrl = ctrl.mech_control
463        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
464        self.browser.getControl(
465            name='upload_birthcertificateupload').click()
466        # There is a correct <img> link included
467        self.assertTrue(
468            '<a target="image" href="birth_certificate">'
469            in self.browser.contents)
470
471        # Browsing the link shows a real image
472        self.browser.open('birth_certificate')
473        self.assertEqual(
474            self.browser.headers['content-type'], 'image/jpeg')
475        self.assertEqual(len(self.browser.contents), 31)
476        # Reuploading a file which is bigger than 150k will raise an error
477        self.browser.open(self.edit_clearance_student_path)
478        photo_content = 'A' * 1024 * 151  # A string of 21 KB size
479        pseudo_image = StringIO(photo_content)
480        ctrl = self.browser.getControl(name='birthcertificateupload')
481        file_ctrl = ctrl.mech_control
482        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.jpg')
483        self.browser.getControl(
484            name='upload_birthcertificateupload').click()
485        self.assertTrue(
486            'Uploaded file is too big' in self.browser.contents)
487        # File names must meet several conditions
488        pseudo_image = StringIO('I pretend to be a graphics file')
489        ctrl = self.browser.getControl(name='birthcertificateupload')
490        file_ctrl = ctrl.mech_control
491        file_ctrl.add_file(pseudo_image, filename='my.photo.jpg')
492        self.browser.getControl(
493            name='upload_birthcertificateupload').click()
494        self.assertTrue('File name contains more than one dot'
495            in self.browser.contents)
496        ctrl = self.browser.getControl(name='birthcertificateupload')
497        file_ctrl = ctrl.mech_control
498        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate')
499        self.browser.getControl(
500            name='upload_birthcertificateupload').click()
501        self.assertTrue('File name has no extension' in self.browser.contents)
502        ctrl = self.browser.getControl(name='birthcertificateupload')
503        file_ctrl = ctrl.mech_control
504        file_ctrl.add_file(pseudo_image, filename='my_birth_certificate.bmp')
505        self.browser.getControl(
506            name='upload_birthcertificateupload').click()
507        self.assertTrue('Only the following extension are allowed'
508            in self.browser.contents)
509        # Managers can delete files
510        self.browser.getControl(name='delete_birthcertificateupload').click()
511        self.assertTrue(
512            'birth_certificate deleted' in self.browser.contents)
513        # Managers can add and delete second file
514        self.browser.open(self.edit_clearance_student_path)
515        pseudo_image = StringIO('I pretend to be a graphics file')
516        ctrl = self.browser.getControl(name='birthcertificateupload')
517        file_ctrl = ctrl.mech_control
518        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
519        self.browser.getControl(
520            name='upload_acceptanceletterupload').click()
521        self.assertFalse(
522            '<a target="image" href="acceptance_letter">'
523            in self.browser.contents)
524        ctrl = self.browser.getControl(name='acceptanceletterupload')
525        file_ctrl = ctrl.mech_control
526        file_ctrl.add_file(pseudo_image, filename='my_acceptance_letter.jpg')
527        self.browser.getControl(
528            name='upload_acceptanceletterupload').click()
529        self.assertTrue(
530            '<a target="image" href="acceptance_letter">'
531            in self.browser.contents)
532        self.browser.getControl(
533            name='delete_acceptanceletterupload').click()
534        self.assertTrue(
535            'acceptance_letter deleted'
536            in self.browser.contents)
537        # Managers can upload a file via the StudentBaseManageFormPage
538        self.browser.open(self.manage_student_path)
539        pseudo_image = StringIO('I pretend to be a graphics file')
540        ctrl = self.browser.getControl(name='passportuploadmanage')
541        file_ctrl = ctrl.mech_control
542        file_ctrl.add_file(pseudo_image, filename='my_photo.bmp')
543        self.browser.getControl(
544            name='upload_passportuploadmanage').click()
545        self.assertTrue('jpg file extension expected'
546            in self.browser.contents)
547        ctrl = self.browser.getControl(name='passportuploadmanage')
548        file_ctrl = ctrl.mech_control
549        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
550        self.browser.getControl(
551            name='upload_passportuploadmanage').click()
552        self.assertTrue(
553            '<img align="middle" height="125px" src="passport.jpg" />'
554            in self.browser.contents)
555        # The clearance slip can't be opened because it requires a proper
556        # passport jpg file.
557        self.browser.open(self.student_path + '/clearance.pdf')
558        self.assertEqual(self.browser.headers['Status'], '200 Ok')
559        self.assertTrue('Error in image file' in self.browser.contents)
560        # We remove the passport file again
561        self.browser.open(self.manage_student_path)
562        self.browser.getControl('Delete').click()
563        self.browser.open(self.student_path + '/clearance.pdf')
564        self.assertEqual(self.browser.headers['Status'], '200 Ok')
565        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
566
567    def test_manage_course_lists(self):
568        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
569        self.browser.open(self.student_path)
570        self.browser.getLink("Study Course").click()
571        self.assertEqual(self.browser.headers['Status'], '200 Ok')
572        self.assertEqual(self.browser.url, self.studycourse_student_path)
573        self.browser.getLink("Manage").click()
574        self.assertTrue('Manage study course' in self.browser.contents)
575        # Before we can select a level, the certificate must
576        # be selected and saved
577        self.browser.getControl(name="form.certificate").value = ['CERT1']
578        self.browser.getControl(name="form.current_session").value = ['2004']
579        self.browser.getControl(name="form.current_verdict").value = ['A']
580        self.browser.getControl("Save").click()
581        # Now we can save also the current level which depends on start and end
582        # level of the certificate
583        self.browser.getControl(name="form.current_level").value = ['100']
584        self.browser.getControl("Save").click()
585        # Managers can add and remove any study level (course list)
586        self.browser.getControl(name="addlevel").value = ['100']
587        self.browser.getControl("Add study level").click()
588        self.assertMatches('...<span>100</span>...', self.browser.contents)
589        self.browser.getControl("Add study level").click()
590        self.assertMatches('...This level exists...', self.browser.contents)
591        self.browser.getControl("Remove selected").click()
592        self.assertMatches(
593            '...No study level selected...', self.browser.contents)
594        self.browser.getControl(name="val_id").value = ['100']
595        self.browser.getControl("Remove selected").click()
596        self.assertMatches('...Successfully removed...', self.browser.contents)
597        # Add level again
598        self.browser.getControl(name="addlevel").value = ['100']
599        self.browser.getControl("Add study level").click()
600        self.browser.getControl(name="addlevel").value = ['100']
601
602        # Managers can view and manage course lists
603        self.browser.getLink("100").click()
604        self.assertMatches(
605            '...: Study Level 100 (Year 1)...', self.browser.contents)
606        self.browser.getLink("Manage").click()
607        self.browser.getControl(name="form.level_session").value = ['2002']
608        self.browser.getControl("Save").click()
609        self.browser.getControl("Remove selected").click()
610        self.assertMatches('...No ticket selected...', self.browser.contents)
611        ctrl = self.browser.getControl(name='val_id')
612        ctrl.getControl(value='COURSE1').selected = True
613        self.browser.getControl("Remove selected", index=0).click()
614        self.assertTrue('Successfully removed' in self.browser.contents)
615        self.browser.getControl("Add course ticket").click()
616        self.browser.getControl(name="form.course").value = ['COURSE1']
617        self.browser.getControl("Add course ticket").click()
618        self.assertTrue('Successfully added' in self.browser.contents)
619        self.browser.getControl("Add course ticket").click()
620        self.browser.getControl(name="form.course").value = ['COURSE1']
621        self.browser.getControl("Add course ticket").click()
622        self.assertTrue('The ticket exists' in self.browser.contents)
623        self.browser.getControl("Cancel").click()
624        self.browser.getLink("COURSE1").click()
625        self.browser.getLink("Manage").click()
626        self.browser.getControl(name="form.score").value = '10'
627        self.browser.getControl("Save").click()
628        self.assertTrue('Form has been saved' in self.browser.contents)
629        return
630
631    def test_manage_workflow(self):
632        # Managers can pass through the whole workflow
633        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
634        student = self.app['students'][self.student_id]
635        self.browser.open(self.manage_student_path)
636        self.assertTrue(student.clearance_locked)
637        self.browser.getControl(name="transition").value = ['admit']
638        self.browser.getControl("Save").click()
639        self.assertTrue(student.clearance_locked)
640        self.browser.getControl(name="transition").value = ['start_clearance']
641        self.browser.getControl("Save").click()
642        self.assertFalse(student.clearance_locked)
643        self.browser.getControl(name="transition").value = ['request_clearance']
644        self.browser.getControl("Save").click()
645        self.assertTrue(student.clearance_locked)
646        self.browser.getControl(name="transition").value = ['clear']
647        self.browser.getControl("Save").click()
648        self.browser.getControl(
649            name="transition").value = ['pay_first_school_fee']
650        self.browser.getControl("Save").click()
651        self.browser.getControl(name="transition").value = ['reset6']
652        self.browser.getControl("Save").click()
653        # In state returning the pay_school_fee transition triggers some
654        # changes of attributes
655        self.browser.getControl(name="transition").value = ['pay_school_fee']
656        self.browser.getControl("Save").click()
657        self.assertEqual(student['studycourse'].current_session, 2005) # +1
658        self.assertEqual(student['studycourse'].current_level, 200) # +100
659        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = not set
660        self.assertEqual(student['studycourse'].previous_verdict, 'A')
661        self.browser.getControl(name="transition").value = ['register_courses']
662        self.browser.getControl("Save").click()
663        self.browser.getControl(name="transition").value = ['validate_courses']
664        self.browser.getControl("Save").click()
665        self.browser.getControl(name="transition").value = ['return']
666        self.browser.getControl("Save").click()
667        return
668
669    def test_manage_import(self):
670        # Managers can import student data files
671        datacenter_path = 'http://localhost/app/datacenter'
672        # Prepare a csv file for students
673        open('students.csv', 'wb').write(
674"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone
675Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234
676Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234
677Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234
678""")
679        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
680        self.browser.open(datacenter_path)
681        self.browser.getLink('Upload CSV file').click()
682        filecontents = StringIO(open('students.csv', 'rb').read())
683        filewidget = self.browser.getControl(name='uploadfile:file')
684        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
685        self.browser.getControl(name='SUBMIT').click()
686        self.browser.getLink('Batch processing').click()
687        button = lookup_submit_value(
688            'select', 'students_zope.mgr.csv', self.browser)
689        button.click()
690        importerselect = self.browser.getControl(name='importer')
691        modeselect = self.browser.getControl(name='mode')
692        importerselect.getControl('Student Importer').selected = True
693        modeselect.getControl(value='create').selected = True
694        self.browser.getControl('Proceed to step 3...').click()
695        self.assertTrue('Header fields OK' in self.browser.contents)
696        self.browser.getControl('Perform import...').click()
697        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
698        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
699        self.assertTrue('Batch processing finished' in self.browser.contents)
700        open('studycourses.csv', 'wb').write(
701"""reg_number,matric_number,certificate,current_session,current_level
7021,,CERT1,2008,100
703,100001,CERT1,2008,100
704,100002,CERT1,2008,100
705""")
706        self.browser.open(datacenter_path)
707        self.browser.getLink('Upload CSV file').click()
708        filecontents = StringIO(open('studycourses.csv', 'rb').read())
709        filewidget = self.browser.getControl(name='uploadfile:file')
710        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
711        self.browser.getControl(name='SUBMIT').click()
712        self.browser.getLink('Batch processing').click()
713        button = lookup_submit_value(
714            'select', 'studycourses_zope.mgr.csv', self.browser)
715        button.click()
716        importerselect = self.browser.getControl(name='importer')
717        modeselect = self.browser.getControl(name='mode')
718        importerselect.getControl(
719            'StudentStudyCourse Importer (update only)').selected = True
720        modeselect.getControl(value='create').selected = True
721        self.browser.getControl('Proceed to step 3...').click()
722        self.assertTrue('Update mode only' in self.browser.contents)
723        self.browser.getControl('Proceed to step 3...').click()
724        self.assertTrue('Header fields OK' in self.browser.contents)
725        self.browser.getControl('Perform import...').click()
726        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
727        self.assertTrue('Successfully processed 2 rows'
728                        in self.browser.contents)
729        return
730
731    def test_handle_clearance_by_co(self):
732        # Create clearance officer
733        self.app['users'].addUser('mrclear', 'mrclearsecret')
734        # Clearance officers need not necessarily to get
735        # the StudentsOfficer site role
736        #prmglobal = IPrincipalRoleManager(self.app)
737        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
738        # Assign local ClearanceOfficer role
739        department = self.app['faculties']['fac1']['dep1']
740        prmlocal = IPrincipalRoleManager(department)
741        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
742        IWorkflowInfo(self.student).fireTransition('admit')
743        IWorkflowInfo(self.student).fireTransition('start_clearance')
744        # Login as clearance officer
745        self.browser.open(self.login_path)
746        self.browser.getControl(name="form.login").value = 'mrclear'
747        self.browser.getControl(name="form.password").value = 'mrclearsecret'
748        self.browser.getControl("Login").click()
749        self.assertMatches('...You logged in...', self.browser.contents)
750        # CO can see his roles
751        self.browser.getLink("My Roles").click()
752        self.assertMatches(
753            '...<div>Academics Officer (view only)</div>...',
754            self.browser.contents)
755        #self.assertMatches(
756        #    '...<div>Students Officer (view only)</div>...',
757        #    self.browser.contents)
758        # But not his local role ...
759        self.assertFalse('Clearance Officer' in self.browser.contents)
760        # ... because we forgot to notify the department that the local role
761        # has changed
762        notify(LocalRoleSetEvent(
763            department, 'waeup.local.ClearanceOfficer', 'mrclear', granted=True))
764        self.browser.open('http://localhost/app/users/mrclear/my_roles')
765        self.assertTrue('Clearance Officer' in self.browser.contents)
766        self.assertMatches(
767            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
768            self.browser.contents)
769        # CO can view the student ...
770        self.browser.open(self.clearance_student_path)
771        self.assertEqual(self.browser.headers['Status'], '200 Ok')
772        self.assertEqual(self.browser.url, self.clearance_student_path)
773        # ... but not other students
774        other_student = Student()
775        other_student.firstname = u'Dep2'
776        other_student.lastname = u'Student'
777        self.app['students'].addStudent(other_student)
778        other_student_path = (
779            'http://localhost/app/students/%s' % other_student.student_id)
780        self.assertRaises(
781            Unauthorized, self.browser.open, other_student_path)
782        # Only in state clearance requested the CO does see the 'Clear' button
783        self.browser.open(self.clearance_student_path)
784        self.assertFalse('Clear student' in self.browser.contents)
785        IWorkflowInfo(self.student).fireTransition('request_clearance')
786        self.browser.open(self.clearance_student_path)
787        self.assertTrue('Clear student' in self.browser.contents)
788        self.browser.getLink("Clear student").click()
789        self.assertTrue('Student has been cleared' in self.browser.contents)
790        self.assertTrue('State: <span>cleared</span>' in self.browser.contents)
791        self.browser.getLink("Reject clearance").click()
792        self.assertTrue('Clearance has been annulled' in self.browser.contents)
793        urlmessage = 'Clearance+has+been+annulled'
794        self.assertEqual(self.browser.url, self.student_path +
795            '/contactstudent?subject=%s' % urlmessage)
796        self.assertTrue('State: <span>clearance started</span>'
797            in self.browser.contents)
798        IWorkflowInfo(self.student).fireTransition('request_clearance')
799        self.browser.open(self.clearance_student_path)
800        self.browser.getLink("Reject clearance").click()
801        self.assertTrue('Clearance request has been rejected'
802            in self.browser.contents)
803        self.assertTrue('State: <span>clearance started</span>'
804            in self.browser.contents)
805        # The CO can't clear students if not in state
806        # clearance requested
807        self.browser.open(self.student_path + '/clear')
808        self.assertTrue('Student is in the wrong state'
809            in self.browser.contents)
810        # The CO can go to his department throug the my_roles page
811        self.browser.open('http://localhost/app/users/mrclear/my_roles')
812        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
813        # and view the list of students
814        self.browser.getLink("Show students").click()
815        self.assertTrue(self.student_id in self.browser.contents)
816
817    def test_handle_courses_by_ca(self):
818        # Create course adviser
819        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
820        # Assign local CourseAdviser100 role for a certificate
821        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
822        prmlocal = IPrincipalRoleManager(cert)
823        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
824        IWorkflowInfo(self.student).fireTransition('admit')
825        IWorkflowInfo(self.student).fireTransition('start_clearance')
826        IWorkflowInfo(self.student).fireTransition('request_clearance')
827        IWorkflowInfo(self.student).fireTransition('clear')
828        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
829        # Login as course adviser
830        self.browser.open(self.login_path)
831        self.browser.getControl(name="form.login").value = 'mrsadvise'
832        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
833        self.browser.getControl("Login").click()
834        self.assertMatches('...You logged in...', self.browser.contents)
835        # CO can see his roles
836        self.browser.getLink("My Roles").click()
837        self.assertMatches(
838            '...<div>Academics Officer (view only)</div>...',
839            self.browser.contents)
840        # But not his local role ...
841        self.assertFalse('Course Adviser' in self.browser.contents)
842        # ... because we forgot to notify the certificate that the local role
843        # has changed
844        notify(LocalRoleSetEvent(
845            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
846        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
847        self.assertTrue('Course Adviser 100L' in self.browser.contents)
848        self.assertMatches(
849            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
850            self.browser.contents)
851        # CA can view the student ...
852        self.browser.open(self.student_path)
853        self.assertEqual(self.browser.headers['Status'], '200 Ok')
854        self.assertEqual(self.browser.url, self.student_path)
855        # ... but not other students
856        other_student = Student()
857        other_student.firstname = u'Dep2'
858        other_student.lastname = u'Student'
859        self.app['students'].addStudent(other_student)
860        other_student_path = (
861            'http://localhost/app/students/%s' % other_student.student_id)
862        self.assertRaises(
863            Unauthorized, self.browser.open, other_student_path)
864        # We add study level 110 to the student's studycourse
865        studylevel = StudentStudyLevel()
866        studylevel.level = 110
867        self.student['studycourse'].addStudentStudyLevel(
868            cert,studylevel)
869        L110_student_path = self.studycourse_student_path + '/110'
870        # Only in state courses registered and only if the current level
871        # corresponds with the name of the study level object
872        # the 100L CA does see the 'Validate' button
873        self.browser.open(L110_student_path)
874        self.assertFalse('Validate' in self.browser.contents)
875        IWorkflowInfo(self.student).fireTransition('register_courses')
876        self.browser.open(L110_student_path)
877        self.assertFalse('Validate' in self.browser.contents)
878        self.student['studycourse'].current_level = 110
879        self.browser.open(L110_student_path)
880        self.assertTrue('Validate' in self.browser.contents)
881        # ... but a 100L CA does not see the button on other levels
882        studylevel2 = StudentStudyLevel()
883        studylevel2.level = 200
884        self.student['studycourse'].addStudentStudyLevel(
885            cert,studylevel2)
886        L200_student_path = self.studycourse_student_path + '/200'
887        self.browser.open(L200_student_path)
888        self.assertFalse('Validate' in self.browser.contents)
889        self.browser.open(L110_student_path)
890        self.browser.getLink("Validate courses").click()
891        self.assertTrue('Course list has been validated' in self.browser.contents)
892        self.assertTrue('State: <span>courses validated</span>' in self.browser.contents)
893        self.browser.getLink("Reject courses").click()
894        self.assertTrue('Course list request has been annulled'
895            in self.browser.contents)
896        urlmessage = 'Course+list+request+has+been+annulled'
897        self.assertEqual(self.browser.url, self.student_path +
898            '/contactstudent?subject=%s' % urlmessage)
899        self.assertTrue('State: <span>school fee paid</span>'
900            in self.browser.contents)
901        IWorkflowInfo(self.student).fireTransition('register_courses')
902        self.browser.open(L110_student_path)
903        self.browser.getLink("Reject courses").click()
904        self.assertTrue('Course list request has been rejected'
905            in self.browser.contents)
906        self.assertTrue('State: <span>school fee paid</span>'
907            in self.browser.contents)
908        # The CA can't validate courses if not in state
909        # courses registered
910        self.browser.open(L110_student_path + '/validate_courses')
911        self.assertTrue('Student is in the wrong state'
912            in self.browser.contents)
913        # The CA can go to his certificate throug the my_roles page
914        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
915        self.browser.getLink(
916            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
917        # and view the list of students
918        self.browser.getLink("Show students").click()
919        self.assertTrue(self.student_id in self.browser.contents)
920
921    def test_student_change_password(self):
922        # Students can change the password
923        self.browser.open(self.login_path)
924        self.browser.getControl(name="form.login").value = self.student_id
925        self.browser.getControl(name="form.password").value = 'spwd'
926        self.browser.getControl("Login").click()
927        self.assertEqual(self.browser.url, self.student_path)
928        self.assertTrue('You logged in' in self.browser.contents)
929        # Change password
930        self.browser.getLink("Change password").click()
931        self.browser.getControl(name="change_password").value = 'pw'
932        self.browser.getControl(
933            name="change_password_repeat").value = 'pw'
934        self.browser.getControl("Save").click()
935        self.assertTrue('Password must have at least' in self.browser.contents)
936        self.browser.getControl(name="change_password").value = 'new_password'
937        self.browser.getControl(
938            name="change_password_repeat").value = 'new_passssword'
939        self.browser.getControl("Save").click()
940        self.assertTrue('Passwords do not match' in self.browser.contents)
941        self.browser.getControl(name="change_password").value = 'new_password'
942        self.browser.getControl(
943            name="change_password_repeat").value = 'new_password'
944        self.browser.getControl("Save").click()
945        self.assertTrue('Password changed' in self.browser.contents)
946        # We are still logged in. Changing the password hasn't thrown us out.
947        self.browser.getLink("My Data").click()
948        self.assertEqual(self.browser.url, self.student_path)
949        # We can logout
950        self.browser.getLink("Logout").click()
951        self.assertTrue('You have been logged out' in self.browser.contents)
952        self.assertEqual(self.browser.url, 'http://localhost/app')
953        # We can login again with the new password
954        self.browser.getLink("Login").click()
955        self.browser.open(self.login_path)
956        self.browser.getControl(name="form.login").value = self.student_id
957        self.browser.getControl(name="form.password").value = 'new_password'
958        self.browser.getControl("Login").click()
959        self.assertEqual(self.browser.url, self.student_path)
960        self.assertTrue('You logged in' in self.browser.contents)
961        return
962
963    def test_setpassword(self):
964        # Set password for first-time access
965        student = Student()
966        student.reg_number = u'123456'
967        student.firstname = u'Klaus'
968        student.lastname = u'Tester'
969        self.app['students'].addStudent(student)
970        setpassword_path = 'http://localhost/app/setpassword'
971        student_path = 'http://localhost/app/students/%s' % student.student_id
972        self.browser.open(setpassword_path)
973        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
974        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
975        self.browser.getControl(name="reg_number").value = '223456'
976        self.browser.getControl("Show").click()
977        self.assertMatches('...No student found...',
978                           self.browser.contents)
979        self.browser.getControl(name="reg_number").value = '123456'
980        self.browser.getControl(name="ac_number").value = '999999'
981        self.browser.getControl("Show").click()
982        self.assertMatches('...Access code is invalid...',
983                           self.browser.contents)
984        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
985        self.browser.getControl("Show").click()
986        self.assertMatches('...Password has been set. Your Student Id is...',
987                           self.browser.contents)
988        self.browser.getControl("Show").click()
989        self.assertMatches(
990            '...Password has already been set. Your Student Id is...',
991            self.browser.contents)
992        existing_pwdpin = self.pwdpins[1]
993        parts = existing_pwdpin.split('-')[1:]
994        existing_pwdseries, existing_pwdnumber = parts
995        self.browser.getControl(name="ac_series").value = existing_pwdseries
996        self.browser.getControl(name="ac_number").value = existing_pwdnumber
997        self.browser.getControl(name="reg_number").value = '123456'
998        self.browser.getControl("Show").click()
999        self.assertMatches(
1000            '...You are using the wrong Access Code...',
1001            self.browser.contents)
1002        # The student can login with the new credentials
1003        self.browser.open(self.login_path)
1004        self.browser.getControl(name="form.login").value = student.student_id
1005        self.browser.getControl(
1006            name="form.password").value = self.existing_pwdnumber
1007        self.browser.getControl("Login").click()
1008        self.assertEqual(self.browser.url, student_path)
1009        self.assertTrue('You logged in' in self.browser.contents)
1010        return
1011
1012    def test_student_access(self):
1013        # Students can access their own objects
1014        # and can perform actions
1015        IWorkflowInfo(self.student).fireTransition('admit')
1016        self.browser.open(self.login_path)
1017        self.browser.getControl(name="form.login").value = self.student_id
1018        self.browser.getControl(name="form.password").value = 'spwd'
1019        self.browser.getControl("Login").click()
1020        # Student can upload a passport picture
1021        self.browser.open(self.student_path + '/change_portrait')
1022        ctrl = self.browser.getControl(name='passportuploadedit')
1023        file_obj = open(
1024            os.path.join(os.path.dirname(__file__), 'test_image.jpg'),'rb')
1025        file_ctrl = ctrl.mech_control
1026        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
1027        self.browser.getControl(
1028            name='upload_passportuploadedit').click()
1029        self.assertTrue(
1030            '<img align="middle" height="125px" src="passport.jpg" />'
1031            in self.browser.contents)
1032        # Student can view the clearance data
1033        self.browser.getLink("Clearance Data").click()
1034        # Student can't open clearance edit form before starting clearance
1035        self.browser.open(self.student_path + '/cedit')
1036        self.assertMatches('...The requested form is locked...',
1037                           self.browser.contents)
1038        self.browser.getLink("Clearance Data").click()
1039        self.browser.getLink("Start clearance").click()
1040        self.student.email = None
1041        # Uups, we forgot to fill the email fields
1042        self.browser.getControl("Start clearance").click()
1043        self.assertMatches('...Not all required fields filled...',
1044                           self.browser.contents)
1045        self.student.email = 'aa@aa.ng'
1046        self.browser.open(self.student_path + '/start_clearance')
1047        self.browser.getControl(name="ac_series").value = '3'
1048        self.browser.getControl(name="ac_number").value = '4444444'
1049        self.browser.getControl("Start clearance now").click()
1050        self.assertMatches('...Activation code is invalid...',
1051                           self.browser.contents)
1052        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1053        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1054        # Owner is Hans Wurst, AC can't be invalidated
1055        self.browser.getControl("Start clearance now").click()
1056        self.assertMatches('...You are not the owner of this access code...',
1057                           self.browser.contents)
1058        # Set the correct owner
1059        self.existing_clrac.owner = self.student_id
1060        self.browser.getControl("Start clearance now").click()
1061        self.assertMatches('...Clearance process has been started...',
1062                           self.browser.contents)
1063        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
1064        self.browser.getControl("Save", index=0).click()
1065        # Student can view the clearance data
1066        self.browser.getLink("Clearance Data").click()
1067        # and go back to the edit form
1068        self.browser.getLink("Edit").click()
1069        self.browser.getControl("Save and request clearance").click()
1070       
1071        self.browser.getControl(name="ac_series").value = self.existing_clrseries
1072        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
1073        self.browser.getControl("Request clearance now").click()
1074        self.assertMatches('...Clearance has been requested...',
1075                           self.browser.contents)
1076        # Student can't reopen clearance form after requesting clearance
1077        self.browser.open(self.student_path + '/cedit')
1078        self.assertMatches('...The requested form is locked...',
1079                           self.browser.contents)
1080        # Student can't add study level if not in state 'school fee paid'
1081        self.browser.open(self.student_path + '/studycourse/add')
1082        self.assertMatches('...The requested form is locked...',
1083                           self.browser.contents)
1084        # ... and must be transferred first
1085        IWorkflowInfo(self.student).fireTransition('clear')
1086        IWorkflowInfo(self.student).fireTransition('pay_first_school_fee')
1087        # Now students can add the current study level
1088        self.browser.getLink("Study Course").click()
1089        self.browser.getLink("Add course list").click()
1090        self.assertMatches('...Add current level 100 (Year 1)...',
1091                           self.browser.contents)
1092        self.browser.getControl("Create course list now").click()
1093        self.browser.getLink("100").click()
1094        self.browser.getLink("Add and remove courses").click()
1095        self.browser.getControl("Add course ticket").click()
1096        self.browser.getControl(name="form.course").value = ['COURSE1']
1097        self.browser.getControl("Add course ticket").click()
1098        self.assertMatches('...The ticket exists...',
1099                           self.browser.contents)
1100        self.student['studycourse'].current_level = 200
1101        self.browser.getLink("Study Course").click()
1102        self.browser.getLink("Add course list").click()
1103        self.assertMatches('...Add current level 200 (Year 2)...',
1104                           self.browser.contents)
1105        self.browser.getControl("Create course list now").click()
1106        self.browser.getLink("200").click()
1107        self.browser.getLink("Add and remove courses").click()
1108        self.browser.getControl("Add course ticket").click()
1109        self.browser.getControl(name="form.course").value = ['COURSE1']
1110        self.browser.getControl("Add course ticket").click()
1111        self.assertMatches('...Successfully added COURSE1...',
1112                           self.browser.contents)
1113        # Students can open the pdf course registration slip
1114        self.browser.open(self.student_path + '/studycourse/200')
1115        self.browser.getLink("Download course registration slip").click()
1116        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1117        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1118        # Students can remove course tickets
1119        self.browser.open(self.student_path + '/studycourse/200/edit')
1120        self.browser.getControl("Remove selected", index=0).click()
1121        self.assertTrue('No ticket selected' in self.browser.contents)
1122        ctrl = self.browser.getControl(name='val_id')
1123        ctrl.getControl(value='COURSE1').selected = True
1124        self.browser.getControl("Remove selected", index=0).click()
1125        self.assertTrue('Successfully removed' in self.browser.contents)
1126        self.browser.getControl("Register course list").click()
1127        self.assertTrue('Course list has been registered' in self.browser.contents)
1128        self.assertEqual(self.student.state, 'courses registered')
1129        return
1130
1131    def test_manage_payments(self):
1132        # Managers can add online school fee payment tickets
1133        # if certain requirements are met
1134        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1135        self.browser.open(self.payments_student_path)
1136        self.browser.getControl("Add online payment ticket").click()
1137        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1138        self.browser.getControl("Create ticket").click()
1139        self.assertMatches('...ticket created...',
1140                           self.browser.contents)
1141        ctrl = self.browser.getControl(name='val_id')
1142        value = ctrl.options[0]
1143        self.browser.getLink(value).click()
1144        self.assertMatches('...Amount Authorized...',
1145                           self.browser.contents)
1146        payment_url = self.browser.url
1147
1148        # The pdf payment receipt can't yet be opened
1149        self.browser.open(payment_url + '/payment_receipt.pdf')
1150        self.assertMatches('...Ticket not yet paid...',
1151                           self.browser.contents)
1152
1153        # The same payment (with same p_item, p_session and p_category)
1154        # can be initialized a second time if the former ticket is not yet paid.
1155        self.browser.open(self.payments_student_path)
1156        self.browser.getControl("Add online payment ticket").click()
1157        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1158        self.browser.getControl("Create ticket").click()
1159        self.assertMatches('...Payment ticket created...',
1160                           self.browser.contents)
1161
1162        # Managers can open the callback view which simulates a valid callback
1163        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1164        self.browser.open(payment_url)
1165        self.browser.getLink("Request callback").click()
1166        self.assertMatches('...Valid callback received...',
1167                          self.browser.contents)
1168
1169        # Callback can't be applied twice
1170        self.browser.open(payment_url + '/callback')
1171        self.assertMatches('...This ticket has already been paid...',
1172                          self.browser.contents)
1173
1174        # Now the first ticket is paid and no more ticket of same type
1175        # (with same p_item, p_session and p_category) can be added
1176        self.browser.open(self.payments_student_path)
1177        self.browser.getControl("Add online payment ticket").click()
1178        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1179        self.browser.getControl("Create ticket").click()
1180        self.assertMatches(
1181            '...This type of payment has already been made...',
1182            self.browser.contents)
1183
1184        # Managers can open the pdf payment receipt
1185        self.browser.open(payment_url)
1186        self.browser.getLink("Download payment receipt").click()
1187        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1188        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1189
1190        # Managers can remove online school fee payment tickets
1191        self.browser.open(self.payments_student_path)
1192        self.browser.getControl("Remove selected").click()
1193        self.assertMatches('...No payment selected...', self.browser.contents)
1194        ctrl = self.browser.getControl(name='val_id')
1195        value = ctrl.options[0]
1196        ctrl.getControl(value=value).selected = True
1197        self.browser.getControl("Remove selected", index=0).click()
1198        self.assertTrue('Successfully removed' in self.browser.contents)
1199
1200        # Managers can add online clearance payment tickets
1201        self.browser.open(self.payments_student_path + '/addop')
1202        self.browser.getControl(name="form.p_category").value = ['clearance']
1203        self.browser.getControl("Create ticket").click()
1204        self.assertMatches('...ticket created...',
1205                           self.browser.contents)
1206
1207        # Managers can open the callback view which simulates a valid callback
1208        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1209        ctrl = self.browser.getControl(name='val_id')
1210        value = ctrl.options[1] # The clearance payment is the second in the table
1211        self.browser.getLink(value).click()
1212        self.browser.open(self.browser.url + '/callback')
1213        self.assertMatches('...Valid callback received...',
1214                          self.browser.contents)
1215        expected = '''...
1216        <td>
1217          Paid
1218        </td>...'''
1219        #import pdb; pdb.set_trace()
1220        self.assertMatches(expected,self.browser.contents)
1221        # The new CLR-0 pin has been created
1222        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1223        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1224        ac = self.app['accesscodes']['CLR-0'][pin]
1225        ac.owner = self.student_id
1226        return
1227
1228    def test_student_payments(self):
1229        # Login
1230        self.browser.open(self.login_path)
1231        self.browser.getControl(name="form.login").value = self.student_id
1232        self.browser.getControl(name="form.password").value = 'spwd'
1233        self.browser.getControl("Login").click()
1234
1235        # Students can add online clearance payment tickets
1236        self.browser.open(self.payments_student_path + '/addop')
1237        self.browser.getControl(name="form.p_category").value = ['clearance']
1238        self.browser.getControl("Create ticket").click()
1239        self.assertMatches('...ticket created...',
1240                           self.browser.contents)
1241
1242        # Students can open the callback view which simulates a valid callback
1243        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1244        ctrl = self.browser.getControl(name='val_id')
1245        value = ctrl.options[0]
1246        self.browser.getLink(value).click()
1247        payment_url = self.browser.url
1248        self.browser.open(payment_url + '/callback')
1249        self.assertMatches('...Valid callback received...',
1250                          self.browser.contents)
1251        expected = '''...
1252        <td>
1253          Paid
1254        </td>...'''
1255        self.assertMatches(expected,self.browser.contents)
1256        # The new CLR-0 pin has been created
1257        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1258        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1259        ac = self.app['accesscodes']['CLR-0'][pin]
1260        ac.owner = self.student_id
1261
1262        # Students can open the pdf payment receipt
1263        self.browser.open(payment_url + '/payment_receipt.pdf')
1264        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1265        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1266
1267        # The new CLR-0 pin can be used for starting clearance
1268        # but they have to upload a passport picture first
1269        # which is only possible in state admitted
1270        self.browser.open(self.student_path + '/change_portrait')
1271        self.assertMatches('...form is locked...',
1272                          self.browser.contents)
1273        IWorkflowInfo(self.student).fireTransition('admit')
1274        self.browser.open(self.student_path + '/change_portrait')
1275        pseudo_image = StringIO('I pretend to be a graphics file')
1276        ctrl = self.browser.getControl(name='passportuploadedit')
1277        file_ctrl = ctrl.mech_control
1278        file_ctrl.add_file(pseudo_image, filename='my_photo.jpg')
1279        self.browser.getControl(
1280            name='upload_passportuploadedit').click()
1281        self.browser.open(self.student_path + '/start_clearance')
1282        parts = pin.split('-')[1:]
1283        clrseries, clrnumber = parts
1284        self.browser.getControl(name="ac_series").value = clrseries
1285        self.browser.getControl(name="ac_number").value = clrnumber
1286        self.browser.getControl("Start clearance now").click()
1287        self.assertMatches('...Clearance process has been started...',
1288                           self.browser.contents)
1289
1290        # Students can add online school fee payment tickets
1291        self.browser.open(self.payments_student_path)
1292        self.browser.getControl("Add online payment ticket").click()
1293        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1294        self.browser.getControl("Create ticket").click()
1295        self.assertMatches('...ticket created...',
1296                           self.browser.contents)
1297        ctrl = self.browser.getControl(name='val_id')
1298        value = ctrl.options[0]
1299        self.browser.getLink(value).click()
1300        self.assertMatches('...Amount Authorized...',
1301                           self.browser.contents)
1302
1303        # Students can open the callback view which simulates a valid callback
1304        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1305        self.browser.open(self.browser.url + '/callback')
1306        self.assertMatches('...Valid callback received...',
1307                          self.browser.contents)
1308
1309        # Students can remove only online payment tickets which have
1310        # not received a valid callback
1311        self.browser.open(self.payments_student_path)
1312        self.assertRaises(
1313            LookupError, self.browser.getControl, name='val_id')
1314        self.browser.open(self.payments_student_path + '/addop')
1315        self.browser.getControl(name="form.p_category").value = ['gown']
1316        self.browser.getControl("Create ticket").click()
1317        self.browser.open(self.payments_student_path)
1318        ctrl = self.browser.getControl(name='val_id')
1319        value = ctrl.options[0]
1320        ctrl.getControl(value=value).selected = True
1321        self.browser.getControl("Remove selected", index=0).click()
1322        self.assertTrue('Successfully removed' in self.browser.contents)
1323
1324        # The new SFE-0 pin can be used for starting course registration
1325        IWorkflowInfo(self.student).fireTransition('request_clearance')
1326        IWorkflowInfo(self.student).fireTransition('clear')
1327        self.browser.open(self.studycourse_student_path)
1328        self.browser.getLink('Start course registration').click()
1329        pin = self.app['accesscodes']['SFE-0'].keys()[0]
1330        parts = pin.split('-')[1:]
1331        sfeseries, sfenumber = parts
1332        self.browser.getControl(name="ac_series").value = sfeseries
1333        self.browser.getControl(name="ac_number").value = sfenumber
1334        self.browser.getControl("Start course registration now").click()
1335        self.assertMatches('...Course registration has been started...',
1336                           self.browser.contents)
1337        self.assertTrue(self.student.state == 'school fee paid')
1338        return
1339
1340    def test_manage_accommodation(self):
1341        # Managers can add online booking fee payment tickets and open the
1342        # callback view (see test_manage_payments)
1343        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1344        self.browser.open(self.payments_student_path)
1345        self.browser.getControl("Add online payment ticket").click()
1346        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1347        # If student is not in accommodation session, payment cannot be processed
1348        self.app['configuration'].accommodation_session = 2011
1349        self.browser.getControl("Create ticket").click()
1350        self.assertMatches('...Your current session does not match...',
1351                           self.browser.contents)
1352        self.app['configuration'].accommodation_session = 2004
1353        self.browser.getControl("Add online payment ticket").click()
1354        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1355        self.browser.getControl("Create ticket").click()
1356        ctrl = self.browser.getControl(name='val_id')
1357        value = ctrl.options[0]
1358        self.browser.getLink(value).click()
1359        self.browser.open(self.browser.url + '/callback')
1360        # The new HOS-0 pin has been created
1361        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1362        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1363        ac = self.app['accesscodes']['HOS-0'][pin]
1364        ac.owner = self.student_id
1365        parts = pin.split('-')[1:]
1366        sfeseries, sfenumber = parts
1367        # Managers can use HOS code and book a bed space with it
1368        self.browser.open(self.acco_student_path)
1369        self.browser.getLink("Book accommodation").click()
1370        self.assertMatches('...You are in the wrong...',
1371                           self.browser.contents)
1372        IWorkflowInfo(self.student).fireTransition('admit')
1373        # An existing HOS code can only be used if students
1374        # are in accommodation session
1375        self.student['studycourse'].current_session = 2003
1376        self.browser.getLink("Book accommodation").click()
1377        self.assertMatches('...Your current session does not match...',
1378                           self.browser.contents)
1379        self.student['studycourse'].current_session = 2004
1380        # All requirements are met and ticket can be created
1381        self.browser.getLink("Book accommodation").click()
1382        self.assertMatches('...Activation Code:...',
1383                           self.browser.contents)
1384        self.browser.getControl(name="ac_series").value = sfeseries
1385        self.browser.getControl(name="ac_number").value = sfenumber
1386        self.browser.getControl("Create bed ticket").click()
1387        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1388                           self.browser.contents)
1389        # Bed has been allocated
1390        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1391        self.assertTrue(bed1.owner == self.student_id)
1392        # BedTicketAddPage is now blocked
1393        self.browser.getLink("Book accommodation").click()
1394        self.assertMatches('...You already booked a bed space...',
1395            self.browser.contents)
1396        # The bed ticket displays the data correctly
1397        self.browser.open(self.acco_student_path + '/2004')
1398        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1399                           self.browser.contents)
1400        self.assertMatches('...2004/2005...', self.browser.contents)
1401        self.assertMatches('...regular_male_fr...', self.browser.contents)
1402        self.assertMatches('...%s...' % pin, self.browser.contents)
1403        # Managers can relocate students if the student's bed_type has changed
1404        self.browser.getLink("Relocate student").click()
1405        self.assertMatches(
1406            "...Student can't be relocated...", self.browser.contents)
1407        self.student.sex = u'f'
1408        self.browser.getLink("Relocate student").click()
1409        self.assertMatches(
1410            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1411        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1412        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1413        self.assertTrue(bed2.owner == self.student_id)
1414        self.assertTrue(self.student['accommodation'][
1415            '2004'].bed_type == u'regular_female_fr')
1416        # The payment object still shows the original payment item
1417        payment_id = self.student['payments'].keys()[0]
1418        payment = self.student['payments'][payment_id]
1419        self.assertTrue(payment.p_item == u'regular_male_fr')
1420        # Managers can relocate students if the bed's bed_type has changed
1421        bed1.bed_type = u'regular_female_fr'
1422        bed2.bed_type = u'regular_male_fr'
1423        notify(grok.ObjectModifiedEvent(bed1))
1424        notify(grok.ObjectModifiedEvent(bed2))
1425        self.browser.getLink("Relocate student").click()
1426        self.assertMatches(
1427            "...Student relocated...", self.browser.contents)
1428        self.assertMatches(
1429            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1430        self.assertMatches(bed1.owner, self.student_id)
1431        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1432        # Managers can't relocate students if bed is reserved
1433        self.student.sex = u'm'
1434        bed1.bed_type = u'regular_female_reserved'
1435        notify(grok.ObjectModifiedEvent(bed1))
1436        self.browser.getLink("Relocate student").click()
1437        self.assertMatches(
1438            "...Students in reserved beds can't be relocated...",
1439            self.browser.contents)
1440        # Managers can relocate students if booking has been cancelled but
1441        # other bed space has been manually allocated after cancellation
1442        old_owner = bed1.releaseBed()
1443        self.assertMatches(old_owner, self.student_id)
1444        bed2.owner = self.student_id
1445        self.browser.open(self.acco_student_path + '/2004')
1446        self.assertMatches(
1447            "...booking cancelled...", self.browser.contents)
1448        self.browser.getLink("Relocate student").click()
1449        # We didn't informed the catalog therefore the new owner is not found
1450        self.assertMatches(
1451            "...There is no free bed in your category regular_male_fr...",
1452            self.browser.contents)
1453        # Now we fire the event properly
1454        notify(grok.ObjectModifiedEvent(bed2))
1455        self.browser.getLink("Relocate student").click()
1456        self.assertMatches(
1457            "...Student relocated...", self.browser.contents)
1458        self.assertMatches(
1459            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1460          # Managers can delete bed tickets
1461        self.browser.open(self.acco_student_path)
1462        ctrl = self.browser.getControl(name='val_id')
1463        value = ctrl.options[0]
1464        ctrl.getControl(value=value).selected = True
1465        self.browser.getControl("Remove selected", index=0).click()
1466        self.assertMatches('...Successfully removed...', self.browser.contents)
1467        # The bed has been properly released by the event handler
1468        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1469        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1470        return
1471
1472    def test_student_accommodation(self):
1473        # Login
1474        self.browser.open(self.login_path)
1475        self.browser.getControl(name="form.login").value = self.student_id
1476        self.browser.getControl(name="form.password").value = 'spwd'
1477        self.browser.getControl("Login").click()
1478
1479        # Students can add online booking fee payment tickets and open the
1480        # callback view (see test_manage_payments)
1481        self.browser.getLink("Payments").click()
1482        self.browser.getControl("Add online payment ticket").click()
1483        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1484        self.browser.getControl("Create ticket").click()
1485        ctrl = self.browser.getControl(name='val_id')
1486        value = ctrl.options[0]
1487        self.browser.getLink(value).click()
1488        self.browser.open(self.browser.url + '/callback')
1489        # The new HOS-0 pin has been created
1490        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1491        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1492        ac = self.app['accesscodes']['HOS-0'][pin]
1493        ac.owner = u'Anybody'
1494        parts = pin.split('-')[1:]
1495        sfeseries, sfenumber = parts
1496
1497        # Students can use HOS code and book a bed space with it
1498        self.browser.open(self.acco_student_path)
1499        self.browser.getLink("Book accommodation").click()
1500        self.assertMatches('...You are in the wrong...',
1501                           self.browser.contents)
1502        IWorkflowInfo(self.student).fireTransition('admit')
1503        self.browser.getLink("Book accommodation").click()
1504        self.assertMatches('...Activation Code:...',
1505                           self.browser.contents)
1506        self.browser.getControl(name="ac_series").value = u'nonsense'
1507        self.browser.getControl(name="ac_number").value = sfenumber
1508        self.browser.getControl("Create bed ticket").click()
1509        self.assertMatches('...Activation code is invalid...',
1510                           self.browser.contents)
1511        self.browser.getControl(name="ac_series").value = sfeseries
1512        self.browser.getControl(name="ac_number").value = sfenumber
1513        self.browser.getControl("Create bed ticket").click()
1514        self.assertMatches('...You are not the owner of this access code...',
1515                           self.browser.contents)
1516        ac.owner = self.student_id
1517        self.browser.getControl(name="ac_series").value = sfeseries
1518        self.browser.getControl(name="ac_number").value = sfenumber
1519        self.browser.getControl("Create bed ticket").click()
1520        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1521                           self.browser.contents)
1522
1523        # Bed has been allocated
1524        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
1525        self.assertTrue(bed.owner == self.student_id)
1526
1527        # BedTicketAddPage is now blocked
1528        self.browser.getLink("Book accommodation").click()
1529        self.assertMatches('...You already booked a bed space...',
1530            self.browser.contents)
1531
1532        # The bed ticket displays the data correctly
1533        self.browser.open(self.acco_student_path + '/2004')
1534        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1535                           self.browser.contents)
1536        self.assertMatches('...2004/2005...', self.browser.contents)
1537        self.assertMatches('...regular_male_fr...', self.browser.contents)
1538        self.assertMatches('...%s...' % pin, self.browser.contents)
1539
1540        # Students can open the pdf slip
1541        self.browser.open(self.browser.url + '/bed_allocation.pdf')
1542        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1543        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1544
1545        # Students can't relocate themselves
1546        self.assertFalse('Relocate' in self.browser.contents)
1547        relocate_path = self.acco_student_path + '/2004/relocate'
1548        self.assertRaises(
1549            Unauthorized, self.browser.open, relocate_path)
1550
1551        # Students can't the Remove button and check boxes
1552        self.browser.open(self.acco_student_path)
1553        self.assertFalse('Remove' in self.browser.contents)
1554        self.assertFalse('val_id' in self.browser.contents)
1555        return
Note: See TracBrowser for help on using the repository browser.