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

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

Use absolute URLs. Otherwise images and files might not be found.

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