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

Last change on this file since 13375 was 13349, checked in by Henrik Bettermann, 9 years ago

Tell the student which data are missing.

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